147 lines
4.6 KiB
C#
147 lines
4.6 KiB
C#
using System.Text.Json;
|
|
using AbsPod.Model;
|
|
using AbsPod.Util;
|
|
|
|
var configDir = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
"abspod");
|
|
var configFile = Path.Combine(configDir, "config.json");
|
|
|
|
// if no config file, generate one and exit
|
|
if (!Directory.Exists(configDir)
|
|
|| !File.Exists(configFile)) {
|
|
// first run
|
|
var exampleOptions = new AbsPodConfig {
|
|
HostUrl = "https://myaudiobookshelfserver.com",
|
|
Username = "myuser",
|
|
Password = "mypassword",
|
|
LibraryId = Guid.Empty,
|
|
SyncToDirectory = "/path/to/podcasts",
|
|
MaxEpisodesPerPodcast = 10,
|
|
};
|
|
|
|
if (!Directory.Exists(configDir))
|
|
Directory.CreateDirectory(configDir);
|
|
|
|
File.WriteAllText(configFile, JsonSerializer.Serialize(
|
|
exampleOptions, JsonOpts.Options));
|
|
Console.WriteLine(
|
|
$"Generated example config at {configFile}. Manually edit before running again.");
|
|
Environment.Exit(0);
|
|
}
|
|
|
|
AbsPodConfig? config;
|
|
var configStr = File.ReadAllText(configFile);
|
|
config = JsonSerializer.Deserialize<AbsPodConfig>(configStr, JsonOpts.Options);
|
|
|
|
if (config == null) {
|
|
Console.WriteLine("Unable to read config.");
|
|
Environment.Exit(1);
|
|
}
|
|
|
|
if (!Directory.Exists(config.SyncToDirectory)) {
|
|
Console.WriteLine($"Sync directory {config.SyncToDirectory} does not exist.");
|
|
Environment.Exit(1);
|
|
}
|
|
|
|
var podMetaPath = Path.Combine(config.SyncToDirectory, ".podmeta.json");
|
|
var podMeta = File.Exists(podMetaPath)
|
|
? JsonSerializer.Deserialize<PodcastMeta>(File.ReadAllText(podMetaPath), JsonOpts.Options)
|
|
: new() { Podcasts = [] };
|
|
|
|
if (podMeta == null) {
|
|
Console.WriteLine("Error reading podcast metadata.");
|
|
Environment.Exit(1);
|
|
}
|
|
|
|
var client = new AbsClient(
|
|
config.HostUrl,
|
|
config.Username,
|
|
config.Password);
|
|
|
|
if (!await client.Authenticate()) {
|
|
Console.WriteLine("Failed to authenticate.");
|
|
Environment.Exit(1);
|
|
}
|
|
|
|
var podcasts = await client.GetPodcasts(config.LibraryId);
|
|
|
|
foreach (var (podcastId, podcastName) in podcasts) {
|
|
var meta = podMeta.Podcasts.SingleOrDefault(p => p.Id == podcastId);
|
|
if (meta == null) {
|
|
Console.WriteLine($"Found new podcast: {podcastName}.");
|
|
meta = new() {
|
|
Id = podcastId,
|
|
};
|
|
podMeta.Podcasts.Add(meta);
|
|
} else {
|
|
Console.WriteLine($"Processing podcast: {podcastName}.");
|
|
}
|
|
|
|
var podDir = Path.Combine(config.SyncToDirectory, FileNamer.PodDir(podcastName));
|
|
if (!Directory.Exists(podDir)) Directory.CreateDirectory(podDir);
|
|
|
|
var episodes = await client.GetEpisodes(podcastId);
|
|
var lastDownloadedIndex = -1;
|
|
if (meta.LastEpisodeId.HasValue) {
|
|
foreach (var (episodeId, _, _, _) in episodes) {
|
|
lastDownloadedIndex++;
|
|
if (episodeId == meta.LastEpisodeId) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
var downloadedCount = 0;
|
|
for (var i = 0; i < episodes.Length; i++) {
|
|
var (episodeId, episodeName, episodeDate, extension) = episodes[i];
|
|
var epFile = Path.Combine(podDir, FileNamer.EpFile(episodeName, episodeDate, extension));
|
|
var fileExists = File.Exists(epFile);
|
|
var isFinished = await client.GetMediaFinished(podcastId, episodeId);
|
|
|
|
if (i <= lastDownloadedIndex) {
|
|
// handle files we already may have downloaded
|
|
if (fileExists) {
|
|
if (isFinished) {
|
|
// episode was finished somewhere else, delete it
|
|
Console.WriteLine($"Deleting finished episode: {episodeName}");
|
|
File.Delete(epFile);
|
|
} else {
|
|
// episode was downloaded and not yet finished, keep it
|
|
downloadedCount++;
|
|
}
|
|
} else {
|
|
if (!isFinished) {
|
|
// file was previously downloaded and deleted, mark it as finished on the server
|
|
Console.WriteLine($"Marking episode finished: {episodeName}");
|
|
await client.SetMediaFinished(podcastId, episodeId);
|
|
}
|
|
}
|
|
} else {
|
|
if (fileExists) {
|
|
// probably means we crashed last time and didn't save metadata
|
|
// let's delete it and re-process
|
|
File.Delete(epFile);
|
|
}
|
|
|
|
// handle new episodes not yet downloaded
|
|
if (downloadedCount >= config.MaxEpisodesPerPodcast) {
|
|
// if we're already at the max we don't need to download anymore
|
|
break;
|
|
}
|
|
|
|
if (!isFinished) {
|
|
// download the episode
|
|
await client.DownloadEpisode(podcastId, episodeId, episodeName, epFile);
|
|
meta.LastEpisodeId = episodeId;
|
|
downloadedCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// save metadata
|
|
var oldPath = $"{podMetaPath}.old";
|
|
if (File.Exists(oldPath)) File.Delete(oldPath);
|
|
if (File.Exists(podMetaPath)) File.Move(podMetaPath, oldPath);
|
|
File.WriteAllText(podMetaPath, JsonSerializer.Serialize(podMeta, JsonOpts.Options));
|