diff --git a/README.md b/README.md index 48ab927..ac06c68 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,25 @@ A command-line tool written in Kotlin to automatically download all albums of a ``` -Usage:
[-h] [-c=] [-d=] [-f=] - The bandcamp user account from which all albums must be downloaded. +Usage:
[-h] [-c=] [-d=] [-f=] [-r=] + The bandcamp user account from which all albums must be downloaded. -c, --cookies-file= - A JSON file with valid bandcamp credential cookies. - "Cookie Quick Manager" can be used to obtain this file after logging into bandcamp. - (visit https://addons.mozilla.org/en-US/firefox/addon/cookie-quick-manager/). - If no cookies file is provided, cookies from the local Firefox installation are used (Windows and - Linux only). + + A JSON file with valid bandcamp credential cookies. + "Cookie Quick Manager" can be used to obtain this file after logging into bandcamp. + (visit https://addons.mozilla.org/en-US/firefox/addon/cookie-quick-manager/). + If no cookies file is provided, cookies from the local Firefox installation are used (Windows and Linux + only). -d, --download-folder= - The folder in which downloaded albums must be extracted. - The following structure is considered: // - . - (default: current folder) + The folder in which downloaded albums must be extracted. + The following structure is considered: // - . + (default: current folder) -f, --audio-format= - The chosen audio format of the files to download (default: vorbis). - Possible values: flac, wav, aac-hi, mp3-320, aiff-lossless, vorbis, mp3-v0, alac. - -h, --help Display this help message. -``` + The chosen audio format of the files to download (default: vorbis). + Possible values: flac, wav, aac-hi, mp3-320, aiff-lossless, vorbis, mp3-v0, alac. + -h, --help Display this help message. + -r, --retries= Amount of retries when downloading an album. +``` ## Bandcamp authentication diff --git a/src/main/kotlin/bandcampcollectiondownloader/BandcampCollectionDownloader.kt b/src/main/kotlin/bandcampcollectiondownloader/BandcampCollectionDownloader.kt index 3ee8de2..a3048db 100644 --- a/src/main/kotlin/bandcampcollectiondownloader/BandcampCollectionDownloader.kt +++ b/src/main/kotlin/bandcampcollectiondownloader/BandcampCollectionDownloader.kt @@ -31,11 +31,10 @@ data class ParsedStatDownload( ) - /** * Core function called from the main */ -fun downloadAll(cookiesFile: Path?, bandcampUser: String, downloadFormat: String, downloadFolder: Path) { +fun downloadAll(cookiesFile: Path?, bandcampUser: String, downloadFormat: String, downloadFolder: Path, retries: Int) { val gson = Gson() val cookies = @@ -87,8 +86,8 @@ fun downloadAll(cookiesFile: Path?, bandcampUser: String, downloadFormat: String // If windows, replace colons in file names by a unicode char that looks like a colon if (isWindows()) { - albumtitle = albumtitle.replace(':','꞉') - artist = artist.replace(':','꞉') + albumtitle = albumtitle.replace(':', '꞉') + artist = artist.replace(':', '꞉') } // Prepare artist and album folder @@ -96,13 +95,25 @@ fun downloadAll(cookiesFile: Path?, bandcampUser: String, downloadFormat: String val artistFolderPath = Paths.get("$downloadFolder").resolve(artist) val albumFolderPath = artistFolderPath.resolve(albumFolderName) - downloadAlbum(artistFolderPath, albumFolderPath, albumtitle, url, cookies, gson, isSingleTrack, artid) - + // Download album, with as many retries as configured + val attempts = retries + 1 + for (i in 1..attempts) { + if (i > 1) { + println("Retrying download (${i - 1}/$retries).") + } + try { + downloadAlbum(artistFolderPath, albumFolderPath, albumtitle, url, cookies, gson, isSingleTrack, artid) + } catch (e: Throwable) { + println("""Error while downloading: "${e.javaClass.name}: ${e.message}".""") + if (i == attempts) { + throw BandCampDownloaderError("Could not download album after $retries retries.") + } + } + } } } - class BandCampDownloaderError(s: String) : Exception(s) fun downloadAlbum(artistFolderPath: Path?, albumFolderPath: Path, albumtitle: String, url: String, cookies: Map, gson: Gson, isSingleTrack: Boolean, artid: String) { diff --git a/src/main/kotlin/bandcampcollectiondownloader/Main.kt b/src/main/kotlin/bandcampcollectiondownloader/Main.kt index edacf69..f08f741 100644 --- a/src/main/kotlin/bandcampcollectiondownloader/Main.kt +++ b/src/main/kotlin/bandcampcollectiondownloader/Main.kt @@ -23,7 +23,10 @@ data class Args( var pathToDownloadFolder: Path = Paths.get("."), @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["Display this help message."]) - var help: Boolean = false + var help: Boolean = false, + + @CommandLine.Option(names = ["-r", "--retries"], usageHelp = false, description = ["Amount of retries when downloading an album."]) + var retries: Int = 3 ) @@ -31,7 +34,7 @@ data class Args( fun main(args: Array) { // Parsing args - System.setProperty("picocli.usage.width", "120") + System.setProperty("picocli.usage.width", "130") val parsedArgs: Args = try { CommandLine.populateCommand(Args(), *args) @@ -55,8 +58,9 @@ fun main(args: Array) { val cookiesFile = parsedArgs.pathToCookiesFile val downloadFormat = parsedArgs.audioFormat val downloadFolder = parsedArgs.pathToDownloadFolder + val retries = parsedArgs.retries try { - downloadAll(cookiesFile, bandcampUser, downloadFormat, downloadFolder) + downloadAll(cookiesFile, bandcampUser, downloadFormat, downloadFolder, retries) } catch (e: BandCampDownloaderError) { System.err.println("ERROR: ${e.message}") } diff --git a/src/test/kotlin/bandcampcollectiodownloader/test/BandcampCollectionDownloaderTests.kt b/src/test/kotlin/bandcampcollectiodownloader/test/BandcampCollectionDownloaderTests.kt index f61ac92..89b3be0 100644 --- a/src/test/kotlin/bandcampcollectiodownloader/test/BandcampCollectionDownloaderTests.kt +++ b/src/test/kotlin/bandcampcollectiodownloader/test/BandcampCollectionDownloaderTests.kt @@ -16,42 +16,42 @@ class BandcampCollectionDownloaderTests { @Test fun testErrorCookiesFileNotFound() { assertThrows { - downloadAll(Paths.get("bli"), "bli", "bli", Paths.get("bli")) + downloadAll(Paths.get("bli"), "bli", "bli", Paths.get("bli"), 0) } } @Test fun testErrorCookiesFileInvalidJson() { assertThrows { - downloadAll(Paths.get("./test-data/notjsoncookies.json"), "bli", "bli", Paths.get("bli")) + downloadAll(Paths.get("./test-data/notjsoncookies.json"), "bli", "bli", Paths.get("bli"), 0) } } @Test fun testErrorCookiesFileInvalidContent_wrongkey() { assertThrows { - downloadAll(Paths.get("./test-data/invalidcookies_wrongkeys.json"), "bli", "bli", Paths.get("bli")) + downloadAll(Paths.get("./test-data/invalidcookies_wrongkeys.json"), "bli", "bli", Paths.get("bli"), 0) } } @Test fun testErrorCookiesFileInvalidContent_noarray() { assertThrows { - downloadAll(Paths.get("./test-data/invalidcookies_noarray.json"), "bli", "bli", Paths.get("bli")) + downloadAll(Paths.get("./test-data/invalidcookies_noarray.json"), "bli", "bli", Paths.get("bli"), 0) } } @Test fun testErrorInvalidBandcampUser() { assertThrows { - downloadAll(Paths.get("./test-data/wellformedcookies.json"), "zerz1e3687dfs3df7", "bli", Paths.get("bli")) + downloadAll(Paths.get("./test-data/wellformedcookies.json"), "zerz1e3687dfs3df7", "bli", Paths.get("bli"), 0) } } @Test fun testErrorCookiesUselessForBandcampUser() { assertThrows { - downloadAll(Paths.get("./test-data/wellformedcookies.json"), "bli", "bli", Paths.get("bli")) + downloadAll(Paths.get("./test-data/wellformedcookies.json"), "bli", "bli", Paths.get("bli"), 0) } } @@ -59,7 +59,7 @@ class BandcampCollectionDownloaderTests { fun testErrorNoCookiesAtAll() { addToEnv("HOME", "NOPE") assertThrows { - downloadAll(null, "bli", "bli", Paths.get("bli")) + downloadAll(null, "bli", "bli", Paths.get("bli"), 0) } }