Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added custom download folders to Android #570

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,9 @@ class BookMetadata(
title:String,
var subtitle:String?,
var authors:MutableList<Author>?,
var series:MutableList<Series>?,
var narrators:MutableList<String>?,
var genres:MutableList<String>,
var genres:MutableList<String>?,
var publishedYear:String?,
var publishedDate:String?,
var publisher:String?,
Expand All @@ -217,6 +218,54 @@ class BookMetadata(
) : MediaTypeMetadata(title) {
@JsonIgnore
override fun getAuthorDisplayName():String { return authorName ?: "Unknown" }
@JsonIgnore
fun getSubtitleDisplay():String { return subtitle ?: "Unknown" }
@JsonIgnore
fun validateSeries(index: Int):Int {
return if(series.isNullOrEmpty() || index > series!!.size - 1 || index < 0) {
-1
}
else{
index
}
}
@JsonIgnore
open fun getSeriesNameDisplay():String { return getSeriesNameDisplay(0)}
@JsonIgnore
open fun getSeriesNameDisplay(index: Int):String { if(validateSeries(index) >=0 ) {return series!![index].name } else {return "Unknown"} }
@JsonIgnore
open fun getSeriesSequenceDisplay():String { return getSeriesSequenceDisplay(0)}
@JsonIgnore
open fun getSeriesSequenceDisplay(index: Int):String {
if(validateSeries(index) >=0 ) {
return series!![index].sequence ?: "X"
} else {return "X"}
}
@JsonIgnore
open fun getNarratorDisplayName():String { return narratorName ?: "Unknown" }
@JsonIgnore
open fun getGenresDisplayName():String {
if(genres.isNullOrEmpty() || genres!!.size == 0) return "Unknown"
return genres!!.joinToString(", ")
}
@JsonIgnore
open fun getPublishedYearDisplay():String { return publishedYear ?: "Unknown" }
@JsonIgnore
open fun getPublishedDateDisplay():String { return publishedDate ?: "Unknown" }
@JsonIgnore
open fun getPublisherDisplay():String { return publisher ?: "Unknown" }
@JsonIgnore
open fun getISBNDisplay():String { return isbn ?: "Unknown" }
@JsonIgnore
open fun getASINDisplay():String { return asin ?: "Unknown" }
@JsonIgnore
open fun getLanguageDisplay():String { return language ?: "Unknown" }
@JsonIgnore
open fun getExplicitDisplay():String { return if(explicit) "Explicit" else "Not Explicit" }
@JsonIgnore
open fun getNarratorNameDisplay():String { return narratorName ?: "Unknown" }
@JsonIgnore
open fun getSeriesSummary():String { return seriesName ?: "Unknown" }
}

@JsonIgnoreProperties(ignoreUnknown = true)
Expand All @@ -237,6 +286,13 @@ data class Author(
var coverPath:String?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Series(
var id:String,
var name:String,
var sequence:String?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class PodcastEpisode(
var id:String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ data class DeviceSettings(
var autoSleepTimerEndTime: String,
var sleepTimerLength: Long, // Time in milliseconds
var disableSleepTimerFadeOut: Boolean,
var disableSleepTimerResetFeedback: Boolean
var disableSleepTimerResetFeedback: Boolean,
var showAdvancedSettings: Boolean,
var localPathFormat: String
) {
companion object {
// Static method to get default device settings
Expand All @@ -123,7 +125,9 @@ data class DeviceSettings(
autoSleepTimerEndTime = "06:00",
sleepTimerLength = 900000L, // 15 minutes
disableSleepTimerFadeOut = false,
disableSleepTimerResetFeedback = false
disableSleepTimerResetFeedback = false,
showAdvancedSettings = false,
localPathFormat = "\$bookAuthor/\$bookTitle"
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ data class LocalMediaItem(
@JsonIgnore
fun getMediaMetadata():MediaTypeMetadata {
return if (mediaType == "book") {
BookMetadata(name,null, mutableListOf(), mutableListOf(), mutableListOf(),null,null,null,null,null,null,null,false,null,null,null,null)
BookMetadata(name,null, mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(),null,null,null,null,null,null,null,false,null,null,null,null)
} else {
PodcastMetadata(name,null,null, mutableListOf())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,20 +127,104 @@ class AbsDownloader : Plugin() {
}
return newTitle
}
private fun parseTemplateString(userTemplate: String?, libraryItem: LibraryItem):String{
val template = userTemplate ?: "\$bookAuthor/\$bookTitle"
val bookTitle = cleanStringForFileSystem(libraryItem.media.metadata.title)
val bookAuthor = cleanStringForFileSystem(libraryItem.media.metadata.getAuthorDisplayName())
val subtitle = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getSubtitleDisplay())
//series
val narrator = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getNarratorDisplayName())
val genres = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getGenresDisplayName())
val publishedYear = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getPublishedYearDisplay())
val publishedDate = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getPublishedDateDisplay())
val publisher = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getPublisherDisplay())
val isbn = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getISBNDisplay())
val asin = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getASINDisplay())
val language = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getLanguageDisplay())
val explicit = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getExplicitDisplay())
val seriesSummary = cleanStringForFileSystem((libraryItem.media.metadata as BookMetadata).getSeriesSummary())
var output = template
while(output.contains("\$bookTitle")) output = output.replace("\$bookTitle", bookTitle)
while(output.contains("\$bookAuthor")) output = output.replace("\$bookAuthor", bookAuthor)
while(output.contains("\$subtitle")) output = output.replace("\$subtitle", subtitle)
while(output.contains("\$narrator")) output = output.replace("\$narrator", narrator)
while(output.contains("\$genres")) output = output.replace("\$genres", genres)
while(output.contains("\$publishedYear")) output = output.replace("\$publishedYear", publishedYear)
while(output.contains("\$publishedDate")) output = output.replace("\$publishedDate", publishedDate)
while(output.contains("\$publisher")) output = output.replace("\$publisher", publisher)
while(output.contains("\$isbn")) output = output.replace("\$isbn", isbn)
while(output.contains("\$asin")) output = output.replace("\$asin", asin)
while(output.contains("\$language")) output = output.replace("\$language", language)
while(output.contains("\$explicit")) output = output.replace("\$explicit", explicit)
while(output.contains("\$seriesSummary")) output = output.replace("\$seriesSummary", seriesSummary)

