Skip to content

Commit

Permalink
Skips download if file exists (unless overridden)
Browse files Browse the repository at this point in the history
New Feature: If a episode has already been downloaded, then it won't be
downloaded when the program runs again - it will be skipped.  Previously
downloaded episodes are matched based on the file size and the file
name.

If a download already exists, but the size of the file is different from
what is being downloaded, then it is considered to be an incomplete
download and will be overwritten (resuming downloads is not supported at
this time).

This behavior can be overwritten using the -o (or -overwrite) flag,
which will cause an existing download to be overwritten regardless of
whether or not the download completed fully on a previous download
attempt.
  • Loading branch information
codethug committed Apr 25, 2016
1 parent dc43440 commit 19c255f
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 86 deletions.
96 changes: 96 additions & 0 deletions SimpleTv.Downloader/Arguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using Fclp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SimpleTv.Downloader
{
public static class Arguments
{
public static FluentCommandLineParser<Configuration> Setup()
{
var p = new FluentCommandLineParser<Configuration>();

p.Setup(arg => arg.Username)
.As('u', "username")
.WithDescription("Username for logging into Simple.Tv")
.Required();

p.Setup(arg => arg.Password)
.As('p', "password")
.WithDescription("Password for logging into Simple.Tv")
.Required();

// Later on, -d / -dryrun will be used to do a dry run (parsing without downloading)

p.Setup(arg => arg.ShowIncludeFilter)
.As('i', "includeShows")
.WithDescription("[optional] Type in show(s) to include. Wildcards accepted.")
.SetDefault("*");

p.Setup(arg => arg.ShowExcludeFilter)
.As('x', "excludeShows")
.WithDescription("[optional] Type in show(s) to exclude. Wildcards accepted.")
.SetDefault(string.Empty);

p.Setup(arg => arg.ServerIncludeFilter)
.As('s', "includeServers")
.WithDescription("[optional] Type in media server(s) to include. Wildcards accepted.")
.SetDefault("*");

p.Setup(arg => arg.ServerExcludeFilter)
.As('e', "excludeServers")
.WithDescription("[optional] Type in media server(s) to exclude. Wildcards accepted.")
.SetDefault(string.Empty);

p.Setup(arg => arg.DownloadFolder)
.As('f', "downloadFolder")
.WithDescription("[optional] Folder to place downloaded recordings in. Defaults to current working directory.")
.SetDefault(Directory.GetCurrentDirectory());

p.Setup(arg => arg.FolderFormat)
.As('t', "folderformat")
.WithDescription("[optional] The folder format for saving the recording, relative to the downloadfolder. Defaults to Plex format defined at https://support.plex.tv/hc/en-us/articles/200220687-Naming-Series-Season-Based-TV-Shows")
.SetDefault("{ShowName}\\Season {SeasonNumber00}");

p.Setup(arg => arg.FilenameFormat)
.As('n', "filenameformat")
.WithDescription("[optional] The filename format for saving the recording. Defaults to Plex format defined at https://support.plex.tv/hc/en-us/articles/200220687-Naming-Series-Season-Based-TV-Shows")
.SetDefault("{ShowName} - S{SeasonNumber00}E{EpisodeNumber00} - {EpisodeName}.mp4");

p.Setup(arg => arg.OverwriteExistingDownloads)
.As('o', "overwrite")
.WithDescription("[optional] Will overwrite previously downloaded episodes. If you do not set this, episodes that have previously been downloaded will be skipped.")
.SetDefault(false);

p.Setup(arg => arg.LogHttpCalls)
.As('l', "logHttpCalls")
.WithDescription("[optional] Will save a log of all http calls, helpful for debugging errors")
.SetDefault(false);

p.Setup(arg => arg.Reboot)
.As('r', "reboot")
.WithDescription("Used to reboot Simple.TV DVRs. Can be used with -s and -t.")
.SetDefault(false);

p.SetupHelp("?", "help")
.WithHeader(string.Format(
"SimpleTV Downloader\n" +
"Version {0}\n" +
"\n" +
"SimpleTV Downloader can download your Simple.Tv recordings. \r\n" +
"Usage:\r\n" +
"\t{1} -u [email protected] -p \"P@ssw0Rd\" -d c:\\tvshows -i \"NCIS*\"",
Assembly.GetExecutingAssembly().GetName().Version,
Path.GetFileName(Assembly.GetEntryAssembly().Location)
))
.Callback(text => Console.WriteLine(text));

return p;
}
}
}
1 change: 1 addition & 0 deletions SimpleTv.Downloader/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class Configuration
public string DownloadFolder { get; set; }
public string FolderFormat { get; set; }
public string FilenameFormat { get; set; }
public bool OverwriteExistingDownloads { get; set; }
public string ShowIncludeFilter { get; set; }
public string ShowExcludeFilter { get; set; }
public string ServerIncludeFilter { get; set; }
Expand Down
14 changes: 12 additions & 2 deletions SimpleTv.Downloader/Downloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,20 @@ public void Download()
episode.EpisodeName,
episode.DateTime.IfNotNull(d => d.Value.ToString("d")),
episode.Error));
} else
continue;
}

