diff --git a/CHANGELOG.md b/CHANGELOG.md
index de7b8255..261d389e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,19 @@
+# 0.2.9.0
+
+## New features
+
+* Starting next version, the changelog will be displayed when a new version is available.
+* Starting next version, an option will allow to directly download the new version from the application.
+
+## Improvements
+
+* Minor UI improvements.
+
+## Bug fixes
+
+* Fixed playlist creation for tracks with foreign characters. [#82](https://github.com/Otiel/BandcampDownloader/issues/82)
+* Fixed application crash when saving tracks to a path with more than 260 characters.
+
# 0.2.8.2
## Bug fixes
@@ -8,7 +24,7 @@
## Bug fixes
-* Fixed a bug preventing the "Playlist file name format" setting to be saved in the _ini_ file.
+* Fixed a bug preventing the "Playlist file name format" setting to be saved in the _ini_ file.
# 0.2.8.0
diff --git a/docs/dependencies.md b/docs/dependencies.md
index 450cd96a..9e0f9d1c 100644
--- a/docs/dependencies.md
+++ b/docs/dependencies.md
@@ -9,6 +9,8 @@ BandcampDownloader relies on a number of open-source libraries, all listed below
* [`HtmlAgilityPack`](https://github.com/zzzprojects/html-agility-pack): used to parse Html from bandcamp.com pages.
* [`ImageResizer`](https://github.com/imazen/resizer): used to resize/compress the album covers.
* [`Json.NET`](https://github.com/JamesNK/Newtonsoft.Json): used to parse Json from bandcamp.com pages.
+* [`Markdig`](https://github.com/lunet-io/markdig): required by `Markdig.WPF`.
+* [`Markdig.WPF`](https://github.com/Kryptos-FR/markdig.wpf): used to display Markdown.
* [`MessageBoxManager`](https://www.codeproject.com/Articles/18399/Localizing-System-MessageBox): used to localize the buttons of the Windows system message box.
* [`NLog`](https://github.com/NLog/NLog): used to log events.
* [`PlaylistsNET`](https://github.com/tmk907/PlaylistsNET): used to create playlists.
diff --git a/src/BandcampDownloader/BandcampDownloader.csproj b/src/BandcampDownloader/BandcampDownloader.csproj
index d82479f7..263d3866 100644
--- a/src/BandcampDownloader/BandcampDownloader.csproj
+++ b/src/BandcampDownloader/BandcampDownloader.csproj
@@ -94,6 +94,12 @@
..\packages\ImageResizer.4.2.5\lib\net45\ImageResizer.dll
+
+ ..\packages\Markdig.0.16.0\lib\net40\Markdig.dll
+
+
+ ..\packages\Markdig.Wpf.0.2.8\lib\net452\Markdig.Wpf.dll
+
@@ -142,18 +148,27 @@
MSBuild:Compile
Designer
+
+
+
-
+
+
+
+ UserControlChangelog.xaml
+
+
+ WindowUpdate.xaml
+
-
@@ -229,6 +244,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
@@ -237,6 +256,10 @@
MSBuild:Compile
Designer
+
+ Designer
+ MSBuild:Compile
+
@@ -252,11 +275,15 @@
Settings.settings
True
-
+
+ Designer
+
- Resources.fr.Designer.cs
+ Designer
+
+
+ Designer
-
ResXFileCodeGenerator
Resources.Designer.cs
diff --git a/src/BandcampDownloader/Core/Constants.cs b/src/BandcampDownloader/Core/Constants.cs
index c12208e0..3354dba2 100644
--- a/src/BandcampDownloader/Core/Constants.cs
+++ b/src/BandcampDownloader/Core/Constants.cs
@@ -10,6 +10,10 @@ internal static class Constants {
///
public static readonly String AppVersion = Assembly.GetEntryAssembly().GetName().Version.ToString();
///
+ /// The URL redirecting to the changelog file on GitHub.
+ ///
+ public static readonly String ChangelogUrl = "https://raw.githubusercontent.com/Otiel/BandcampDownloader/master/CHANGELOG.md";
+ ///
/// The URL redirecting to the help page on translating the app on GitHub.
///
public static readonly String HelpTranslateWebsite = "https://github.com/Otiel/BandcampDownloader/blob/master/docs/help-translate.md";
@@ -33,5 +37,9 @@ internal static class Constants {
/// The absolute path to the settings file.
///
public static readonly String UserSettingsFilePath = Directory.GetParent(Assembly.GetExecutingAssembly().Location) + @"\BandcampDownloader.ini";
+ ///
+ /// The URL redirecting to the zip file. Must be formatted with a version.
+ ///
+ public static readonly String ZipUrl = "https://github.com/Otiel/BandcampDownloader/releases/download/v{0}/BandcampDownloader.zip";
}
}
\ No newline at end of file
diff --git a/src/BandcampDownloader/Core/DownloadManager.cs b/src/BandcampDownloader/Core/DownloadManager.cs
new file mode 100644
index 00000000..d0bbcdac
--- /dev/null
+++ b/src/BandcampDownloader/Core/DownloadManager.cs
@@ -0,0 +1,579 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using ImageResizer;
+
+namespace BandcampDownloader {
+
+ internal class DownloadManager {
+ ///
+ /// The URLs to download.
+ ///
+ private readonly String _urls;
+ ///
+ /// The albums to download.
+ ///
+ private List _albums;
+ ///
+ /// True if we received the order to cancel downloads; false otherwise.
+ ///
+ private Boolean _cancelDownloads;
+ ///
+ /// The list of WebClients currently used to download files. Used when downloads must be cancelled.
+ ///
+ private List _pendingDownloads = new List();
+
+ ///
+ /// The files to download, or being downloaded, or already downloaded. Used to compute the current received bytes and the total bytes to download.
+ ///
+ public List DownloadingFiles { get; set; }
+
+ public delegate void LogAddedEventHandler(object sender, LogArgs eventArgs);
+
+ ///
+ /// Initializes a new instance of DownloadManager.
+ ///
+ /// The URLs we'll download from.
+ public DownloadManager(String urls) {
+ _urls = urls;
+ }
+
+ ///
+ /// Cancels all downloads.
+ ///
+ public void CancelDownloads() {
+ _cancelDownloads = true;
+
+ lock (_pendingDownloads) {
+ // Stop current downloads
+ foreach (WebClient webClient in _pendingDownloads) {
+ webClient.CancelAsync();
+ }
+ }
+ }
+
+ ///
+ /// Fetch albums data from the URLs specified when creating this DownloadManager.
+ ///
+ public async Task FetchUrlsAsync() {
+ var urls = _urls.Split(new String[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList();
+ urls = urls.Distinct().ToList();
+
+ // Get URLs of albums to download
+ if (App.UserSettings.DownloadArtistDiscography) {
+ urls = await GetArtistDiscographyAsync(urls);
+ }
+
+ // Get info on albums
+ _albums = await GetAlbumsAsync(urls);
+
+ // Save the files to download and get their size
+ DownloadingFiles = await GetFilesToDownloadAsync(_albums);
+ }
+
+ ///
+ /// Starts downloads.
+ ///
+ public async Task StartDownloadsAsync() {
+ if (_albums == null) {
+ throw new Exception("Must call FetchUrls before calling StartDownloadsAsync");
+ }
+
+ _pendingDownloads = new List();
+
+ // Start downloading albums
+ if (App.UserSettings.DownloadOneAlbumAtATime) {
+ // Download one album at a time
+ foreach (Album album in _albums) {
+ await DownloadAlbumAsync(album);
+ }
+ } else {
+ // Parallel download
+ Int32[] albumsIndexes = Enumerable.Range(0, _albums.Count).ToArray();
+ await Task.WhenAll(albumsIndexes.Select(i => DownloadAlbumAsync(_albums[i])));
+ }
+ }
+
+ ///
+ /// Downloads an album.
+ ///
+ /// The album to download.
+ private async Task DownloadAlbumAsync(Album album) {
+ if (_cancelDownloads) {
+ // Abort
+ return;
+ }
+
+ // Create directory to place track files
+ try {
+ Directory.CreateDirectory(album.Path);
+ } catch {
+ LogAdded(this, new LogArgs("An error occured when creating the album folder. Make sure you have the rights to write files in the folder you chose", LogType.Error));
+ return;
+ }
+
+ TagLib.Picture artwork = null;
+
+ // Download artwork
+ if ((App.UserSettings.SaveCoverArtInTags || App.UserSettings.SaveCoverArtInFolder) && album.HasArtwork) {
+ artwork = await DownloadCoverArtAsync(album);
+ }
+
+ // Download & tag tracks
+ Boolean[] tracksDownloaded = new Boolean[album.Tracks.Count];
+ Int32[] indexes = Enumerable.Range(0, album.Tracks.Count).ToArray();
+ await Task.WhenAll(indexes.Select(async i => tracksDownloaded[i] = await DownloadAndTagTrackAsync(album, album.Tracks[i], artwork)));
+
+ // Create playlist file
+ if (App.UserSettings.CreatePlaylist && !_cancelDownloads) {
+ new PlaylistCreator(album).SavePlaylistToFile();
+ LogAdded(this, new LogArgs($"Saved playlist for album \"{album.Title}\"", LogType.IntermediateSuccess));
+ }
+
+ if (!_cancelDownloads) {
+ // Tasks have not been aborted
+ if (tracksDownloaded.All(x => x == true)) {
+ LogAdded(this, new LogArgs($"Successfully downloaded album \"{album.Title}\"", LogType.Success));
+ } else {
+ LogAdded(this, new LogArgs($"Finished downloading album \"{album.Title}\". Some tracks were not downloaded", LogType.Success));
+ }
+ }
+ }
+
+ ///
+ /// Downloads and tags a track. Returns true if the track has been correctly downloaded; false otherwise.
+ ///
+ /// The album of the track to download.
+ /// The track to download.
+ /// The cover art.
+ private async Task DownloadAndTagTrackAsync(Album album, Track track, TagLib.Picture artwork) {
+ LogAdded(this, new LogArgs($"Downloading track \"{track.Title}\" from url: {track.Mp3Url}", LogType.VerboseInfo));
+
+ int tries = 0;
+ Boolean trackDownloaded = false;
+ TrackFile currentFile = DownloadingFiles.Where(f => f.Url == track.Mp3Url).First();
+
+ if (File.Exists(track.Path)) {
+ long length = new FileInfo(track.Path).Length;
+ if (currentFile.Size > length - (currentFile.Size * App.UserSettings.AllowedFileSizeDifference) &&
+ currentFile.Size < length + (currentFile.Size * App.UserSettings.AllowedFileSizeDifference)) {
+ LogAdded(this, new LogArgs($"Track already exists within allowed file size range: track \"{Path.GetFileName(track.Path)}\" from album \"{album.Title}\" - Skipping download!", LogType.IntermediateSuccess));
+ return false;
+ }
+ }
+
+ do {
+ using (var webClient = new WebClient()) {
+ ProxyHelper.SetProxy(webClient);
+
+ // Update progress bar when downloading
+ webClient.DownloadProgressChanged += (s, e) => {
+ currentFile.BytesReceived = e.BytesReceived;
+ };
+
+ // Warn & tag when downloaded
+ webClient.DownloadFileCompleted += async (s, e) => {
+ if (!e.Cancelled && e.Error == null) {
+ trackDownloaded = true;
+
+ if (App.UserSettings.ModifyTags) {
+ // Tag (ID3) the file when downloaded
+ var tagFile = TagLib.File.Create(track.Path);
+ tagFile = TagHelper.UpdateArtist(tagFile, album.Artist, App.UserSettings.TagArtist);
+ tagFile = TagHelper.UpdateAlbumArtist(tagFile, album.Artist, App.UserSettings.TagAlbumArtist);
+ tagFile = TagHelper.UpdateAlbumTitle(tagFile, album.Title, App.UserSettings.TagAlbumTitle);
+ tagFile = TagHelper.UpdateAlbumYear(tagFile, (uint) album.ReleaseDate.Year, App.UserSettings.TagYear);
+ tagFile = TagHelper.UpdateTrackNumber(tagFile, (uint) track.Number, App.UserSettings.TagTrackNumber);
+ tagFile = TagHelper.UpdateTrackTitle(tagFile, track.Title, App.UserSettings.TagTrackTitle);
+ tagFile = TagHelper.UpdateTrackLyrics(tagFile, track.Lyrics, App.UserSettings.TagLyrics);
+ tagFile = TagHelper.UpdateComments(tagFile, App.UserSettings.TagComments);
+ tagFile.Save();
+ }
+
+ if (App.UserSettings.SaveCoverArtInTags && artwork != null) {
+ // Save cover in tags when downloaded
+ var tagFile = TagLib.File.Create(track.Path);
+ tagFile.Tag.Pictures = new TagLib.IPicture[1] { artwork };
+ tagFile.Save();
+ }
+
+ // Note the file as downloaded
+ currentFile.Downloaded = true;
+ LogAdded(this, new LogArgs($"Downloaded track \"{Path.GetFileName(track.Path)}\" from album \"{album.Title}\"", LogType.IntermediateSuccess));
+ } else if (!e.Cancelled && e.Error != null) {
+ if (tries + 1 < App.UserSettings.DownloadMaxTries) {
+ LogAdded(this, new LogArgs($"Unable to download track \"{Path.GetFileName(track.Path)}\" from album \"{album.Title}\". Try {tries + 1} of {App.UserSettings.DownloadMaxTries}", LogType.Warning));
+ } else {
+ LogAdded(this, new LogArgs($"Unable to download track \"{Path.GetFileName(track.Path)}\" from album \"{album.Title}\". Hit max retries of {App.UserSettings.DownloadMaxTries}", LogType.Error));
+ }
+ } // Else the download has been cancelled (by the user)
+
+ tries++;
+ if (!trackDownloaded && tries < App.UserSettings.DownloadMaxTries) {
+ await WaitForCooldownAsync(tries);
+ }
+ };
+
+ lock (_pendingDownloads) {
+ if (_cancelDownloads) {
+ // Abort
+ return false;
+ }
+ // Register current download
+ _pendingDownloads.Add(webClient);
+ }
+
+ // Start download
+ try {
+ await webClient.DownloadFileTaskAsync(track.Mp3Url, track.Path);
+ } catch (WebException ex) when (ex.Status == WebExceptionStatus.RequestCanceled) {
+ // Downloads cancelled by the user
+ // Do nothing
+ }
+
+ lock (_pendingDownloads) {
+ _pendingDownloads.Remove(webClient);
+ }
+ }
+ } while (!trackDownloaded && tries < App.UserSettings.DownloadMaxTries);
+
+ return trackDownloaded;
+ }
+
+ ///
+ /// Downloads and returns the cover art of the specified album.
+ /// Depending on UserSettings, save the cover art in the album folder.
+ ///
+ /// The album.
+ private async Task DownloadCoverArtAsync(Album album) {
+ TagLib.Picture artworkInTags = null;
+
+ int tries = 0;
+ Boolean artworkDownloaded = false;
+ TrackFile currentFile = DownloadingFiles.Where(f => f.Url == album.ArtworkUrl).First();
+
+ do {
+ using (var webClient = new WebClient()) {
+ ProxyHelper.SetProxy(webClient);
+
+ // Update progress bar when downloading
+ webClient.DownloadProgressChanged += (s, e) => {
+ currentFile.BytesReceived = e.BytesReceived;
+ };
+
+ // Warn when downloaded
+ webClient.DownloadFileCompleted += async (s, e) => {
+ if (!e.Cancelled && e.Error == null) {
+ artworkDownloaded = true;
+
+ // Convert/resize artwork to be saved in album folder
+ if (App.UserSettings.SaveCoverArtInFolder && (App.UserSettings.CoverArtInFolderConvertToJpg || App.UserSettings.CoverArtInFolderResize)) {
+ var settings = new ResizeSettings();
+ if (App.UserSettings.CoverArtInFolderConvertToJpg) {
+ settings.Format = "jpg";
+ settings.Quality = 90;
+ }
+ if (App.UserSettings.CoverArtInFolderResize) {
+ settings.MaxHeight = App.UserSettings.CoverArtInFolderMaxSize;
+ settings.MaxWidth = App.UserSettings.CoverArtInFolderMaxSize;
+ }
+
+ await Task.Run(() => {
+ ImageBuilder.Current.Build(album.ArtworkTempPath, album.ArtworkPath, settings); // Save it to the album folder
+ });
+ } else if (App.UserSettings.SaveCoverArtInFolder) {
+ File.Copy(album.ArtworkTempPath, album.ArtworkPath, true);
+ }
+
+ // Convert/resize artwork to be saved in tags
+ if (App.UserSettings.SaveCoverArtInTags && (App.UserSettings.CoverArtInTagsConvertToJpg || App.UserSettings.CoverArtInTagsResize)) {
+ var settings = new ResizeSettings();
+ if (App.UserSettings.CoverArtInTagsConvertToJpg) {
+ settings.Format = "jpg";
+ settings.Quality = 90;
+ }
+ if (App.UserSettings.CoverArtInTagsResize) {
+ settings.MaxHeight = App.UserSettings.CoverArtInTagsMaxSize;
+ settings.MaxWidth = App.UserSettings.CoverArtInTagsMaxSize;
+ }
+
+ await Task.Run(() => {
+ ImageBuilder.Current.Build(album.ArtworkTempPath, album.ArtworkTempPath, settings); // Save it to %Temp%
+ });
+ }
+ artworkInTags = new TagLib.Picture(album.ArtworkTempPath) {
+ Description = "Picture"
+ };
+
+ try {
+ File.Delete(album.ArtworkTempPath);
+ } catch {
+ // Could not delete the file. Nevermind, it's in %Temp% folder...
+ }
+
+ // Note the file as downloaded
+ currentFile.Downloaded = true;
+ LogAdded(this, new LogArgs($"Downloaded artwork for album \"{album.Title}\"", LogType.IntermediateSuccess));
+ } else if (!e.Cancelled && e.Error != null) {
+ if (tries < App.UserSettings.DownloadMaxTries) {
+ LogAdded(this, new LogArgs($"Unable to download artwork for album \"{album.Title}\". Try {tries + 1} of {App.UserSettings.DownloadMaxTries}", LogType.Warning));
+ } else {
+ LogAdded(this, new LogArgs($"Unable to download artwork for album \"{album.Title}\". Hit max retries of {App.UserSettings.DownloadMaxTries}", LogType.Error));
+ }
+ } // Else the download has been cancelled (by the user)
+
+ tries++;
+ if (!artworkDownloaded && tries < App.UserSettings.DownloadMaxTries) {
+ await WaitForCooldownAsync(tries);
+ }
+ };
+
+ lock (_pendingDownloads) {
+ if (_cancelDownloads) {
+ // Abort
+ return null;
+ }
+ // Register current download
+ _pendingDownloads.Add(webClient);
+ }
+
+ // Start download
+ try {
+ await webClient.DownloadFileTaskAsync(album.ArtworkUrl, album.ArtworkTempPath);
+ } catch (WebException ex) when (ex.Status == WebExceptionStatus.RequestCanceled) {
+ // Downloads cancelled by the user
+ // Do nothing
+ }
+
+ lock (_pendingDownloads) {
+ _pendingDownloads.Remove(webClient);
+ }
+ }
+ } while (!artworkDownloaded && tries < App.UserSettings.DownloadMaxTries);
+
+ return artworkInTags;
+ }
+
+ ///
+ /// Returns the albums located at the specified URLs.
+ ///
+ /// The URLs.
+ private async Task> GetAlbumsAsync(List urls) {
+ var albums = new List();
+
+ foreach (String url in urls) {
+ LogAdded(this, new LogArgs($"Retrieving album data for {url}", LogType.Info));
+
+ // Retrieve URL HTML source code
+ String htmlCode = "";
+ using (var webClient = new WebClient() { Encoding = Encoding.UTF8 }) {
+ ProxyHelper.SetProxy(webClient);
+
+ if (_cancelDownloads) {
+ // Abort
+ return new List();
+ }
+
+ try {
+ htmlCode = await webClient.DownloadStringTaskAsync(url);
+ } catch {
+ LogAdded(this, new LogArgs($"Could not retrieve data for {url}", LogType.Error));
+ continue;
+ }
+ }
+
+ // Get info on album
+ try {
+ albums.Add(BandcampHelper.GetAlbum(htmlCode));
+ } catch {
+ LogAdded(this, new LogArgs($"Could not retrieve album info for {url}", LogType.Error));
+ continue;
+ }
+ }
+
+ return albums;
+ }
+
+ ///
+ /// Returns the artists discography from any URL (artist, album, track).
+ ///
+ /// The URLs.
+ private async Task> GetArtistDiscographyAsync(List urls) {
+ var albumsUrls = new List();
+
+ foreach (String url in urls) {
+ LogAdded(this, new LogArgs($"Retrieving artist discography from {url}", LogType.Info));
+
+ // Retrieve URL HTML source code
+ String htmlCode = "";
+ using (var webClient = new WebClient() { Encoding = Encoding.UTF8 }) {
+ ProxyHelper.SetProxy(webClient);
+
+ if (_cancelDownloads) {
+ // Abort
+ return new List();
+ }
+
+ try {
+ htmlCode = await webClient.DownloadStringTaskAsync(url);
+ } catch {
+ LogAdded(this, new LogArgs($"Could not retrieve data for {url}", LogType.Error));
+ continue;
+ }
+ }
+
+ // Get artist "music" bandcamp page (http://artist.bandcamp.com/music)
+ var regex = new Regex("band_url = \"(?.*)\"");
+ if (!regex.IsMatch(htmlCode)) {
+ LogAdded(this, new LogArgs($"No discography could be found on {url}. Try to uncheck the \"Download artist discography\" option", LogType.Error));
+ continue;
+ }
+ String artistMusicPage = regex.Match(htmlCode).Groups["url"].Value + "/music";
+
+ // Retrieve artist "music" page HTML source code
+ using (var webClient = new WebClient() { Encoding = Encoding.UTF8 }) {
+ ProxyHelper.SetProxy(webClient);
+
+ if (_cancelDownloads) {
+ // Abort
+ return new List();
+ }
+
+ try {
+ htmlCode = await webClient.DownloadStringTaskAsync(artistMusicPage);
+ } catch {
+ LogAdded(this, new LogArgs($"Could not retrieve data for {artistMusicPage}", LogType.Error));
+ continue;
+ }
+ }
+
+ // Get albums referred on the page
+ regex = new Regex("TralbumData.*\n.*url:.*'/music'\n");
+ if (!regex.IsMatch(htmlCode)) {
+ // This seem to be a one-album artist with no "music" page => URL redirects to the unique album URL
+ albumsUrls.Add(url);
+ } else {
+ // We are on a real "music" page
+ try {
+ albumsUrls.AddRange(BandcampHelper.GetAlbumsUrl(htmlCode));
+ } catch (NoAlbumFoundException) {
+ LogAdded(this, new LogArgs($"No referred album could be found on {artistMusicPage}. Try to uncheck the \"Download artist discography\" option", LogType.Error));
+ continue;
+ }
+ }
+ }
+
+ return albumsUrls.Distinct().ToList();
+ }
+
+ ///
+ /// Returns the size of the file located at the specified URL.
+ ///
+ /// The URL.
+ /// The title of the file to be displayed in the log.
+ /// The type of the file.
+ private async Task GetFileSizeAsync(String url, String titleForLog, FileType fileType) {
+ long size = 0;
+ Boolean sizeRetrieved;
+ int tries = 0;
+ String fileTypeForLog;
+ String protocolMethod;
+
+ switch (fileType) {
+ case FileType.Artwork:
+ fileTypeForLog = "cover art file for album";
+ protocolMethod = "HEAD";
+ break;
+ case FileType.Track:
+ fileTypeForLog = "MP3 file for the track";
+ // Using the HEAD method on tracks urls does not work (Error 405: Method not allowed)
+ // Surprisingly, using the GET method does not seem to download the whole file, so we will use it to retrieve the mp3 sizes
+ protocolMethod = "GET";
+ break;
+ default:
+ throw new NotImplementedException();
+ }
+
+ do {
+ if (_cancelDownloads) {
+ // Abort
+ return 0;
+ }
+
+ try {
+ size = await FileHelper.GetFileSizeAsync(url, protocolMethod);
+ sizeRetrieved = true;
+ LogAdded(this, new LogArgs($"Retrieved the size of the {fileTypeForLog} \"{titleForLog}\"", LogType.VerboseInfo));
+ } catch {
+ sizeRetrieved = false;
+ if (tries + 1 < App.UserSettings.DownloadMaxTries) {
+ LogAdded(this, new LogArgs($"Failed to retrieve the size of the {fileTypeForLog} \"{titleForLog}\". Try {tries + 1} of {App.UserSettings.DownloadMaxTries}", LogType.Warning));
+ } else {
+ LogAdded(this, new LogArgs($"Failed to retrieve the size of the {fileTypeForLog} \"{titleForLog}\". Hit max retries of {App.UserSettings.DownloadMaxTries}. Progress update may be wrong.", LogType.Error));
+ }
+ }
+
+ tries++;
+ if (!sizeRetrieved && tries < App.UserSettings.DownloadMaxTries) {
+ await WaitForCooldownAsync(tries);
+ }
+ } while (!sizeRetrieved && tries < App.UserSettings.DownloadMaxTries);
+
+ return size;
+ }
+
+ ///
+ /// Returns the files to download from a list of albums.
+ ///
+ /// The albums.
+ private async Task> GetFilesToDownloadAsync(List albums) {
+ var files = new List();
+ foreach (Album album in albums) {
+ LogAdded(this, new LogArgs($"Computing size for album \"{album.Title}\"...", LogType.Info));
+
+ // Artwork
+ if ((App.UserSettings.SaveCoverArtInTags || App.UserSettings.SaveCoverArtInFolder) && album.HasArtwork) {
+ if (App.UserSettings.RetrieveFilesSize) {
+ long size = await GetFileSizeAsync(album.ArtworkUrl, album.Title, FileType.Artwork);
+ files.Add(new TrackFile(album.ArtworkUrl, 0, size));
+ } else {
+ files.Add(new TrackFile(album.ArtworkUrl, 0, 0));
+ }
+ }
+
+ // Tracks
+ if (App.UserSettings.RetrieveFilesSize) {
+ Int32[] tracksIndexes = Enumerable.Range(0, album.Tracks.Count).ToArray();
+ await Task.WhenAll(tracksIndexes.Select(async i => {
+ long size = await GetFileSizeAsync(album.Tracks[i].Mp3Url, album.Tracks[i].Title, FileType.Track);
+ files.Add(new TrackFile(album.Tracks[i].Mp3Url, 0, size));
+ }));
+ } else {
+ foreach (Track track in album.Tracks) {
+ files.Add(new TrackFile(track.Mp3Url, 0, 0));
+ }
+ }
+ }
+
+ return files;
+ }
+
+ ///
+ /// Waits for a "cooldown" time, computed from the specified number of download tries.
+ ///
+ /// The times count we tried to download the same file.
+ ///
+ private async Task WaitForCooldownAsync(int triesNumber) {
+ if (App.UserSettings.DownloadRetryCooldown != 0) {
+ await Task.Delay((int) (Math.Pow(App.UserSettings.DownloadRetryExponent, triesNumber) * App.UserSettings.DownloadRetryCooldown * 1000));
+ }
+ }
+
+ public event LogAddedEventHandler LogAdded;
+ }
+}
\ No newline at end of file
diff --git a/src/BandcampDownloader/Core/LogArgs.cs b/src/BandcampDownloader/Core/LogArgs.cs
new file mode 100644
index 00000000..e4e4955c
--- /dev/null
+++ b/src/BandcampDownloader/Core/LogArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace BandcampDownloader {
+
+ internal class LogArgs: EventArgs {
+ public LogType LogType { get; private set; }
+ public String Message { get; private set; }
+
+ public LogArgs(String message, LogType logType) {
+ Message = message;
+ LogType = logType;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BandcampDownloader/Core/PlaylistCreator.cs b/src/BandcampDownloader/Core/PlaylistCreator.cs
new file mode 100644
index 00000000..771745df
--- /dev/null
+++ b/src/BandcampDownloader/Core/PlaylistCreator.cs
@@ -0,0 +1,131 @@
+using System;
+using System.IO;
+using System.Text;
+using PlaylistsNET.Content;
+using PlaylistsNET.Models;
+
+namespace BandcampDownloader {
+
+ internal class PlaylistCreator {
+ ///
+ /// The album.
+ ///
+ private readonly Album _album;
+
+ ///
+ /// Initializes a new instance of PlaylistCreator.
+ ///
+ ///
+ public PlaylistCreator(Album album) {
+ _album = album;
+ }
+
+ ///
+ /// Saves the playlist to a file.
+ ///
+ public void SavePlaylistToFile() {
+ String fileContent;
+
+ switch (App.UserSettings.PlaylistFormat) {
+ case PlaylistFormat.m3u:
+ fileContent = CreateM3uPlaylist();
+ break;
+ case PlaylistFormat.pls:
+ fileContent = CreatePlsPlaylist();
+ break;
+ case PlaylistFormat.wpl:
+ fileContent = CreateWplPlaylist();
+ break;
+ case PlaylistFormat.zpl:
+ fileContent = CreateZplPlaylist();
+ break;
+ default:
+ throw new NotImplementedException();
+ }
+
+ File.WriteAllText(_album.PlaylistPath, fileContent, Encoding.UTF8);
+ }
+
+ ///
+ /// Returns the playlist in m3u format.
+ ///
+ private String CreateM3uPlaylist() {
+ var playlist = new M3uPlaylist() {
+ IsExtended = App.UserSettings.M3uExtended,
+ };
+
+ foreach (Track track in _album.Tracks) {
+ playlist.PlaylistEntries.Add(new M3uPlaylistEntry() {
+ Album = _album.Title,
+ AlbumArtist = _album.Artist,
+ Duration = TimeSpan.FromSeconds(track.Duration),
+ Path = Path.GetFileName(track.Path),
+ Title = track.Title,
+ });
+ }
+
+ return new M3uContent().ToText(playlist);
+ }
+
+ ///
+ /// Returns the playlist in pls format.
+ ///
+ private String CreatePlsPlaylist() {
+ var playlist = new PlsPlaylist();
+
+ foreach (Track track in _album.Tracks) {
+ playlist.PlaylistEntries.Add(new PlsPlaylistEntry() {
+ Length = TimeSpan.FromSeconds(track.Duration),
+ Path = Path.GetFileName(track.Path),
+ Title = track.Title,
+ });
+ }
+
+ return new PlsContent().ToText(playlist);
+ }
+
+ ///
+ /// Returns the playlist in wpl format.
+ ///
+ private String CreateWplPlaylist() {
+ var playlist = new WplPlaylist() {
+ Title = _album.Title,
+ };
+
+ foreach (Track track in _album.Tracks) {
+ playlist.PlaylistEntries.Add(new WplPlaylistEntry() {
+ AlbumArtist = _album.Artist,
+ AlbumTitle = _album.Title,
+ Duration = TimeSpan.FromSeconds(track.Duration),
+ Path = Path.GetFileName(track.Path),
+ TrackArtist = _album.Artist,
+ TrackTitle = track.Title,
+ });
+ }
+
+ return new WplContent().ToText(playlist);
+ }
+
+ ///
+ /// Returns the playlist in zpl format.
+ ///
+ private String CreateZplPlaylist() {
+ var playlist = new ZplPlaylist() {
+ Title = _album.Title,
+ };
+
+ foreach (Track track in _album.Tracks) {
+ playlist.PlaylistEntries.Add(new ZplPlaylistEntry() {
+ AlbumArtist = _album.Artist,
+ AlbumTitle = _album.Title,
+ Duration = TimeSpan.FromSeconds(track.Duration),
+ Path = Path.GetFileName(track.Path),
+ TrackArtist = _album.Artist,
+ TrackTitle = track.Title,
+ });
+ }
+
+ return new ZplContent().ToText(playlist);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BandcampDownloader/Helpers/FileHelper.cs b/src/BandcampDownloader/Helpers/FileHelper.cs
index d0c3fcde..03479d4e 100644
--- a/src/BandcampDownloader/Helpers/FileHelper.cs
+++ b/src/BandcampDownloader/Helpers/FileHelper.cs
@@ -1,5 +1,6 @@
using System;
using System.Net;
+using System.Threading.Tasks;
namespace BandcampDownloader {
@@ -12,12 +13,12 @@ internal static class FileHelper {
/// The protocol method to use in order to retrieve the file
/// size.
/// The size of the file located at the provided URL.
- public static long GetFileSize(String url, String protocolMethod) {
+ public static async Task GetFileSizeAsync(String url, String protocolMethod) {
WebRequest webRequest = HttpWebRequest.Create(url);
webRequest.Method = protocolMethod;
long fileSize;
try {
- using (WebResponse webResponse = webRequest.GetResponse()) {
+ using (WebResponse webResponse = await webRequest.GetResponseAsync()) {
fileSize = webResponse.ContentLength;
}
} catch (Exception e) {
@@ -26,4 +27,4 @@ public static long GetFileSize(String url, String protocolMethod) {
return fileSize;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/BandcampDownloader/Helpers/PlaylistHelper.cs b/src/BandcampDownloader/Helpers/PlaylistHelper.cs
deleted file mode 100644
index 84614abc..00000000
--- a/src/BandcampDownloader/Helpers/PlaylistHelper.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-using System;
-using System.IO;
-using PlaylistsNET.Content;
-using PlaylistsNET.Models;
-
-namespace BandcampDownloader {
-
- internal static class PlaylistHelper {
-
- ///
- /// Saves the playlist file for the specified album in the specified folder.
- ///
- /// The album relative of the playlist.
- /// The folder where the playlist should be stored.
- public static void SavePlaylistForAlbum(Album album, String folderPath) {
- String fileContent;
-
- switch (App.UserSettings.PlaylistFormat) {
- case PlaylistFormat.m3u:
- fileContent = CreateM3uPlaylist(album);
- break;
- case PlaylistFormat.pls:
- fileContent = CreatePlsPlaylist(album);
- break;
- case PlaylistFormat.wpl:
- fileContent = CreateWplPlaylist(album);
- break;
- case PlaylistFormat.zpl:
- fileContent = CreateZplPlaylist(album);
- break;
- default:
- throw new NotImplementedException();
- }
-
- File.WriteAllText(ComputeFilePath(album, folderPath), fileContent);
- }
-
- ///
- /// Returns the full path where the playlist file should be saved.
- ///
- /// The album relative of the playlist.
- /// The folder where the playlist should be stored.
- private static String ComputeFilePath(Album album, String folderPath) {
- String fileExt = GetFileExtension();
-
- // Compute paths where to save artwork
- String filePath = folderPath + "\\" + ParseFileName(album) + fileExt;
-
- if (filePath.Length >= 260) {
- // Windows doesn't do well with path + filename >= 260 characters (and path >= 248 characters)
- // Path has been shorten to 247 characters before, so we have 12 characters max left for filename.ext
- int fileNameMaxLength = 12 - fileExt.Length;
- filePath = folderPath + "\\" + ParseFileName(album).Substring(0, fileNameMaxLength) + fileExt;
- }
-
- return filePath;
- }
-
- ///
- /// Returns the playlist in m3u format for the specified album.
- ///
- /// The album relative of the playlist.
- private static String CreateM3uPlaylist(Album album) {
- var playlist = new M3uPlaylist() {
- IsExtended = App.UserSettings.M3uExtended,
- };
-
- foreach (Track track in album.Tracks) {
- playlist.PlaylistEntries.Add(new M3uPlaylistEntry() {
- Album = album.Title,
- AlbumArtist = album.Artist,
- Duration = TimeSpan.FromSeconds(track.Duration),
- Path = Path.GetFileName(track.Path),
- Title = track.Title,
- });
- }
-
- return new M3uContent().ToText(playlist);
- }
-
- ///
- /// Returns the playlist in pls format for the specified album.
- ///
- /// The album relative of the playlist.
- private static String CreatePlsPlaylist(Album album) {
- var playlist = new PlsPlaylist();
-
- foreach (Track track in album.Tracks) {
- playlist.PlaylistEntries.Add(new PlsPlaylistEntry() {
- Length = TimeSpan.FromSeconds(track.Duration),
- Path = Path.GetFileName(track.Path),
- Title = track.Title,
- });
- }
-
- return new PlsContent().ToText(playlist);
- }
-
- ///
- /// Returns the playlist in wpl format for the specified album.
- ///
- /// The album relative of the playlist.
- private static String CreateWplPlaylist(Album album) {
- var playlist = new WplPlaylist() {
- Title = album.Title,
- };
-
- foreach (Track track in album.Tracks) {
- playlist.PlaylistEntries.Add(new WplPlaylistEntry() {
- AlbumArtist = album.Artist,
- AlbumTitle = album.Title,
- Duration = TimeSpan.FromSeconds(track.Duration),
- Path = Path.GetFileName(track.Path),
- TrackArtist = album.Artist,
- TrackTitle = track.Title,
- });
- }
-
- return new WplContent().ToText(playlist);
- }
-
- ///
- /// Returns the playlist in zpl format for the specified album.
- ///
- /// The album relative of the playlist.
- private static String CreateZplPlaylist(Album album) {
- var playlist = new ZplPlaylist() {
- Title = album.Title,
- };
-
- foreach (Track track in album.Tracks) {
- playlist.PlaylistEntries.Add(new ZplPlaylistEntry() {
- AlbumArtist = album.Artist,
- AlbumTitle = album.Title,
- Duration = TimeSpan.FromSeconds(track.Duration),
- Path = Path.GetFileName(track.Path),
- TrackArtist = album.Artist,
- TrackTitle = track.Title,
- });
- }
-
- return new ZplContent().ToText(playlist);
- }
-
- ///
- /// Returns the file extension to be used for the playlist, depending of the type of playlist defined in UserSettings.
- ///
- private static String GetFileExtension() {
- switch (App.UserSettings.PlaylistFormat) {
- case PlaylistFormat.m3u:
- return ".m3u";
- case PlaylistFormat.pls:
- return ".pls";
- case PlaylistFormat.wpl:
- return ".wpl";
- case PlaylistFormat.zpl:
- return ".zpl";
- default:
- throw new NotImplementedException();
- }
- }
-
- ///
- /// Returns the file name to be used for the playlist file of the specified album from the file name format saved
- /// in the UserSettings, by replacing the placeholders strings with their corresponding values.
- ///
- /// The album relative of the playlist.
- private static String ParseFileName(Album album) {
- String fileName = App.UserSettings.PlaylistFileNameFormat
- .Replace("{year}", album.ReleaseDate.Year.ToString())
- .Replace("{month}", album.ReleaseDate.Month.ToString("00"))
- .Replace("{day}", album.ReleaseDate.Day.ToString("00"))
- .Replace("{album}", album.Title)
- .Replace("{artist}", album.Artist);
- return fileName.ToAllowedFileName();
- }
- }
-}
\ No newline at end of file
diff --git a/src/BandcampDownloader/Helpers/ProxyHelper.cs b/src/BandcampDownloader/Helpers/ProxyHelper.cs
new file mode 100644
index 00000000..26f96c8f
--- /dev/null
+++ b/src/BandcampDownloader/Helpers/ProxyHelper.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Net;
+
+namespace BandcampDownloader {
+
+ internal class ProxyHelper {
+
+ ///
+ /// Sets the proxy of the specified WebClient according to the UserSettings.
+ ///
+ /// The WebClient to modify.
+ public static void SetProxy(WebClient webClient) {
+ switch (App.UserSettings.Proxy) {
+ case ProxyType.None:
+ webClient.Proxy = null;
+ break;
+ case ProxyType.System:
+ if (webClient.Proxy != null) {
+ webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
+ }
+ break;
+ case ProxyType.Manual:
+ webClient.Proxy = new WebProxy(App.UserSettings.ProxyHttpAddress, App.UserSettings.ProxyHttpPort);
+ break;
+ default:
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BandcampDownloader/Model/Album.cs b/src/BandcampDownloader/Model/Album.cs
index 76d86edf..faa57ecb 100644
--- a/src/BandcampDownloader/Model/Album.cs
+++ b/src/BandcampDownloader/Model/Album.cs
@@ -39,6 +39,11 @@ public Boolean HasArtwork {
///
public String Path { get; private set; }
+ ///
+ /// The local path (full path with file name) where the playlist file should be saved.
+ ///
+ public String PlaylistPath { get; private set; }
+
///
/// The release date of the album.
///
@@ -63,10 +68,30 @@ public Album(String artist, String artworkUrl, DateTime releaseDate, String titl
ReleaseDate = releaseDate;
Title = title;
// Must be done after other properties are filled!
- Path = ParseFolderPath(App.UserSettings.DownloadsPath);
+ Path = ParseFolderPath();
+ // Must be done after Path is set!
+ PlaylistPath = ParsePlaylistPath();
SetArtworkPaths();
}
+ ///
+ /// Returns the file extension to be used for the playlist, depending of the type of playlist defined in UserSettings.
+ ///
+ private static String GetPlaylistFileExtension() {
+ switch (App.UserSettings.PlaylistFormat) {
+ case PlaylistFormat.m3u:
+ return ".m3u";
+ case PlaylistFormat.pls:
+ return ".pls";
+ case PlaylistFormat.wpl:
+ return ".wpl";
+ case PlaylistFormat.zpl:
+ return ".zpl";
+ default:
+ throw new NotImplementedException();
+ }
+ }
+
///
/// Returns the file name to be used for the cover art of the specified album from the file name format saved in
/// the UserSettings, by replacing the placeholders strings with their corresponding values.
@@ -86,20 +111,55 @@ private String ParseCoverArtFileName() {
/// Returns the folder path from the specified path format, by replacing the placeholders strings with their
/// corresponding values. If the path is too long (> 247 characters), it will be stripped.
///
- /// The download path to parse.
- private String ParseFolderPath(String downloadPath) {
- downloadPath = downloadPath.Replace("{year}", ReleaseDate.Year.ToString().ToAllowedFileName());
- downloadPath = downloadPath.Replace("{month}", ReleaseDate.Month.ToString("00").ToAllowedFileName());
- downloadPath = downloadPath.Replace("{day}", ReleaseDate.Day.ToString("00").ToAllowedFileName());
- downloadPath = downloadPath.Replace("{artist}", Artist.ToAllowedFileName());
- downloadPath = downloadPath.Replace("{album}", Title.ToAllowedFileName());
-
- if (downloadPath.Length >= 248) {
+ private String ParseFolderPath() {
+ String path = App.UserSettings.DownloadsPath;
+ path = path.Replace("{year}", ReleaseDate.Year.ToString().ToAllowedFileName());
+ path = path.Replace("{month}", ReleaseDate.Month.ToString("00").ToAllowedFileName());
+ path = path.Replace("{day}", ReleaseDate.Day.ToString("00").ToAllowedFileName());
+ path = path.Replace("{artist}", Artist.ToAllowedFileName());
+ path = path.Replace("{album}", Title.ToAllowedFileName());
+
+ if (path.Length >= 248) {
// Windows doesn't do well with path >= 248 characters (and path + filename >= 260 characters)
- downloadPath = downloadPath.Substring(0, 247);
+ path = path.Substring(0, 247);
+ }
+
+ return path;
+ }
+
+ ///
+ /// Returns the file name to be used for the playlist file of the specified album from the file name format saved
+ /// in the UserSettings, by replacing the placeholders strings with their corresponding values.
+ ///
+ private String ParsePlaylistFileName() {
+ String fileName = App.UserSettings.PlaylistFileNameFormat
+ .Replace("{year}", ReleaseDate.Year.ToString())
+ .Replace("{month}", ReleaseDate.Month.ToString("00"))
+ .Replace("{day}", ReleaseDate.Day.ToString("00"))
+ .Replace("{album}", Title)
+ .Replace("{artist}", Artist);
+ return fileName.ToAllowedFileName();
+ }
+
+ ///
+ /// Returns the path to be used for the playlist file from the file name format saved in the UserSettings, by
+ /// replacing the placeholders strings with their corresponding values. If the path is too long (> 259
+ /// characters), it will be stripped.
+ ///
+ private String ParsePlaylistPath() {
+ String fileExt = GetPlaylistFileExtension();
+
+ // Compute paths where to save artwork
+ String filePath = Path + "\\" + ParsePlaylistFileName() + fileExt;
+
+ if (filePath.Length >= 260) {
+ // Windows doesn't do well with path + filename >= 260 characters (and path >= 248 characters)
+ // Path has been shorten to 247 characters before, so we have 12 characters max left for "\filename.ext", so 11 character max for "filename.ext"
+ int fileNameMaxLength = 11 - fileExt.Length;
+ filePath = Path + "\\" + ParsePlaylistFileName().Substring(0, fileNameMaxLength) + fileExt;
}
- return downloadPath;
+ return filePath;
}
///
@@ -118,10 +178,10 @@ private void SetArtworkPaths() {
if (ArtworkTempPath.Length >= 260 || ArtworkPath.Length >= 260) {
// Windows doesn't do well with path + filename >= 260 characters (and path >= 248 characters)
- // Path has been shorten to 247 characters before, so we have 12 characters max left for filename.ext
+ // Path has been shorten to 247 characters before, so we have 12 characters max left for "\filename.ext", so 11 character max for "filename.ext"
// There may be only one path needed to shorten, but it's better to use the same file name in both places
- int fileNameInTempMaxLength = 12 - randomNumber.Length - artworkFileExt.Length;
- int fileNameInFolderMaxLength = 12 - artworkFileExt.Length;
+ int fileNameInTempMaxLength = 11 - randomNumber.Length - artworkFileExt.Length;
+ int fileNameInFolderMaxLength = 11 - artworkFileExt.Length;
ArtworkTempPath = System.IO.Path.GetTempPath() + "\\" + ParseCoverArtFileName().Substring(0, fileNameInTempMaxLength) + randomNumber + artworkFileExt;
ArtworkPath = Path + "\\" + ParseCoverArtFileName().Substring(0, fileNameInFolderMaxLength) + artworkFileExt;
}
diff --git a/src/BandcampDownloader/Model/DownloadProgress.cs b/src/BandcampDownloader/Model/DownloadProgress.cs
deleted file mode 100644
index b990a221..00000000
--- a/src/BandcampDownloader/Model/DownloadProgress.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-
-namespace BandcampDownloader {
-
- internal class DownloadProgress {
- public long BytesReceived { get; set; }
- public String FileUrl { get; set; }
-
- public DownloadProgress(String fileUrl, long bytesReceived) {
- FileUrl = fileUrl;
- BytesReceived = bytesReceived;
- }
- }
-}
\ No newline at end of file
diff --git a/src/BandcampDownloader/Model/FileType.cs b/src/BandcampDownloader/Model/FileType.cs
new file mode 100644
index 00000000..a5a708f0
--- /dev/null
+++ b/src/BandcampDownloader/Model/FileType.cs
@@ -0,0 +1,7 @@
+namespace BandcampDownloader {
+
+ internal enum FileType {
+ Artwork,
+ Track
+ }
+}
\ No newline at end of file
diff --git a/src/BandcampDownloader/Model/Track.cs b/src/BandcampDownloader/Model/Track.cs
index c5c68689..8ec9fee7 100644
--- a/src/BandcampDownloader/Model/Track.cs
+++ b/src/BandcampDownloader/Model/Track.cs
@@ -79,8 +79,8 @@ private String ParseTrackFilePath() {
String path = Album.Path + "\\" + fileName;
if (path.Length >= 260) {
// Windows doesn't do well with path + filename >= 260 characters (and path >= 248 characters)
- // album.Path has been shorten to 247 characters before, so we have 12 characters max left for filename.ext
- int fileNameMaxLength = 12 - System.IO.Path.GetExtension(path).Length;
+ // album.Path has been shorten to 247 characters before, so we have 12 characters max left for "\filename.ext", so 11 character max for "filename.ext"
+ int fileNameMaxLength = 11 - System.IO.Path.GetExtension(path).Length;
path = Album.Path + "\\" + fileName.Substring(0, fileNameMaxLength) + System.IO.Path.GetExtension(path);
}
diff --git a/src/BandcampDownloader/Properties/AssemblyInfo.cs b/src/BandcampDownloader/Properties/AssemblyInfo.cs
index f47b2df1..8c91246c 100644
--- a/src/BandcampDownloader/Properties/AssemblyInfo.cs
+++ b/src/BandcampDownloader/Properties/AssemblyInfo.cs
@@ -51,6 +51,6 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.2.8.2")]
-[assembly: AssemblyFileVersion("0.2.8.2")]
+[assembly: AssemblyVersion("0.2.9.0")]
+[assembly: AssemblyFileVersion("0.2.9.0")]
[assembly: GuidAttribute("8C171C7F-9BAC-4EC0-A287-59908B48953F")]
diff --git a/src/BandcampDownloader/Properties/Resources.Designer.cs b/src/BandcampDownloader/Properties/Resources.Designer.cs
index 8e761dc0..45084ba2 100644
--- a/src/BandcampDownloader/Properties/Resources.Designer.cs
+++ b/src/BandcampDownloader/Properties/Resources.Designer.cs
@@ -106,6 +106,15 @@ internal static string buttonCheckForUpdates {
}
}
+ ///
+ /// Looks up a localized string similar to _Download update.
+ ///
+ internal static string buttonDownloadUpdate {
+ get {
+ return ResourceManager.GetString("buttonDownloadUpdate", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to _Settings.
///
@@ -160,6 +169,15 @@ internal static string buttonStop {
}
}
+ ///
+ /// Looks up a localized string similar to Could not download changelog from {0}.
+ ///
+ internal static string changelogDownloadError {
+ get {
+ return ResourceManager.GetString("changelogDownloadError", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Check for _updates at startup.
///
@@ -538,6 +556,15 @@ internal static string labelButtonResetSettings {
}
}
+ ///
+ /// Looks up a localized string similar to Changelog.
+ ///
+ internal static string labelChangelogTitle {
+ get {
+ return ResourceManager.GetString("labelChangelogTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Comm_ents.
///
@@ -574,6 +601,15 @@ internal static string labelCoverArtInTagsMaxSize {
}
}
+ ///
+ /// Looks up a localized string similar to Current version:.
+ ///
+ internal static string labelCurrentVersion {
+ get {
+ return ResourceManager.GetString("labelCurrentVersion", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Download _max tries.
///
@@ -709,15 +745,6 @@ internal static string labelVerboseLogInfo {
}
}
- ///
- /// Looks up a localized string similar to Click to go to official project website:.
- ///
- internal static string labelVersion_ToolTip_Part {
- get {
- return ResourceManager.GetString("labelVersion_ToolTip_Part", resourceCulture);
- }
- }
-
///
/// Looks up a localized string similar to Could not check for updates.
///
@@ -728,7 +755,7 @@ internal static string labelVersionError {
}
///
- /// Looks up a localized string similar to A new version ({0}) is available.
+ /// Looks up a localized string similar to A new version is available.
///
internal static string labelVersionNewUpdateAvailable {
get {
@@ -974,6 +1001,15 @@ internal static string TagRemoveAction_Empty {
}
}
+ ///
+ /// Looks up a localized string similar to Go to website.
+ ///
+ internal static string textBlockGoToWebsite {
+ get {
+ return ResourceManager.GetString("textBlockGoToWebsite", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Help translate.
///
@@ -1108,5 +1144,14 @@ internal static string windowSettings_Title {
return ResourceManager.GetString("windowSettings_Title", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Update.
+ ///
+ internal static string windowUpdate_Title {
+ get {
+ return ResourceManager.GetString("windowUpdate_Title", resourceCulture);
+ }
+ }
}
}
diff --git a/src/BandcampDownloader/Properties/Resources.de.resx b/src/BandcampDownloader/Properties/Resources.de.resx
index 040d35be..7133696e 100644
--- a/src/BandcampDownloader/Properties/Resources.de.resx
+++ b/src/BandcampDownloader/Properties/Resources.de.resx
@@ -117,7 +117,6 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
...
@@ -130,6 +129,9 @@
_Jetzt suchen
+
+ _Download update
+
_Einstellungen
@@ -148,6 +150,9 @@
_Abbrechen
+
+ Could not download changelog from {0}
+
Beim start auf _updates prüfen
@@ -197,7 +202,7 @@
Wenn ausgewählt wird ein Benachrichtigungston nach erfolgreichem Download abgespielt.
- Use _extended format (for M3U only)
+ Use _extended format (for M3U only)
If checked, the extended format (directives prefaced by the # character) will be used when creating M3U playlists.
@@ -264,6 +269,9 @@ Das Herunterladen geht schneller wenn diese Option ausgeschaltet ist.
_Zurücksetzen
+
+ Changelog
+
Komm_entare
@@ -276,6 +284,9 @@ Das Herunterladen geht schneller wenn diese Option ausgeschaltet ist.
Maximale Größe (p_x)
+
+ Current version:
+
_Maximale Downloadversuche
@@ -321,14 +332,11 @@ Das Herunterladen geht schneller wenn diese Option ausgeschaltet ist.
(Die Logdatei ist immer ausführlich)
-
- Klicken um zur Homepage diese Projektes zu gelangen:
-
Konnte nicht auf Updates prüfen
- Eine neue Version ({0}) ist verfügbar
+ Eine neue Version ist verfügbar
Album _Veröffentlichungsjahr
@@ -466,6 +474,9 @@ Empfohlender Wert = 4
- {album} Wird mit dem albumnamen ersetzt
- {year}, {month} und {day} werden mit dem Erscheinungsdatum ersetzt
+
+ Go to website
+
You can use placeholders to customize the file name:
- {artist} will be replaced by the album artist
@@ -482,4 +493,7 @@ seine alben herunterzuladen.
Einstellungen
+
+ Update
+
\ No newline at end of file
diff --git a/src/BandcampDownloader/Properties/Resources.fr.resx b/src/BandcampDownloader/Properties/Resources.fr.resx
index be31ad32..c9d1a08b 100644
--- a/src/BandcampDownloader/Properties/Resources.fr.resx
+++ b/src/BandcampDownloader/Properties/Resources.fr.resx
@@ -117,7 +117,6 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
...
@@ -130,6 +129,9 @@
Vérifier _maintenant
+
+ _Télécharger la mise à jour
+
_Paramètres
@@ -148,6 +150,9 @@
_Annuler
+
+ Impossible de télécharger les notes de version depuis {0}
+
Vérifier les mises à jour au _démarrage
@@ -264,6 +269,9 @@ Décocher cette option permet de gagner du temps.
_Réinitialiser
+
+ Notes de version
+
C_ommentaires
@@ -276,6 +284,9 @@ Décocher cette option permet de gagner du temps.
Taille maximale (p_x)
+
+ Version actuelle :
+
Nombre de téléchargements _maximum
@@ -321,14 +332,11 @@ Décocher cette option permet de gagner du temps.
(le fichier log est toujours verbeux)
-
- Cliquer pour se rendre sur le site web du projet :
-
Impossible de vérifier les mises à jour
- Une nouvelle version ({0}) est disponible
+ Une nouvelle version est disponible
Année _de parution de l'album
@@ -466,6 +474,9 @@ Valeur conseillée = 4
- {album} sera remplacé par le nom de l'album
- {year} (année), {month} (mois) et {day} (jour) seront remplacés par la date de parution de l'album
+
+ Site web
+
Vous pouvez utiliser des paramètres de substitution pour personnaliser le nom des fichiers :
- {artist} sera remplacé par l'interprète de l'album
@@ -482,4 +493,7 @@ de leurs albums.
Paramètres
+
+ Mise à jour
+
\ No newline at end of file
diff --git a/src/BandcampDownloader/Properties/Resources.it.resx b/src/BandcampDownloader/Properties/Resources.it.resx
index a615fc3c..585d54af 100644
--- a/src/BandcampDownloader/Properties/Resources.it.resx
+++ b/src/BandcampDownloader/Properties/Resources.it.resx
@@ -117,7 +117,6 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
...
@@ -130,6 +129,9 @@
Controlla _ora
+
+ _Download update
+
_Impostazioni
@@ -148,6 +150,9 @@
_Annulla
+
+ Could not download changelog from {0}
+
_Cerca aggiornamenti all'avvio
@@ -264,6 +269,9 @@ Togli la spunta da quest'impostazione per risparmiare tempo.
_Ripristina impostazioni
+
+ Changelog
+
Comm_enti
@@ -276,6 +284,9 @@ Togli la spunta da quest'impostazione per risparmiare tempo.
Dimensione massima (p_x)
+
+ Current version:
+
_Massimo numero di tentativi
@@ -321,14 +332,11 @@ Togli la spunta da quest'impostazione per risparmiare tempo.
(il file registro è sempre esteso)
-
- Fai click qui per andare sul sito ufficiale del progetto:
-
Non è possibile trovare aggiornamenti.
- È disponibile una nuova versione ({0})
+ È disponibile una nuova versione
Anno _di rilascio album
@@ -465,6 +473,9 @@ Valore consigliato = 4
- {album} è sostituito col nome dell'album;
- {year}, {month} e {day} sono sostituiti con la data di rilascio.
+
+ Go to website
+
You can use placeholders to customize the file name:
- {artist} will be replaced by the album artist
@@ -480,4 +491,7 @@ Incolla l'URL di una pagina artista http://[artista].bandcamp.com e spunta “
Impostazioni
+
+ Update
+
\ No newline at end of file
diff --git a/src/BandcampDownloader/Properties/Resources.resx b/src/BandcampDownloader/Properties/Resources.resx
index 9c14d547..13085d27 100644
--- a/src/BandcampDownloader/Properties/Resources.resx
+++ b/src/BandcampDownloader/Properties/Resources.resx
@@ -145,6 +145,9 @@
Check no_w
+
+ _Download update
+
_Settings
@@ -163,6 +166,9 @@
_Cancel
+
+ Could not download changelog from {0}
+
Check for _updates at startup
@@ -279,6 +285,9 @@ Uncheck this option to save some time.
_Reset settings
+
+ Changelog
+
Comm_ents
@@ -291,6 +300,9 @@ Uncheck this option to save some time.
Max size (p_x)
+
+ Current version:
+
Download _max tries
@@ -336,14 +348,11 @@ Uncheck this option to save some time.
(the log file is always verbose)
-
- Click to go to official project website:
-
Could not check for updates
- A new version ({0}) is available
+ A new version is available
Album release _year
@@ -481,6 +490,9 @@ Recommended value = 4
- {album} will be replaced by the album name
- {year}, {month} and {day} will be replaced by the album release date
+
+ Go to website
+
You can use placeholders to customize the file name:
- {artist} will be replaced by the album artist
@@ -496,4 +508,7 @@ Paste artist pages: http://[artist].bandcamp.com and check "☑ Download artist
Settings
+
+ Update
+
\ No newline at end of file
diff --git a/src/BandcampDownloader/UI/Dialogs/Settings/UserControlSettingsGeneral.xaml.cs b/src/BandcampDownloader/UI/Dialogs/Settings/UserControlSettingsGeneral.xaml.cs
index 0d0586c6..dbf237da 100644
--- a/src/BandcampDownloader/UI/Dialogs/Settings/UserControlSettingsGeneral.xaml.cs
+++ b/src/BandcampDownloader/UI/Dialogs/Settings/UserControlSettingsGeneral.xaml.cs
@@ -57,9 +57,11 @@ private void ButtonCheckForUpdates_Click(object sender, RoutedEventArgs e) {
Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
if (currentVersion.CompareTo(latestVersion) < 0) {
// The latest version is newer than the current one
- if (MessageBox.Show(String.Format(Properties.Resources.messageBoxUpdateAvailable, currentVersion, latestVersion), "Bandcamp Downloader", MessageBoxButton.YesNo, MessageBoxImage.Information, MessageBoxResult.Yes) == MessageBoxResult.Yes) {
- Process.Start(Constants.ProjectWebsite);
- }
+ var windowUpdate = new WindowUpdate() {
+ ShowInTaskbar = true,
+ WindowStartupLocation = WindowStartupLocation.CenterScreen,
+ };
+ windowUpdate.Show();
} else {
MessageBox.Show(String.Format(Properties.Resources.messageBoxNoUpdateAvailable, currentVersion), "Bandcamp Downloader", MessageBoxButton.OK, MessageBoxImage.Information);
}
diff --git a/src/BandcampDownloader/UI/Dialogs/Update/UserControlChangelog.xaml b/src/BandcampDownloader/UI/Dialogs/Update/UserControlChangelog.xaml
new file mode 100644
index 00000000..51e42155
--- /dev/null
+++ b/src/BandcampDownloader/UI/Dialogs/Update/UserControlChangelog.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/BandcampDownloader/UI/Dialogs/Update/UserControlChangelog.xaml.cs b/src/BandcampDownloader/UI/Dialogs/Update/UserControlChangelog.xaml.cs
new file mode 100644
index 00000000..3eaaf709
--- /dev/null
+++ b/src/BandcampDownloader/UI/Dialogs/Update/UserControlChangelog.xaml.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace BandcampDownloader {
+
+ public partial class UserControlChangelog: UserControl {
+
+ public UserControlChangelog() {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ ///
+ /// Downloads the changelog file and returns its content.
+ ///
+ private async Task DownloadChangelogAsync() {
+ String changelog;
+ using (var webClient = new WebClient() { Encoding = Encoding.UTF8 }) {
+ ProxyHelper.SetProxy(webClient);
+ changelog = await webClient.DownloadStringTaskAsync(Constants.ChangelogUrl);
+ }
+
+ return changelog;
+ }
+
+ private async void OnLoaded(object sender, RoutedEventArgs e) {
+ String changelog;
+ try {
+ changelog = await DownloadChangelogAsync();
+ } catch {
+ changelog = String.Format(Properties.Resources.changelogDownloadError, Constants.ChangelogUrl);
+ }
+
+ markdownViewer.Markdown = changelog;
+ }
+
+ private void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) {
+ Process.Start(e.Parameter.ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BandcampDownloader/UI/Dialogs/Update/WindowUpdate.xaml b/src/BandcampDownloader/UI/Dialogs/Update/WindowUpdate.xaml
new file mode 100644
index 00000000..e691cee6
--- /dev/null
+++ b/src/BandcampDownloader/UI/Dialogs/Update/WindowUpdate.xaml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/BandcampDownloader/UI/Dialogs/Update/WindowUpdate.xaml.cs b/src/BandcampDownloader/UI/Dialogs/Update/WindowUpdate.xaml.cs
new file mode 100644
index 00000000..fd28c127
--- /dev/null
+++ b/src/BandcampDownloader/UI/Dialogs/Update/WindowUpdate.xaml.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Reflection;
+using System.Windows;
+using System.Windows.Forms;
+using System.Windows.Input;
+using System.Windows.Navigation;
+
+namespace BandcampDownloader {
+
+ public partial class WindowUpdate: Window {
+ private Version _latestVersion;
+
+ public WindowUpdate() {
+ InitializeComponent();
+ }
+
+ private async void ButtonDownloadUpdate_Click(object sender, RoutedEventArgs e) {
+ String[] parts = Constants.ZipUrl.Split(new char[] { '/' });
+ String defaultFileName = parts[parts.Length - 1];
+
+ var dialog = new SaveFileDialog {
+ FileName = defaultFileName,
+ Filter = "Archive|*.zip",
+ Title = "Save as",
+ };
+ if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) {
+ return;
+ }
+
+ String path = dialog.FileName;
+ String zipUrl = String.Format(Constants.ZipUrl, _latestVersion.ToString());
+
+ using (var webClient = new WebClient()) {
+ ProxyHelper.SetProxy(webClient);
+ await webClient.DownloadFileTaskAsync(zipUrl, path);
+ }
+ }
+
+ private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) {
+ Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
+ e.Handled = true;
+ }
+
+ private void WindowUpdate_Loaded(object sender, RoutedEventArgs e) {
+ try {
+ _latestVersion = UpdatesHelper.GetLatestVersion();
+ } catch (CouldNotCheckForUpdatesException) {
+ // Do nothing
+ return;
+ }
+
+ Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
+ if (currentVersion.CompareTo(_latestVersion) < 0) {
+ // The latest version is newer than the current one
+ buttonDownloadUpdate.IsEnabled = true;
+ }
+ }
+
+ private void WindowUpdate_Loaded_1(object sender, RoutedEventArgs e) {
+ }
+
+ private void WindowUpdate_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) {
+ if (e.Key == Key.Escape) {
+ Close();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BandcampDownloader/UI/Dialogs/WindowMain.xaml b/src/BandcampDownloader/UI/Dialogs/WindowMain.xaml
index 080f2232..ba8ff32f 100644
--- a/src/BandcampDownloader/UI/Dialogs/WindowMain.xaml
+++ b/src/BandcampDownloader/UI/Dialogs/WindowMain.xaml
@@ -124,34 +124,23 @@
private Boolean _activeDownloads = false;
///
- /// Used to update the downloaded bytes / files count on the UI.
- ///
- private ConcurrentQueue _downloadProgresses;
- ///
- /// The files to download, or being downloaded, or downloaded. Used to compute the current received bytes and the total bytes to download.
+ /// The DownloadManager used to download albums.
///
- private List _filesDownload;
+ private DownloadManager _downloadManager;
///
/// Used to compute and display the download speed.
///
@@ -50,18 +36,10 @@ public partial class WindowMain: Window {
///
private long _lastTotalReceivedBytes = 0;
///
- /// Used when user clicks on 'Cancel' to abort all current downloads.
- ///
- private List _pendingDownloads;
- ///
- /// Used when user clicks on 'Cancel' to manage the cancelation (UI...).
+ /// Used when user clicks on 'Cancel' to manage the cancellation (UI...).
///
private Boolean _userCancelled;
- #endregion
-
- #region Constructor
-
public WindowMain() {
// Save DataContext for bindings (must be called before initializing UI)
DataContext = App.UserSettings;
@@ -89,584 +67,100 @@ public WindowMain() {
//+ "https://goataholicskjald.bandcamp.com/track/epilogue" + Environment.NewLine
//+ "https://afterdarkrecordings.bandcamp.com/album/adr-unreleased-tracks" /* #69 Album without cover */ + Environment.NewLine
//+ "https://liluglymane.bandcamp.com/album/study-of-the-hypothesized-removable-and-or-expandable-nature-of-human-capability-and-limitations-primarily-regarding-introductory-experiences-with-new-and-exciting-technologies-by-way-of-motivati-2" /* #54 Long path */ + Environment.NewLine
+ //+ "https://brzoskamarciniakmarkiewicz.bandcamp.com/album/wp-aw" /* #82 Tracks with diacritics */ + Environment.NewLine
;
#endif
}
- #endregion
-
- #region Methods
-
- ///
- /// Displays a message if a new version is available.
- ///
- private void CheckForUpdates() {
- Version latestVersion = null;
- try {
- latestVersion = UpdatesHelper.GetLatestVersion();
- } catch (CouldNotCheckForUpdatesException) {
- Dispatcher.BeginInvoke(new Action(() => {
- labelVersion.Content += " - " + Properties.Resources.labelVersionError;
- }));
- return;
+ private void ButtonBrowse_Click(object sender, RoutedEventArgs e) {
+ var dialog = new System.Windows.Forms.FolderBrowserDialog {
+ Description = Properties.Resources.folderBrowserDialogDescription
+ };
+ if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
+ textBoxDownloadsPath.Text = dialog.SelectedPath + "\\{artist}\\{album}";
+ // Force update of the settings file (it's not done unless the user gives then loses focus on the textbox)
+ textBoxDownloadsPath.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
+ }
- Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
- if (currentVersion.CompareTo(latestVersion) < 0) {
- // The latest version is newer than the current one
- Dispatcher.BeginInvoke(new Action(() => {
- labelVersion.Content = String.Format(Properties.Resources.labelVersionNewUpdateAvailable, latestVersion);
- }));
- }
+ private void ButtonOpenSettingsWindow_Click(object sender, RoutedEventArgs e) {
+ var windowSettings = new WindowSettings(_activeDownloads) {
+ Owner = this,
+ ShowInTaskbar = false,
+ };
+ windowSettings.ShowDialog();
}
- ///
- /// Downloads an album.
- ///
- /// The album to download.
- private void DownloadAlbum(Album album) {
- if (_userCancelled) {
- // Abort
+ private async void ButtonStart_Click(object sender, RoutedEventArgs e) {
+ if (textBoxUrls.Text == "") {
+ // No URL to look
+ Log("Paste some albums URLs to be downloaded", LogType.Error);
return;
}
- // Create directory to place track files
- try {
- Directory.CreateDirectory(album.Path);
- } catch {
- Log("An error occured when creating the album folder. Make sure you have the rights to write files in the folder you chose", LogType.Error);
- return;
- }
+ // Set controls to "downloading..." state
+ _activeDownloads = true;
+ UpdateControlsState(true);
- TagLib.Picture artwork = null;
+ Log("Starting download...", LogType.Info);
- // Download artwork
- if ((App.UserSettings.SaveCoverArtInTags || App.UserSettings.SaveCoverArtInFolder) && album.HasArtwork) {
- artwork = DownloadCoverArt(album);
- }
+ await StartDownloadAsync();
- // Download & tag tracks
- var tasks = new Task[album.Tracks.Count];
- Boolean[] tracksDownloaded = new Boolean[album.Tracks.Count];
- for (int i = 0; i < album.Tracks.Count; i++) {
- // Temporarily save the index or we will have a race condition exception when i hits its maximum value
- int currentIndex = i;
- tasks[currentIndex] = Task.Factory.StartNew(() => tracksDownloaded[currentIndex] = DownloadAndTagTrack(album, album.Tracks[currentIndex], artwork));
+ if (_userCancelled) {
+ // Display message if user cancelled
+ Log("Downloads cancelled by user", LogType.Info);
}
- // Wait for all tracks to be downloaded before saying the album is downloaded
- Task.WaitAll(tasks);
-
- // Create playlist file
- if (App.UserSettings.CreatePlaylist) {
- PlaylistHelper.SavePlaylistForAlbum(album, album.Path);
- Log($"Saved playlist for album \"{album.Title}\"", LogType.IntermediateSuccess);
- }
+ // Reset controls to "ready" state
+ _activeDownloads = false;
+ _lastTotalReceivedBytes = 0;
+ UpdateControlsState(false);
- if (!_userCancelled) {
- // Tasks have not been aborted
- if (tracksDownloaded.All(x => x == true)) {
- Log($"Successfully downloaded album \"{album.Title}\"", LogType.Success);
- } else {
- Log($"Finished downloading album \"{album.Title}\". Some tracks were not downloaded", LogType.Success);
+ if (App.UserSettings.EnableApplicationSounds) {
+ // Play a sound
+ try {
+ new SoundPlayer(@"C:\Windows\Media\Windows Ding.wav").Play();
+ } catch {
}
}
}
- ///
- /// Downloads and tags a track. Returns true if the track has been correctly downloaded; false otherwise.
- ///
- /// The album of the track to download.
- /// The track to download.
- /// The cover art.
- private Boolean DownloadAndTagTrack(Album album, Track track, TagLib.Picture artwork) {
- Log($"Downloading track \"{track.Title}\" from url: {track.Mp3Url}", LogType.VerboseInfo);
-
- int tries = 0;
- Boolean trackDownloaded = false;
-
- if (File.Exists(track.Path)) {
- long length = new FileInfo(track.Path).Length;
- foreach (TrackFile trackFile in _filesDownload) {
- if (track.Mp3Url == trackFile.Url &&
- trackFile.Size > length - (trackFile.Size * App.UserSettings.AllowedFileSizeDifference) &&
- trackFile.Size < length + (trackFile.Size * App.UserSettings.AllowedFileSizeDifference)) {
- Log($"Track already exists within allowed file size range: track \"{Path.GetFileName(track.Path)}\" from album \"{album.Title}\" - Skipping download!", LogType.IntermediateSuccess);
- return false;
- }
- }
+ private void ButtonStop_Click(object sender, RoutedEventArgs e) {
+ if (MessageBox.Show(Properties.Resources.messageBoxCancelDownloads, "Bandcamp Downloader", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) != MessageBoxResult.Yes) {
+ return;
}
- do {
- var doneEvent = new AutoResetEvent(false);
-
- using (var webClient = new WebClient()) {
- switch (App.UserSettings.Proxy) {
- case ProxyType.None:
- webClient.Proxy = null;
- break;
- case ProxyType.System:
- if (webClient.Proxy != null) {
- webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
- }
- break;
- case ProxyType.Manual:
- webClient.Proxy = new WebProxy(App.UserSettings.ProxyHttpAddress, App.UserSettings.ProxyHttpPort);
- break;
- default:
- throw new NotImplementedException(); // Shouldn't happen
- }
-
- // Update progress bar when downloading
- webClient.DownloadProgressChanged += (s, e) => {
- _downloadProgresses.Enqueue(new DownloadProgress(track.Mp3Url, e.BytesReceived));
- UpdateProgress();
- };
-
- // Warn & tag when downloaded
- webClient.DownloadFileCompleted += (s, e) => {
- if (!e.Cancelled && e.Error == null) {
- trackDownloaded = true;
-
- if (App.UserSettings.ModifyTags) {
- // Tag (ID3) the file when downloaded
- var tagFile = TagLib.File.Create(track.Path);
- tagFile = TagHelper.UpdateArtist(tagFile, album.Artist, App.UserSettings.TagArtist);
- tagFile = TagHelper.UpdateAlbumArtist(tagFile, album.Artist, App.UserSettings.TagAlbumArtist);
- tagFile = TagHelper.UpdateAlbumTitle(tagFile, album.Title, App.UserSettings.TagAlbumTitle);
- tagFile = TagHelper.UpdateAlbumYear(tagFile, (uint) album.ReleaseDate.Year, App.UserSettings.TagYear);
- tagFile = TagHelper.UpdateTrackNumber(tagFile, (uint) track.Number, App.UserSettings.TagTrackNumber);
- tagFile = TagHelper.UpdateTrackTitle(tagFile, track.Title, App.UserSettings.TagTrackTitle);
- tagFile = TagHelper.UpdateTrackLyrics(tagFile, track.Lyrics, App.UserSettings.TagLyrics);
- tagFile = TagHelper.UpdateComments(tagFile, App.UserSettings.TagComments);
- tagFile.Save();
- }
-
- if (App.UserSettings.SaveCoverArtInTags && artwork != null) {
- // Save cover in tags when downloaded
- var tagFile = TagLib.File.Create(track.Path);
- tagFile.Tag.Pictures = new TagLib.IPicture[1] { artwork };
- tagFile.Save();
- }
-
- // Note the file as downloaded
- TrackFile currentFile = _filesDownload.Where(f => f.Url == track.Mp3Url).First();
- currentFile.Downloaded = true;
- Log($"Downloaded track \"{Path.GetFileName(track.Path)}\" from album \"{album.Title}\"", LogType.IntermediateSuccess);
- } else if (!e.Cancelled && e.Error != null) {
- if (tries + 1 < App.UserSettings.DownloadMaxTries) {
- Log($"Unable to download track \"{Path.GetFileName(track.Path)}\" from album \"{album.Title}\". Try {tries + 1} of {App.UserSettings.DownloadMaxTries}", LogType.Warning);
- } else {
- Log($"Unable to download track \"{Path.GetFileName(track.Path)}\" from album \"{album.Title}\". Hit max retries of {App.UserSettings.DownloadMaxTries}", LogType.Error);
- }
- } // Else the download has been cancelled (by the user)
-
- tries++;
- if (!trackDownloaded && tries < App.UserSettings.DownloadMaxTries) {
- WaitForCooldown(tries);
- }
-
- doneEvent.Set();
- };
-
- lock (_pendingDownloads) {
- if (_userCancelled) {
- // Abort
- return false;
- }
- // Register current download
- _pendingDownloads.Add(webClient);
- // Start download
- webClient.DownloadFileAsync(new Uri(track.Mp3Url), track.Path);
- }
- // Wait for download to be finished
- doneEvent.WaitOne();
- lock (_pendingDownloads) {
- _pendingDownloads.Remove(webClient);
- }
- }
- } while (!trackDownloaded && tries < App.UserSettings.DownloadMaxTries);
-
- return trackDownloaded;
- }
-
- ///
- /// Downloads the cover art and returns the one to save in tags.
- ///
- /// The album to download.
- private TagLib.Picture DownloadCoverArt(Album album) {
- TagLib.Picture artworkInTags = null;
-
- int tries = 0;
- Boolean artworkDownloaded = false;
-
- do {
- var doneEvent = new AutoResetEvent(false);
-
- using (var webClient = new WebClient()) {
- switch (App.UserSettings.Proxy) {
- case ProxyType.None:
- webClient.Proxy = null;
- break;
- case ProxyType.System:
- if (webClient.Proxy != null) {
- webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
- }
- break;
- case ProxyType.Manual:
- webClient.Proxy = new WebProxy(App.UserSettings.ProxyHttpAddress, App.UserSettings.ProxyHttpPort);
- break;
- default:
- throw new NotImplementedException(); // Shouldn't happen
- }
-
- // Update progress bar when downloading
- webClient.DownloadProgressChanged += (s, e) => {
- _downloadProgresses.Enqueue(new DownloadProgress(album.ArtworkUrl, e.BytesReceived));
- UpdateProgress();
- };
-
- // Warn when downloaded
- webClient.DownloadFileCompleted += (s, e) => {
- if (!e.Cancelled && e.Error == null) {
- artworkDownloaded = true;
-
- // Convert/resize artwork to be saved in album folder
- if (App.UserSettings.SaveCoverArtInFolder && (App.UserSettings.CoverArtInFolderConvertToJpg || App.UserSettings.CoverArtInFolderResize)) {
- var settings = new ResizeSettings();
- if (App.UserSettings.CoverArtInFolderConvertToJpg) {
- settings.Format = "jpg";
- settings.Quality = 90;
- }
- if (App.UserSettings.CoverArtInFolderResize) {
- settings.MaxHeight = App.UserSettings.CoverArtInFolderMaxSize;
- settings.MaxWidth = App.UserSettings.CoverArtInFolderMaxSize;
- }
- ImageBuilder.Current.Build(album.ArtworkTempPath, album.ArtworkPath, settings); // Save it to the album folder
- } else if (App.UserSettings.SaveCoverArtInFolder) {
- File.Copy(album.ArtworkTempPath, album.ArtworkPath, true);
- }
-
- // Convert/resize artwork to be saved in tags
- if (App.UserSettings.SaveCoverArtInTags && (App.UserSettings.CoverArtInTagsConvertToJpg || App.UserSettings.CoverArtInTagsResize)) {
- var settings = new ResizeSettings();
- if (App.UserSettings.CoverArtInTagsConvertToJpg) {
- settings.Format = "jpg";
- settings.Quality = 90;
- }
- if (App.UserSettings.CoverArtInTagsResize) {
- settings.MaxHeight = App.UserSettings.CoverArtInTagsMaxSize;
- settings.MaxWidth = App.UserSettings.CoverArtInTagsMaxSize;
- }
- ImageBuilder.Current.Build(album.ArtworkTempPath, album.ArtworkTempPath, settings); // Save it to %Temp%
- }
- artworkInTags = new TagLib.Picture(album.ArtworkTempPath) { Description = "Picture" };
-
- try {
- File.Delete(album.ArtworkTempPath);
- } catch {
- // Could not delete the file. Nevermind, it's in %Temp% folder...
- }
-
- // Note the file as downloaded
- TrackFile currentFile = _filesDownload.Where(f => f.Url == album.ArtworkUrl).First();
- currentFile.Downloaded = true;
- Log($"Downloaded artwork for album \"{album.Title}\"", LogType.IntermediateSuccess);
- } else if (!e.Cancelled && e.Error != null) {
- if (tries < App.UserSettings.DownloadMaxTries) {
- Log($"Unable to download artwork for album \"{album.Title}\". Try {tries + 1} of {App.UserSettings.DownloadMaxTries}", LogType.Warning);
- } else {
- Log($"Unable to download artwork for album \"{album.Title}\". Hit max retries of {App.UserSettings.DownloadMaxTries}", LogType.Error);
- }
- } // Else the download has been cancelled (by the user)
-
- tries++;
- if (!artworkDownloaded && tries < App.UserSettings.DownloadMaxTries) {
- WaitForCooldown(tries);
- }
-
- doneEvent.Set();
- };
-
- lock (_pendingDownloads) {
- if (_userCancelled) {
- // Abort
- return null;
- }
- // Register current download
- _pendingDownloads.Add(webClient);
- // Start download
- webClient.DownloadFileAsync(new Uri(album.ArtworkUrl), album.ArtworkTempPath);
- }
-
- // Wait for download to be finished
- doneEvent.WaitOne();
- lock (_pendingDownloads) {
- _pendingDownloads.Remove(webClient);
- }
- }
- } while (!artworkDownloaded && tries < App.UserSettings.DownloadMaxTries);
+ _userCancelled = true;
+ buttonStop.IsEnabled = false;
- return artworkInTags;
+ _downloadManager.CancelDownloads();
}
///
- /// Returns the albums located at the specified URLs.
+ /// Displays a message if a new version is available.
///
- /// The URLs.
- private List GetAlbums(List urls) {
- var albums = new List();
-
- foreach (String url in urls) {
- Log($"Retrieving album data for {url}", LogType.Info);
-
- // Retrieve URL HTML source code
- String htmlCode = "";
- using (var webClient = new WebClient() { Encoding = Encoding.UTF8 }) {
- switch (App.UserSettings.Proxy) {
- case ProxyType.None:
- webClient.Proxy = null;
- break;
- case ProxyType.System:
- if (webClient.Proxy != null) {
- webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
- }
- break;
- case ProxyType.Manual:
- webClient.Proxy = new WebProxy(App.UserSettings.ProxyHttpAddress, App.UserSettings.ProxyHttpPort);
- break;
- default:
- throw new NotImplementedException(); // Shouldn't happen
- }
-
- if (_userCancelled) {
- // Abort
- return new List();
- }
-
- try {
- htmlCode = webClient.DownloadString(url);
- } catch {
- Log($"Could not retrieve data for {url}", LogType.Error);
- continue;
- }
- }
-
- // Get info on album
- try {
- albums.Add(BandcampHelper.GetAlbum(htmlCode));
- } catch {
- Log($"Could not retrieve album info for {url}", LogType.Error);
- continue;
- }
+ private void CheckForUpdates() {
+ Version latestVersion;
+ try {
+ latestVersion = UpdatesHelper.GetLatestVersion();
+ } catch (CouldNotCheckForUpdatesException) {
+ Dispatcher.BeginInvoke(new Action(() => {
+ labelNewVersion.Content = Properties.Resources.labelVersionError;
+ }));
+ return;
}
- return albums;
- }
-
- ///
- /// Returns the artists discography from any URL (artist, album, track).
- ///
- /// The URLs.
- private List GetArtistDiscography(List urls) {
- var albumsUrls = new List();
-
- foreach (String url in urls) {
- Log($"Retrieving artist discography from {url}", LogType.Info);
-
- // Retrieve URL HTML source code
- String htmlCode = "";
- using (var webClient = new WebClient() { Encoding = Encoding.UTF8 }) {
- switch (App.UserSettings.Proxy) {
- case ProxyType.None:
- webClient.Proxy = null;
- break;
- case ProxyType.System:
- if (webClient.Proxy != null) {
- webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
- }
- break;
- case ProxyType.Manual:
- webClient.Proxy = new WebProxy(App.UserSettings.ProxyHttpAddress, App.UserSettings.ProxyHttpPort);
- break;
- default:
- throw new NotImplementedException(); // Shouldn't happen
- }
-
- if (_userCancelled) {
- // Abort
- return new List();
- }
-
- try {
- htmlCode = webClient.DownloadString(url);
- } catch {
- Log($"Could not retrieve data for {url}", LogType.Error);
- continue;
- }
- }
-
- // Get artist "music" bandcamp page (http://artist.bandcamp.com/music)
- var regex = new Regex("band_url = \"(?.*)\"");
- if (!regex.IsMatch(htmlCode)) {
- Log($"No discography could be found on {url}. Try to uncheck the \"Download artist discography\" option", LogType.Error);
- continue;
- }
- String artistMusicPage = regex.Match(htmlCode).Groups["url"].Value + "/music";
-
- // Retrieve artist "music" page HTML source code
- using (var webClient = new WebClient() { Encoding = Encoding.UTF8 }) {
- switch (App.UserSettings.Proxy) {
- case ProxyType.None:
- webClient.Proxy = null;
- break;
- case ProxyType.System:
- if (webClient.Proxy != null) {
- webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
- }
- break;
- case ProxyType.Manual:
- webClient.Proxy = new WebProxy(App.UserSettings.ProxyHttpAddress, App.UserSettings.ProxyHttpPort);
- break;
- default:
- throw new NotImplementedException(); // Shouldn't happen
- }
-
- if (_userCancelled) {
- // Abort
- return new List();
- }
-
- try {
- htmlCode = webClient.DownloadString(artistMusicPage);
- } catch {
- Log($"Could not retrieve data for {artistMusicPage}", LogType.Error);
- continue;
- }
- }
-
- // Get albums referred on the page
- regex = new Regex("TralbumData.*\n.*url:.*'/music'\n");
- if (!regex.IsMatch(htmlCode)) {
- // This seem to be a one-album artist with no "music" page => URL redirects to the unique album URL
- albumsUrls.Add(url);
- } else {
- // We are on a real "music" page
- try {
- albumsUrls.AddRange(BandcampHelper.GetAlbumsUrl(htmlCode));
- } catch (NoAlbumFoundException) {
- Log($"No referred album could be found on {artistMusicPage}. Try to uncheck the \"Download artist discography\" option", LogType.Error);
- continue;
- }
- }
+ Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
+ if (currentVersion.CompareTo(latestVersion) < 0) {
+ // The latest version is newer than the current one
+ Dispatcher.BeginInvoke(new Action(() => {
+ labelNewVersion.Content = Properties.Resources.labelVersionNewUpdateAvailable;
+ }));
}
-
- return albumsUrls;
}
- ///
- /// Returns the files to download from a list of albums.
- ///
- /// The albums.
- /// True if the cover arts must be downloaded, false otherwise.
- private List GetFilesToDownload(List albums, Boolean downloadCoverArt) {
- var files = new List();
- foreach (Album album in albums) {
- Log($"Computing size for album \"{album.Title}\"...", LogType.Info);
-
- // Artwork
- if (downloadCoverArt && album.HasArtwork) {
- if (App.UserSettings.RetrieveFilesSize) {
- long size = 0;
- Boolean sizeRetrieved = false;
- int tries = 0;
- do {
- if (_userCancelled) {
- // Abort
- return new List();
- }
-
- try {
- size = FileHelper.GetFileSize(album.ArtworkUrl, "HEAD");
- sizeRetrieved = true;
- Log($"Retrieved the size of the cover art file for album \"{album.Title}\"", LogType.VerboseInfo);
- } catch {
- sizeRetrieved = false;
- if (tries + 1 < App.UserSettings.DownloadMaxTries) {
- Log($"Failed to retrieve the size of the cover art file for album \"{album.Title}\". Try {tries + 1} of {App.UserSettings.DownloadMaxTries}", LogType.Warning);
- } else {
- Log($"Failed to retrieve the size of the cover art file for album \"{album.Title}\". Hit max retries of {App.UserSettings.DownloadMaxTries}. Progress update may be wrong.", LogType.Error);
- }
- }
-
- tries++;
- if (!sizeRetrieved && tries < App.UserSettings.DownloadMaxTries) {
- WaitForCooldown(tries);
- }
- } while (!sizeRetrieved && tries < App.UserSettings.DownloadMaxTries);
- files.Add(new TrackFile(album.ArtworkUrl, 0, size));
- } else {
- files.Add(new TrackFile(album.ArtworkUrl, 0, 0));
- }
- }
-
- // Tracks
- if (App.UserSettings.RetrieveFilesSize) {
- var tasks = new Task[album.Tracks.Count];
- for (int i = 0; i < album.Tracks.Count; i++) {
- // Temporarily save the index or we will have a race condition exception when i hits its maximum value
- int trackIndex = i;
-
- if (_userCancelled) {
- // Abort
- return new List();
- }
-
- tasks[trackIndex] = Task.Factory.StartNew(() => {
- long size = 0;
- Boolean sizeRetrieved = false;
- int tries = 0;
- do {
- if (_userCancelled) {
- // Abort
- break;
- }
-
- try {
- // Using the HEAD method on tracks urls does not work (Error 405: Method not allowed)
- // Surprisingly, using the GET method does not seem to download the whole file, so we will use it to retrieve
- // the mp3 sizes
- size = FileHelper.GetFileSize(album.Tracks[trackIndex].Mp3Url, "GET");
- sizeRetrieved = true;
- Log($"Retrieved the size of the MP3 file for the track \"{album.Tracks[trackIndex].Title}\"", LogType.VerboseInfo);
- } catch {
- sizeRetrieved = false;
- if (tries + 1 < App.UserSettings.DownloadMaxTries) {
- Log($"Failed to retrieve the size of the MP3 file for the track \"{album.Tracks[trackIndex].Title}\". Try {tries + 1} of {App.UserSettings.DownloadMaxTries}", LogType.Warning);
- } else {
- Log($"Failed to retrieve the size of the MP3 file for the track \"{album.Tracks[trackIndex].Title}\". Hit max retries of {App.UserSettings.DownloadMaxTries}. Progress update may be wrong.", LogType.Error);
- }
- }
-
- tries++;
- if (!sizeRetrieved && tries < App.UserSettings.DownloadMaxTries) {
- WaitForCooldown(tries);
- }
- } while (!sizeRetrieved && tries < App.UserSettings.DownloadMaxTries);
- files.Add(new TrackFile(album.Tracks[trackIndex].Mp3Url, 0, size));
- });
- }
-
- // Wait for all tracks size to be retrieved
- Task.WaitAll(tasks);
- } else {
- foreach (Track track in album.Tracks) {
- files.Add(new TrackFile(track.Mp3Url, 0, 0));
- }
- }
- }
-
- return files;
+ private void DownloadManager_LogAdded(object sender, LogArgs eventArgs) {
+ Log(eventArgs.Message, eventArgs.LogType);
}
///
@@ -686,11 +180,20 @@ private void InitializeLogger() {
LogManager.Configuration = config;
}
+ private void LabelNewVersion_MouseDown(object sender, MouseButtonEventArgs e) {
+ var windowUpdate = new WindowUpdate() {
+ Owner = this,
+ ShowInTaskbar = true,
+ WindowStartupLocation = WindowStartupLocation.CenterScreen,
+ };
+ windowUpdate.Show();
+ }
+
///
- /// Logs to file and displays the specified message in the log textbox with the specified color.
+ /// Logs to file and displays the specified message in the log textbox.
///
/// The message.
- /// The color.
+ /// The log type.
private void Log(String message, LogType logType) {
// Log to file
Logger logger = LogManager.GetCurrentClassLogger();
@@ -698,278 +201,165 @@ private void Log(String message, LogType logType) {
// Log to window
if (App.UserSettings.ShowVerboseLog || logType == LogType.Error || logType == LogType.Info || logType == LogType.IntermediateSuccess || logType == LogType.Success) {
- Dispatcher.Invoke(new Action(() => {
- // Time
- var textRange = new TextRange(richTextBoxLog.Document.ContentEnd, richTextBoxLog.Document.ContentEnd) {
- Text = DateTime.Now.ToString("HH:mm:ss") + " "
- };
- textRange.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Gray);
- // Message
- textRange = new TextRange(richTextBoxLog.Document.ContentEnd, richTextBoxLog.Document.ContentEnd) {
- Text = message
- };
- textRange.ApplyPropertyValue(TextElement.ForegroundProperty, LogHelper.GetColor(logType));
- // Line break
- richTextBoxLog.AppendText(Environment.NewLine);
-
- if (richTextBoxLog.IsScrolledToEnd()) {
- richTextBoxLog.ScrollToEnd();
- }
- }));
+ // Time
+ var textRange = new TextRange(richTextBoxLog.Document.ContentEnd, richTextBoxLog.Document.ContentEnd) {
+ Text = DateTime.Now.ToString("HH:mm:ss") + " "
+ };
+ textRange.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Gray);
+ // Message
+ textRange = new TextRange(richTextBoxLog.Document.ContentEnd, richTextBoxLog.Document.ContentEnd) {
+ Text = message
+ };
+ textRange.ApplyPropertyValue(TextElement.ForegroundProperty, LogHelper.GetColor(logType));
+ // Line break
+ richTextBoxLog.AppendText(Environment.NewLine);
+
+ if (richTextBoxLog.IsScrolledToEnd()) {
+ richTextBoxLog.ScrollToEnd();
+ }
}
}
///
- /// Updates the state of the controls.
+ /// Starts downloads.
///
- /// True if the download just started, false if it just stopped.
- private void UpdateControlsState(Boolean downloadStarted) {
- Dispatcher.Invoke(new Action(() => {
- if (downloadStarted) {
- // We just started the download
- buttonBrowse.IsEnabled = false;
- buttonStart.IsEnabled = false;
- buttonStop.IsEnabled = true;
- checkBoxDownloadDiscography.IsEnabled = false;
- labelProgress.Content = "";
- progressBar.IsIndeterminate = true;
- progressBar.Value = progressBar.Minimum;
- richTextBoxLog.Document.Blocks.Clear();
- TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Indeterminate;
- TaskbarItemInfo.ProgressValue = 0;
- textBoxDownloadsPath.IsReadOnly = true;
- textBoxUrls.IsReadOnly = true;
- } else {
- // We just finished the download (or user has cancelled)
- buttonBrowse.IsEnabled = true;
- buttonStart.IsEnabled = true;
- buttonStop.IsEnabled = false;
- checkBoxDownloadDiscography.IsEnabled = true;
- labelDownloadSpeed.Content = "";
- progressBar.Foreground = new SolidColorBrush((Color) ColorConverter.ConvertFromString("#FF01D328")); // Green
- progressBar.IsIndeterminate = false;
- progressBar.Value = progressBar.Minimum;
- TaskbarItemInfo.ProgressState = TaskbarItemProgressState.None;
- TaskbarItemInfo.ProgressValue = 0;
- textBoxDownloadsPath.IsReadOnly = false;
- textBoxUrls.IsReadOnly = false;
- }
- }));
- }
+ private async Task StartDownloadAsync() {
+ _userCancelled = false;
- ///
- /// Updates the progress messages and the progressbar.
- ///
- private void UpdateProgress() {
- DateTime now = DateTime.Now;
+ // Initializes the DownloadManager
+ _downloadManager = new DownloadManager(textBoxUrls.Text);
+ _downloadManager.LogAdded += DownloadManager_LogAdded;
- _downloadProgresses.TryDequeue(out DownloadProgress downloadProgress);
+ // Fetch URL to get the files size
+ await _downloadManager.FetchUrlsAsync();
- // Compute new progress values
- TrackFile currentFile = _filesDownload.Where(f => f.Url == downloadProgress.FileUrl).First();
- currentFile.BytesReceived = downloadProgress.BytesReceived;
- long totalReceivedBytes = _filesDownload.Sum(f => f.BytesReceived);
- long bytesToDownload = _filesDownload.Sum(f => f.Size);
- Double downloadedFilesCount = _filesDownload.Count(f => f.Downloaded);
-
- Double bytesPerSecond;
- if (_lastTotalReceivedBytes == 0) {
- // First time we update the progress
- bytesPerSecond = 0;
- _lastTotalReceivedBytes = totalReceivedBytes;
- _lastDownloadSpeedUpdate = now;
- } else if ((now - _lastDownloadSpeedUpdate).TotalMilliseconds > 500) {
- // Last update of progress happened more than 500 milliseconds ago
- // We only update the download speed every 500+ milliseconds
- bytesPerSecond =
- ((Double) (totalReceivedBytes - _lastTotalReceivedBytes)) /
- (now - _lastDownloadSpeedUpdate).TotalSeconds;
- _lastTotalReceivedBytes = totalReceivedBytes;
- _lastDownloadSpeedUpdate = now;
-
- // Update UI
- Dispatcher.Invoke(new Action(() => {
- // Update download speed
- labelDownloadSpeed.Content = (bytesPerSecond / 1024).ToString("0.0") + " kB/s";
- }));
+ // Set progressBar max value
+ long maxProgressBarValue;
+ if (App.UserSettings.RetrieveFilesSize) {
+ maxProgressBarValue = _downloadManager.DownloadingFiles.Sum(f => f.Size); // Bytes to download
+ } else {
+ maxProgressBarValue = _downloadManager.DownloadingFiles.Count; // Number of files to download
}
-
- // Update UI
- Dispatcher.Invoke(new Action(() => {
- if (!_userCancelled) {
- // Update progress label
- labelProgress.Content =
- ((Double) totalReceivedBytes / (1024 * 1024)).ToString("0.00") + " MB" +
- (App.UserSettings.RetrieveFilesSize ? (" / " + ((Double) bytesToDownload / (1024 * 1024)).ToString("0.00") + " MB") : "");
- if (App.UserSettings.RetrieveFilesSize) {
- // Update progress bar based on bytes received
- progressBar.Value = totalReceivedBytes;
- // Taskbar progress is between 0 and 1
- TaskbarItemInfo.ProgressValue = totalReceivedBytes / progressBar.Maximum;
- } else {
- // Update progress bar based on downloaded files
- progressBar.Value = downloadedFilesCount;
- // Taskbar progress is between 0 and count of files to download
- TaskbarItemInfo.ProgressValue = downloadedFilesCount / progressBar.Maximum;
- }
- }
- }));
- }
-
- private void WaitForCooldown(int triesNumber) {
- if (App.UserSettings.DownloadRetryCooldown != 0) {
- Thread.Sleep((int) (Math.Pow(App.UserSettings.DownloadRetryExponent, triesNumber) * App.UserSettings.DownloadRetryCooldown * 1000));
+ if (maxProgressBarValue > 0) {
+ progressBar.IsIndeterminate = false;
+ progressBar.Maximum = maxProgressBarValue;
+ TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Normal;
}
- }
- #endregion
-
- #region Events
-
- private void ButtonBrowse_Click(object sender, RoutedEventArgs e) {
- var dialog = new System.Windows.Forms.FolderBrowserDialog {
- Description = Properties.Resources.folderBrowserDialogDescription
+ // Start timer to update progress on UI
+ var updateProgressTimer = new DispatcherTimer() {
+ Interval = TimeSpan.FromMilliseconds(100)
};
- if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
- textBoxDownloadsPath.Text = dialog.SelectedPath + "\\{artist}\\{album}";
- // Force update of the settings file (it's not done unless the user give then lose focus on the textbox)
- textBoxDownloadsPath.GetBindingExpression(TextBox.TextProperty).UpdateSource();
- }
- }
+ updateProgressTimer.Tick += UpdateProgressTimer_Tick;
+ updateProgressTimer.Start();
- private void ButtonOpenSettingsWindow_Click(object sender, RoutedEventArgs e) {
- var windowSettings = new WindowSettings(_activeDownloads) {
- Owner = this,
- ShowInTaskbar = false,
+ // Start timer to update download speed on UI
+ var updateDownloadSpeedTimer = new DispatcherTimer() {
+ Interval = TimeSpan.FromMilliseconds(1000)
};
- windowSettings.ShowDialog();
- }
+ updateDownloadSpeedTimer.Tick += UpdateDownloadSpeedTimer_Tick;
+ updateDownloadSpeedTimer.Start();
- private void ButtonStart_Click(object sender, RoutedEventArgs e) {
- if (textBoxUrls.Text == "") {
- // No URL to look
- Log("Paste some albums URLs to be downloaded", LogType.Error);
- return;
- }
+ // Start downloading albums
+ await _downloadManager.StartDownloadsAsync();
- _userCancelled = false;
+ // Stop timers
+ updateProgressTimer.Stop();
+ updateDownloadSpeedTimer.Stop();
- _pendingDownloads = new List();
+ // Update progress one last time to make sure the downloaded bytes displayed on UI is up-to-date
+ UpdateProgress();
+ }
- // Set controls to "downloading..." state
- _activeDownloads = true;
- UpdateControlsState(true);
+ ///
+ /// Updates the state of the controls.
+ ///
+ /// True if the download just started; false if it just stopped.
+ private void UpdateControlsState(Boolean downloadStarted) {
+ if (downloadStarted) {
+ // We just started the download
+ buttonBrowse.IsEnabled = false;
+ buttonStart.IsEnabled = false;
+ buttonStop.IsEnabled = true;
+ checkBoxDownloadDiscography.IsEnabled = false;
+ labelProgress.Content = "";
+ progressBar.Foreground = new SolidColorBrush((Color) ColorConverter.ConvertFromString("#FF01D328")); // Green
+ progressBar.IsIndeterminate = true;
+ progressBar.Value = progressBar.Minimum;
+ richTextBoxLog.Document.Blocks.Clear();
+ TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Indeterminate;
+ TaskbarItemInfo.ProgressValue = 0;
+ textBoxDownloadsPath.IsReadOnly = true;
+ textBoxUrls.IsReadOnly = true;
+ } else {
+ // We just finished the download (or user has cancelled)
+ buttonBrowse.IsEnabled = true;
+ buttonStart.IsEnabled = true;
+ buttonStop.IsEnabled = false;
+ checkBoxDownloadDiscography.IsEnabled = true;
+ labelDownloadSpeed.Content = "";
+ progressBar.Foreground = new SolidColorBrush((Color) ColorConverter.ConvertFromString("#FF01D328")); // Green
+ progressBar.IsIndeterminate = false;
+ progressBar.Value = progressBar.Minimum;
+ TaskbarItemInfo.ProgressState = TaskbarItemProgressState.None;
+ TaskbarItemInfo.ProgressValue = 0;
+ textBoxDownloadsPath.IsReadOnly = false;
+ textBoxUrls.IsReadOnly = false;
+ }
+ }
- Log("Starting download...", LogType.Info);
+ ///
+ /// Updates the download speed on UI.
+ ///
+ private void UpdateDownloadSpeed() {
+ DateTime now = DateTime.Now;
- // Get user inputs
- var userUrls = textBoxUrls.Text.Split(new String[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList();
- userUrls = userUrls.Distinct().ToList();
+ // Compute new progress values
+ long totalReceivedBytes = _downloadManager.DownloadingFiles.Sum(f => f.BytesReceived);
- var urls = new List();
- var albums = new List();
- _downloadProgresses = new ConcurrentQueue();
+ Double bytesPerSecond =
+ ((Double) (totalReceivedBytes - _lastTotalReceivedBytes)) /
+ (now - _lastDownloadSpeedUpdate).TotalSeconds;
+ _lastTotalReceivedBytes = totalReceivedBytes;
+ _lastDownloadSpeedUpdate = now;
- Task.Factory.StartNew(() => {
- // Get URLs of albums to download
- if (App.UserSettings.DownloadArtistDiscography) {
- urls = GetArtistDiscography(userUrls);
- } else {
- urls = userUrls;
- }
- urls = urls.Distinct().ToList();
- }).ContinueWith(x => {
- // Get info on albums
- albums = GetAlbums(urls);
- }).ContinueWith(x => {
- // Save files to download (we'll need the list to update the progressBar)
- _filesDownload = GetFilesToDownload(albums, App.UserSettings.SaveCoverArtInTags || App.UserSettings.SaveCoverArtInFolder);
- }).ContinueWith(x => {
- // Set progressBar max value
- long maxProgressBarValue;
- if (App.UserSettings.RetrieveFilesSize) {
- maxProgressBarValue = _filesDownload.Sum(f => f.Size); // Bytes to download
- } else {
- maxProgressBarValue = _filesDownload.Count; // Number of files to download
- }
- if (maxProgressBarValue > 0) {
- Dispatcher.Invoke(new Action(() => {
- progressBar.IsIndeterminate = false;
- progressBar.Maximum = maxProgressBarValue;
- TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Normal;
- }));
- }
- }).ContinueWith(x => {
- // Start downloading albums
- if (App.UserSettings.DownloadOneAlbumAtATime) {
- // Download one album at a time
- foreach (Album album in albums) {
- DownloadAlbum(album);
- }
- } else {
- // Parallel download
- var tasks = new Task[albums.Count];
- for (int i = 0; i < albums.Count; i++) {
- Album album = albums[i]; // Mandatory or else => race condition
- tasks[i] = Task.Factory.StartNew(() =>
- DownloadAlbum(album));
- }
- // Wait for all albums to be downloaded
- Task.WaitAll(tasks);
- }
- }).ContinueWith(x => {
- if (_userCancelled) {
- // Display message if user cancelled
- Log("Downloads cancelled by user", LogType.Info);
- }
- // Set controls to "ready" state
- _activeDownloads = false;
- UpdateControlsState(false);
- if (App.UserSettings.EnableApplicationSounds) {
- // Play a sound
- try {
- new SoundPlayer(@"C:\Windows\Media\Windows Ding.wav").Play();
- } catch {
- }
- }
- });
+ // Update download speed on UI
+ labelDownloadSpeed.Content = (bytesPerSecond / 1024).ToString("0.0") + " kB/s";
}
- private void ButtonStop_Click(object sender, RoutedEventArgs e) {
- if (MessageBox.Show(Properties.Resources.messageBoxCancelDownloads, "Bandcamp Downloader", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) != MessageBoxResult.Yes) {
- return;
- }
-
- _userCancelled = true;
- Cursor = Cursors.Wait;
- Log("Cancelling downloads. Please wait...", LogType.Info);
-
- lock (_pendingDownloads) {
- if (_pendingDownloads.Count == 0) {
- // Nothing to cancel
- Cursor = Cursors.Arrow;
- return;
- }
- }
+ private void UpdateDownloadSpeedTimer_Tick(object sender, EventArgs e) {
+ UpdateDownloadSpeed();
+ }
- buttonStop.IsEnabled = false;
- progressBar.Foreground = Brushes.Red;
- progressBar.IsIndeterminate = true;
- TaskbarItemInfo.ProgressState = TaskbarItemProgressState.None;
- TaskbarItemInfo.ProgressValue = 0;
-
- lock (_pendingDownloads) {
- // Stop current downloads
- foreach (WebClient webClient in _pendingDownloads) {
- webClient.CancelAsync();
- }
+ ///
+ /// Updates the progress label on UI.
+ ///
+ private void UpdateProgress() {
+ // Compute new progress values
+ long totalReceivedBytes = _downloadManager.DownloadingFiles.Sum(f => f.BytesReceived);
+ long bytesToDownload = _downloadManager.DownloadingFiles.Sum(f => f.Size);
+
+ // Update progress label
+ labelProgress.Content =
+ ((Double) totalReceivedBytes / (1024 * 1024)).ToString("0.00") + " MB" +
+ (App.UserSettings.RetrieveFilesSize ? (" / " + ((Double) bytesToDownload / (1024 * 1024)).ToString("0.00") + " MB") : "");
+
+ if (App.UserSettings.RetrieveFilesSize) {
+ // Update progress bar based on bytes received
+ progressBar.Value = totalReceivedBytes;
+ // Taskbar progress is between 0 and 1
+ TaskbarItemInfo.ProgressValue = totalReceivedBytes / progressBar.Maximum;
+ } else {
+ Double downloadedFilesCount = _downloadManager.DownloadingFiles.Count(f => f.Downloaded);
+ // Update progress bar based on downloaded files
+ progressBar.Value = downloadedFilesCount;
+ // Taskbar progress is between 0 and count of files to download
+ TaskbarItemInfo.ProgressValue = downloadedFilesCount / progressBar.Maximum;
}
-
- Cursor = Cursors.Arrow;
}
- private void LabelVersion_MouseDown(object sender, MouseButtonEventArgs e) {
- Process.Start(Constants.ProjectWebsite);
+ private void UpdateProgressTimer_Tick(object sender, EventArgs e) {
+ UpdateProgress();
}
private void WindowMain_Closing(object sender, CancelEventArgs e) {
@@ -981,7 +371,5 @@ private void WindowMain_Closing(object sender, CancelEventArgs e) {
}
}
}
-
- #endregion
}
}
\ No newline at end of file
diff --git a/src/BandcampDownloader/packages.config b/src/BandcampDownloader/packages.config
index 4729d0a1..a636b8fb 100644
--- a/src/BandcampDownloader/packages.config
+++ b/src/BandcampDownloader/packages.config
@@ -6,6 +6,8 @@
+
+