val seriesNameRegex = "\\\$seriesName\\[\\d+\\]".toRegex()
while(seriesNameRegex.find(output) != null) {
val subString = seriesNameRegex.find(output)!!.value
if(subString != null){
val digitStr = subString.replace("\$seriesName[","").replace("]","")
val digit = digitStr.toInt()
val replacementText = (libraryItem.media.metadata as BookMetadata).getSeriesNameDisplay(digit)
output = output.replace(subString, replacementText)
}
}
while(output.contains("\$seriesName")) output = output.replace("\$seriesName", (libraryItem.media.metadata as BookMetadata).getSeriesNameDisplay(0))

val seriesSeqRegex = "\\\$seriesSequence\\[\\d+\\]".toRegex()
while(seriesSeqRegex.find(output) != null) {
val subString = seriesSeqRegex.find(output)!!.value
if(subString != null){
val digitStr = subString.replace("\$seriesSequence[","").replace("]","")
val digit = digitStr.toInt()
val replacementText = (libraryItem.media.metadata as BookMetadata).getSeriesSequenceDisplay(digit)
output = output.replace(subString, replacementText)
}
}
while(output.contains("\$seriesSequence")) output = output.replace("\$seriesSequence", (libraryItem.media.metadata as BookMetadata).getSeriesSequenceDisplay(0))

return output
}
private fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder, episode:PodcastEpisode?) {
val tempFolderPath = mainActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)

Log.d(tag, "downloadCacheDirectory=$tempFolderPath")

if (libraryItem.mediaType == "book") {
val bookTitle = cleanStringForFileSystem(libraryItem.media.metadata.title)
val bookAuthor = cleanStringForFileSystem(libraryItem.media.metadata.getAuthorDisplayName())

val itemSubfolder = if(DeviceManager.deviceData.deviceSettings != null) {
parseTemplateString(DeviceManager.deviceData.deviceSettings!!.localPathFormat, libraryItem)
} else{
parseTemplateString(null, libraryItem)
}
/*
val testTemplate = "bookAuthor = \$bookAuthor\n" +
"bookTitle = \$bookTitle\n" +
"explicit = \$explicit\n" +
"subtitle = \$subtitle\n" +
"narrator = \$narrator\n" +
"genres = \$genres\n" +
"publishedYear = \$publishedYear\n" +
"publishedDate = \$publishedDate\n" +
"publisher = \$publisher\n" +
"isbn = \$isbn\n" +
"asin = \$asin\n" +
"language = \$language\n" +
"seriesSummary = \$seriesSummary\n" +
"seriesName = \$seriesName\n" +
"seriesName[0] = \$seriesName[0]\n" +
"seriesName[100] = \$seriesName[100]\n" +
"seriesSequence = \$seriesSequence\n" +
"seriesSequence[0] = \$seriesSequence[0]\n" +
"seriesSequence[-1] = \$seriesSequence[-1]\n" +
"seriesSequence[10] = \$seriesSequence[10]\n" +
"seriesSequence[100] = \$seriesSequence[100]\n" +
"seriesSequence[1000] = \$seriesSequence[1000]\n" +
"output = \$output"
val parsedTemplate = parseTemplateString(testTemplate, libraryItem)
*/
val tracks = libraryItem.media.getAudioTracks()
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
val itemSubfolder = "$bookAuthor/$bookTitle"
val itemFolderPath = "${localFolder.absolutePath}/$itemSubfolder"

val bookTitle = cleanStringForFileSystem(libraryItem.media.metadata.title)
val downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null, libraryItem.userMediaProgress,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, itemSubfolder, libraryItem.media, mutableListOf())