var downloadDetails = tvClient.PrepareDownload(episode, config.DownloadFolder, config.FolderFormat, config.FilenameFormat);
if (downloadDetails.FileExistsWithSameSize && !config.OverwriteExistingDownloads)
{
tvClient.DownloadEpisode(episode, config.DownloadFolder, config.FolderFormat, config.FilenameFormat);
Console.WriteLine(string.Format(
"Skipping \"{0}\" from {1}: Episode already downloaded",
episode.EpisodeName,
episode.DateTime.IfNotNull(d => d.Value.ToString("d"))));
continue;
}

tvClient.DownloadEpisode(downloadDetails);
}
catch (StreamNotFoundException e)
{
Expand Down
81 changes: 1 addition & 80 deletions SimpleTv.Downloader/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@ namespace SimpleTv.Downloader
{
class Program
{
private static string execName = Path.GetFileName(System.Reflection.Assembly.GetEntryAssembly().Location);

static void Main(string[] args)
{
var p = SetupArguments();
var p = Arguments.Setup();
var result = p.Parse(args);
if (result.HasErrors)
{
Expand Down Expand Up @@ -55,82 +53,5 @@ static void Main(string[] args)
}
}
}

public static FluentCommandLineParser<Configuration> SetupArguments()
{
var p = new FluentCommandLineParser<Configuration>();

p.Setup(arg => arg.Username)
.As('u', "username")
.WithDescription("Username for logging into Simple.Tv")
.Required();

p.Setup(arg => arg.Password)
.As('p', "password")
.WithDescription("Password for logging into Simple.Tv")
.Required();

// Later on, -d / -dryrun will be used to do a dry run (parsing without downloading)

p.Setup(arg => arg.ShowIncludeFilter)
.As('i', "includeShows")
.WithDescription("[optional] Type in show(s) to include. Wildcards accepted.")
.SetDefault("*");

p.Setup(arg => arg.ShowExcludeFilter)
.As('x', "excludeShows")
.WithDescription("[optional] Type in show(s) to exclude. Wildcards accepted.")
.SetDefault(string.Empty);

p.Setup(arg => arg.ServerIncludeFilter)
.As('s', "includeServers")
.WithDescription("[optional] Type in media server(s) to include. Wildcards accepted.")
.SetDefault("*");

p.Setup(arg => arg.ServerExcludeFilter)
.As('e', "excludeServers")
.WithDescription("[optional] Type in media server(s) to exclude. Wildcards accepted.")
.SetDefault(string.Empty);

p.Setup(arg => arg.DownloadFolder)
.As('f', "downloadFolder")
.WithDescription("[optional] Folder to place downloaded recordings in. Defaults to current working directory.")
.SetDefault(Directory.GetCurrentDirectory());

p.Setup(arg => arg.FolderFormat)
.As('t', "folderformat")
.WithDescription("[optional] The folder format for saving the recording, relative to the downloadfolder. Defaults to Plex format defined at https://support.plex.tv/hc/en-us/articles/200220687-Naming-Series-Season-Based-TV-Shows")
.SetDefault("{ShowName}\\Season {SeasonNumber00}");

p.Setup(arg => arg.FilenameFormat)
.As('n', "filenameformat")
.WithDescription("[optional] The filename format for saving the recording. Defaults to Plex format defined at https://support.plex.tv/hc/en-us/articles/200220687-Naming-Series-Season-Based-TV-Shows")
.SetDefault("{ShowName} - S{SeasonNumber00}E{EpisodeNumber00} - {EpisodeName}.mp4");

p.Setup(arg => arg.LogHttpCalls)
.As('l', "logHttpCalls")
.WithDescription("[optional] Will save a log of all http calls, helpful for debugging errors")
.SetDefault(false);

p.Setup(arg => arg.Reboot)
.As('r', "reboot")
.WithDescription("Used to reboot Simple.TV DVRs. Can be used with -s and -t.")
.SetDefault(false);

p.SetupHelp("?", "help")
.WithHeader(string.Format(
"SimpleTV Downloader\n" +
"Version {0}\n" +
"\n" +
"SimpleTV Downloader can download your Simple.Tv recordings. \r\n" +
"Usage:\r\n" +
"\t{1} -u [email protected] -p \"P@ssw0Rd\" -d c:\\tvshows -i \"NCIS*\"",
Assembly.GetExecutingAssembly().GetName().Version, execName
))
.Callback(text => Console.WriteLine(text));

return p;
}

}
}
1 change: 1 addition & 0 deletions SimpleTv.Downloader/SimpleTv.Downloader.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Arguments.cs" />
<Compile Include="Configuration.cs" />
<Compile Include="StvConsole.cs" />
<Compile Include="Downloader.cs" />
Expand Down
1 change: 1 addition & 0 deletions SimpleTv.Sdk/Http/ISimpleTvHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ public interface ISimpleTvHttpClient
List<Episode> GetEpisodes(Show show);
string GetEpisodeLocation(Episode episode);
void Download(Uri fullPathToVideo, string fileName, string episodeName);
long GetFileSize(Uri uri);
}
}
5 changes: 5 additions & 0 deletions SimpleTv.Sdk/Http/SimpleTvHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,5 +293,10 @@ private void Client_DownloadProgressChanged(object sender, DownloadProgressChang
Console.Write(" ");
}
}

