Skip to content

Commit

Permalink
Add support for moving the directory where db files are stored
Browse files Browse the repository at this point in the history
  • Loading branch information
MarmadileManteater committed Mar 4, 2024
1 parent d5cb978 commit 86ceb09
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 7 deletions.
17 changes: 16 additions & 1 deletion _scripts/_localforage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,26 @@ export function createInstance(kwargs) {
const instance = localforage.createInstance(kwargs)
return {
async getItem(key) {
const dataLocationFile = android.readFile('data://', 'data-location.json')
if (dataLocationFile !== '') {
const locationInfo = JSON.parse(dataLocationFile)
const locationMap = Object.fromEntries(locationInfo.files.map((file) => { return [file.fileName, file.uri] }))
if (key in locationMap) {
return android.readFile(locationMap[key], '')
}
}
const data = android.readFile("data://", key)
if (data === '') return instance.getItem(key)
return data
},
async setItem(key, value) {
const dataLocationFile = android.readFile('data://', 'data-location.json')
if (dataLocationFile !== '') {
const locationInfo = JSON.parse(dataLocationFile)
const locationMap = Object.fromEntries(locationInfo.files.map((file) => { return [file.fileName, file.uri] }))
if (key in locationMap) {
return android.writeFile(locationMap[key], '', value)
}
}
android.writeFile("data://", key, value)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ import android.webkit.JavascriptInterface
import androidx.activity.result.ActivityResult
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationManagerCompat
import androidx.documentfile.provider.DocumentFile
import java.io.File
import java.io.FileInputStream
import java.net.URL
import java.net.URLEncoder
import java.util.UUID.*


class FreeTubeJavaScriptInterface {
private var context: MainActivity
private var mediaSession: MediaSession?
Expand Down Expand Up @@ -453,6 +455,45 @@ class FreeTubeJavaScriptInterface {
return promise
}

@JavascriptInterface
fun requestDirectoryAccessDialog(): String {
val promise = jsPromise()
val openDialogIntent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
context.listenForActivityResults {
result: ActivityResult? ->
if (result!!.resultCode == Activity.RESULT_CANCELED) {
resolve(promise, "USER_CANCELED")
}
try {
val uri = result!!.data!!.data!!
context.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
resolve(promise, URLEncoder.encode(uri.toString(), "utf-8"))
} catch (ex: Exception) {
reject(promise, ex.toString())
}
}
context.activityResultLauncher.launch(openDialogIntent)
return promise
}

@JavascriptInterface
fun listFilesInTree(tree: String): String {
val directory = DocumentFile.fromTreeUri(context, Uri.parse(tree))
val files = directory!!.listFiles().joinToString(",") { file ->
"{ \"uri\": \"${file.uri}\", \"fileName\": \"${file.name}\" }"
}
return "[$files]"
}

@JavascriptInterface
fun createFileInTree(tree: String, fileName: String): String {
val directory = DocumentFile.fromTreeUri(context, Uri.parse(tree))
return directory!!.createFile("*/*", fileName)!!.uri.toString()
}

/**
* hides the splashscreen
*/
Expand Down
59 changes: 58 additions & 1 deletion src/renderer/components/data-settings/data-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '../../helpers/utils'
import { invidiousAPICall } from '../../helpers/api/invidious'
import { getLocalChannel } from '../../helpers/api/local'
import { handleAmbigiousContent } from '../../helpers/android'
import { handleAmbigiousContent, initalizeDatabasesInDirectory, readFile, requestDirectory, writeFile } from '../../helpers/android'

export default defineComponent({
name: 'DataSettings',
Expand Down Expand Up @@ -76,6 +76,9 @@ export default defineComponent({
},
primaryProfile: function () {
return deepCopy(this.profileList[0])
},
usingAndroid: function () {
return process.env.IS_ANDROID
}
},
methods: {
Expand All @@ -85,6 +88,60 @@ export default defineComponent({
})
},

resetDataDirectory: async function () {
try {
const locationData = readFile('data://', 'data-location.json')
let locationInfo = { directory: 'data://', files: [] }
let locationMap = []
if (locationData !== '') {
locationInfo = JSON.parse(locationData)
locationMap = locationInfo.files.map((file) => { return [file.fileName, file.uri] })
}
if (locationMap.length !== 0) {
for (const [key, value] of locationMap) {
writeFile('data://', key, readFile(value))
}
// clear out data-location.json
writeFile('data://', 'data-location.json', '')
showToast(this.$t('Data Settings.Your data directory has been moved successfully'))
} else {
showToast(this.$t('Data Settings.Nothing to change'))
}
} catch (exception) {
showToast(exception)
}
},

selectDataDirectory: async function () {
try {
const directory = await requestDirectory()
const files = await initalizeDatabasesInDirectory(directory)
if (files.length > 0) {
const locationData = readFile('data://', 'data-location.json')
let locationInfo = { directory: 'data://', files: [] }
let locationMap = {}
if (locationData !== '') {
locationInfo = JSON.parse(locationData)
locationMap = Object.fromEntries(locationInfo.files.map((file) => { return [file.fileName, file.uri] }))
}
for (let i = 0; i < files.length; i++) {
const data = locationInfo.files.length === 0
? readFile('data://', files[i].fileName)
: readFile(locationMap[files[i].fileName], '')
writeFile(files[i].uri, '', data)
}
// update the data files
writeFile('data://', 'data-location.json', JSON.stringify({
directory: directory.uri,
files
}))
showToast(this.$t('Data Settings.Your data directory has been moved successfully'))
}
} catch (exception) {
showToast(this.$t('Data Settings.Error moving data directory'))
}
},

importSubscriptions: async function () {
const options = {
properties: ['openFile'],
Expand Down
15 changes: 15 additions & 0 deletions src/renderer/components/data-settings/data-settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
<ft-settings-section
:title="$t('Settings.Data Settings.Data Settings')"
>
<template v-if="usingAndroid">
<h4 class="groupTitle">
{{ $t('Data Settings.Data Directory') }}
</h4>
<ft-flex-box class="dataSettingsBox">
<ft-button
:label="$t('Data Settings.Select Data Directory')"
@click="selectDataDirectory"
/>
<ft-button
:label="$t('Data Settings.Reset Data Directory')"
@click="resetDataDirectory"
/>
</ft-flex-box>
</template>
<h4 class="groupTitle">
{{ $t('Subscriptions.Subscriptions') }}
</h4>
Expand Down
10 changes: 5 additions & 5 deletions src/renderer/components/ft-video-player/ft-video-player.js
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,6 @@ export default defineComponent({

this.stopPowerSaveBlocker()
})

this.player.on(this.statsModalEventName, () => {
if (this.showStatsModal) {
this.statsModal.open()
Expand Down Expand Up @@ -991,10 +990,11 @@ export default defineComponent({
this.player.one('canplay', () => {
this.player.currentTime(currentTime)
this.player.playbackRate(playbackRate)

// need to call play to restore the player state, even if we want to pause afterwards
this.playVideo(() => {
if (isPaused) { this.player.pause() }
this.$refs.video.addEventListener('loadeddata', () => {
// need to call play to restore the player state, even if we want to pause afterwards
this.playVideo(() => {
if (isPaused) { this.player.pause() }
})
})
})

Expand Down
71 changes: 71 additions & 0 deletions src/renderer/helpers/android.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,74 @@ export function handleAmbigiousContent(content, filePath) {
}
return filePath
}

/**
* @typedef AndroidFile
* @property {string} uri
* @property {string} fileName
*/

/**
* @callback ListFiles
* @returns {Array<AndroidFile>}
*/

/**
* @typedef DirectoryHandle
* @property {boolean} canceled
* @property {string?} uri
* @property {Function} createFile
* @property {ListFiles} listFiles
*/

/**
*
* @returns {Promise<DirectoryHandle>}
*/
export async function requestDirectory() {
const uri = await window.awaitAsyncResult(android.requestDirectoryAccessDialog())
if (uri === 'USER_CANCELED') {
return {
canceled: true
}
} else {
return {
uri,
canceled: false,
createFile(fileName) {
return android.createFileInTree(uri, fileName)
},
listFiles() {
return JSON.parse(android.listFilesInTree(uri))
}
}
}
}

export const EXPECTED_FILES = ['profiles.db', 'settings.db', 'history.db', 'playlists.db']

/**
*
* @param {DirectoryHandle} directoryHandle
*/
export async function initalizeDatabasesInDirectory(directoryHandle) {
if (directoryHandle.canceled) {
return []
}
const files = directoryHandle.listFiles()
const filteredFiles = files.filter(({ fileName }) => EXPECTED_FILES.indexOf(fileName) !== -1)
const filteredFileNames = filteredFiles.map((item) => item.fileName)
if (filteredFiles.length === EXPECTED_FILES.length) {
// no changes necessary
} else {
const neededFiles = EXPECTED_FILES.filter((fileName) => filteredFileNames.indexOf(fileName) === -1)
for (const file of neededFiles) {
const fileData = {
uri: directoryHandle.createFile(file),
fileName: file
}
filteredFiles.push(fileData)
}
}
return filteredFiles
}
7 changes: 7 additions & 0 deletions static/locales-android/en-US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ Player Settings:
Use Old Layout For Info: Use old layout for watch video info
Distraction Free Settings:
Hide Thumbnail In Media Controls: Hide thumbnail in media controls notification
Data Settings:
Data Directory: Data Directory
Select Data Directory: Select Data Directory
Reset Data Directory: Reset Data Directory
Your data directory has been moved successfully: Your data directory has been moved successfully
Error moving data directory: Error occured while moving data directory
Nothing to change: Nothing to change

0 comments on commit 86ceb09

Please sign in to comment.