// Create download item part for each audio track
Expand Down
60 changes: 59 additions & 1 deletion pages/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,23 @@
</div>
</div>

<!-- Advanced Settings -->

<template v-if="!isiOS">
<p class="uppercase text-xs font-semibold text-gray-300 mb-2 mt-6">Advanced Settings</p>
<div class="flex items-center py-3" @click="toggleAdvancedSettings">
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.showAdvancedSettings" @input="saveSettings" />
</div>
<p class="pl-4">Show Advanced Settings</p>
<span class="material-icons-outlined ml-2" @click.stop="showInfo('showAdvancedSettings')">info</span>
</div>
<div v-if="settings.showAdvancedSettings" class="py-3 flex items-center">
<ui-text-input-with-label type="text" @input="saveSettingsWithoutInit" v-model="settings.localPathFormat" label="Local Path Format" class="my-2" />
<span class="material-icons-outlined ml-2" @click.stop="showInfo('localPathFormat')">info</span>
</div>
</template>

<modals-dialog v-model="showMoreMenuDialog" :items="moreMenuItems" @action="clickMenuAction" />
<modals-sleep-timer-length-modal v-model="showSleepTimerLengthModal" @change="sleepTimerLengthModalSelection" />
</div>
Expand Down Expand Up @@ -125,7 +142,9 @@ export default {
autoSleepTimerEndTime: '06:00',
sleepTimerLength: 900000, // 15 minutes
disableSleepTimerFadeOut: false,
disableSleepTimerResetFeedback: false
disableSleepTimerResetFeedback: false,
showAdvancedSettings: false,
localPathFormat: '$bookAuthor/$bookTitle'
},
lockCurrentOrientation: false,
settingInfo: {
Expand All @@ -144,6 +163,30 @@ export default {
disableSleepTimerResetFeedback: {
name: 'Disable vibrate on reset',
message: 'When the sleep timer gets reset your device will vibrate. Enable this setting to not vibrate when the sleep timer resets.'
},
showAdvancedSettings: {
name: 'Show Advanced Settings',
message: 'Warning: changing these settings could damage your phone. Do so carefully and only if you have knowledge to do so.'
},
localPathFormat: {
name: 'Customize subfolder for download',
message: 'the "/" character is a folder break\n\n'+
'$bookAuthor = Susan Dennard\n'+
'$bookTitle = Witchshadow\n'+
'$explicit = Not Explicit\n'+
'$subtitle = Witchlands Novel\n'+
'$narrator = Cassandra Campbell\n'+
'$genres = Young Adult, Fantasy\n'+
'$publishedYear = 2021\n'+
'$publishedDate = Unknown\n'+
'$publisher = Listening Library\n'+
'$isbn = 9781466867352\n'+
'$asin = 0147523885\n'+
'$language = English\n'+
'$explicit = Not Explicit\n'+
'$seriesSummary = The Witchlands #4\n'+
'$seriesName[0] = The Witchlands\n'+
'$seriesSequence[0] = 4\n'
}
},
hapticFeedbackItems: [
Expand Down Expand Up @@ -303,6 +346,10 @@ export default {
this.settings.disableAutoRewind = !this.settings.disableAutoRewind
this.saveSettings()
},
toggleAdvancedSettings() {
this.settings.showAdvancedSettings = !this.settings.showAdvancedSettings
this.saveSettings()
},
toggleEnableAltView() {
this.settings.enableAltView = !this.settings.enableAltView
this.saveSettings()
Expand Down Expand Up @@ -335,6 +382,13 @@ export default {
this.settings.jumpBackwardsTime = this.jumpBackwardsItems[next].value
this.saveSettings()
},
async saveSettingsWithoutInit(){
const updatedDeviceData = await this.$db.updateDeviceSettings({ ...this.settings })
if (updatedDeviceData) {
this.$store.commit('setDeviceData', updatedDeviceData)
}

},
async saveSettings() {
await this.$hapticsImpact()
const updatedDeviceData = await this.$db.updateDeviceSettings({ ...this.settings })
Expand Down Expand Up @@ -364,6 +418,10 @@ export default {
this.settings.sleepTimerLength = !isNaN(deviceSettings.sleepTimerLength) ? deviceSettings.sleepTimerLength : 900000 // 15 minutes
this.settings.disableSleepTimerFadeOut = !!deviceSettings.disableSleepTimerFadeOut
this.settings.disableSleepTimerResetFeedback = !!deviceSettings.disableSleepTimerResetFeedback

this.settings.showAdvancedSettings = !!deviceSettings.showAdvancedSettings

this.settings.localPathFormat = deviceSettings.localPathFormat.replace("../","") || '$bookAuthor/$bookTitle'
}
},
mounted() {
Expand Down