public long GetFileSize(Uri uri)
{
return webClient.GetFileSize(uri);
}
}
}
17 changes: 17 additions & 0 deletions SimpleTv.Sdk/Models/DownloadDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SimpleTv.Sdk.Models
{
public class DownloadDetails
{
public Episode Episode { get; set; }
public bool FileExistsWithSameSize { get; set; }
public Uri FullUriToVideo { get; set; }
public long DownloadSize { get; set;}
public string FilePathAndName { get; set; }
}
}
1 change: 1 addition & 0 deletions SimpleTv.Sdk/SimpleTv.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<Compile Include="Http\ISimpleTvHttpClient.cs" />
<Compile Include="Http\IWebClient.cs" />
<Compile Include="Http\SimpleTvHttpClient.cs" />
<Compile Include="Models\DownloadDetails.cs" />
<Compile Include="Models\Episode.cs" />
<Compile Include="Models\INamed.cs" />
<Compile Include="Models\MediaServer.cs" />
Expand Down
18 changes: 14 additions & 4 deletions SimpleTv.Sdk/SimpleTvClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using SimpleTv.Sdk.Http;
using SimpleTv.Sdk.Diagnostics;
using System;
using System.IO;
using SimpleTv.Sdk.Naming;
using System.Linq;

Expand Down Expand Up @@ -61,12 +62,21 @@ public void Reboot(MediaServer server)
tvHttpClient.Reboot(server);
}

public void DownloadEpisode(Episode episode, string downloadFolder, string folderFormat, string filenameFormat)
public DownloadDetails PrepareDownload(Episode episode, string baseFolder, string folderFormat, string fileNameFormat)
{
var fileName = episode.GenerateFileName(downloadFolder, folderFormat, filenameFormat);
var fullPathToVideo = new Uri(episode.Show.Server.StreamBaseUrl + tvHttpClient.GetEpisodeLocation(episode));
var downloadDetails = new DownloadDetails();
downloadDetails.Episode = episode;
downloadDetails.FilePathAndName = episode.GenerateFileName(baseFolder, folderFormat, fileNameFormat);
downloadDetails.FullUriToVideo = new Uri(episode.Show.Server.StreamBaseUrl + tvHttpClient.GetEpisodeLocation(episode));
downloadDetails.DownloadSize = tvHttpClient.GetFileSize(downloadDetails.FullUriToVideo);
downloadDetails.FileExistsWithSameSize = File.Exists(downloadDetails.FilePathAndName) && (new FileInfo(downloadDetails.FilePathAndName)).Length == downloadDetails.DownloadSize;

tvHttpClient.Download(fullPathToVideo, fileName, episode.EpisodeName);
return downloadDetails;
}

public void DownloadEpisode(DownloadDetails downloadDetails)
{
tvHttpClient.Download(downloadDetails.FullUriToVideo, downloadDetails.FilePathAndName, downloadDetails.Episode.EpisodeName);
}
}
}
9 changes: 9 additions & 0 deletions doc/Parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ The `-t` or `-folderformat` and `-n` or `-filenameformat` can
customize the folder names and filenames of the downloaded episodes. For more
details, see the [NamingFormat documentation](doc/NamingFormat.md).

## Overwrite Existing episodes

By default, if the downloader finds that an episode has already been downloaded,
it will skip the episode and not download it again. The episode to be downloaded
is checked against existing files based on the filename and the size of the file.

The `-o` or `-overwrite` flag will cause the downloader to overwrite the
previously downloaded files.

## Reboot

The `-r` or `-reboot` is used to reboot the SimpleTV DVR. This can be used
Expand Down

0 comments on commit 19c255f

Please sign in to comment.