From 561bd55d07e644123b9d2b11c171f4228c81ebb7 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 2 Feb 2024 14:11:32 -0500 Subject: [PATCH] Import custom localforage which maps to cordova file api MarmadileManteater/FreeTubeCordova#152 --- _scripts/webpack.cordova.config.js | 5 +- src/cordova/localforage.js | 20 +++++ src/renderer/helpers/cordova.js | 126 +++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/cordova/localforage.js create mode 100644 src/renderer/helpers/cordova.js diff --git a/_scripts/webpack.cordova.config.js b/_scripts/webpack.cordova.config.js index d667bcc97375a..234aecfea3042 100644 --- a/_scripts/webpack.cordova.config.js +++ b/_scripts/webpack.cordova.config.js @@ -131,7 +131,7 @@ const config = { }), new webpack.ProvidePlugin({ process: 'process/browser', - Buffer: ['buffer', 'Buffer'], + Buffer: ['buffer', 'Buffer'] }), new HtmlWebpackPlugin({ excludeChunks: ['processTaskWorker'], @@ -154,7 +154,8 @@ const config = { // video.js's mpd-parser uses @xmldom/xmldom so that it can support both node and web browsers // As FreeTube only runs in electron and web browsers, we can use the native DOMParser class, instead of the "polyfill" // https://caniuse.com/mdn-api_domparser - '@xmldom/xmldom$': path.resolve(__dirname, '_domParser.js') + '@xmldom/xmldom$': path.resolve(__dirname, '_domParser.js'), + 'localforage': path.resolve(__dirname, "../src/cordova/localforage.js") }, fallback: { 'fs/promises': path.resolve(__dirname, '_empty.js'), diff --git a/src/cordova/localforage.js b/src/cordova/localforage.js new file mode 100644 index 0000000000000..bd80bcd5015bb --- /dev/null +++ b/src/cordova/localforage.js @@ -0,0 +1,20 @@ +import localforage from '../../node_modules/localforage/dist/localforage' +import { requestDirectory, readFromFile, writeToFile } from '../renderer/helpers/cordova' + +export function createInstance(kwargs) { + const instance = localforage.createInstance(kwargs) + return { + async getItem(key) { + const fs = await requestDirectory() + const data = await readFromFile(fs, key) + // if the data is empty, fallback to localstorage + if (data === '') return instance.getItem(key) + // if not, return the data + return data + }, + async setItem(key, value) { + const fs = await requestDirectory() + await writeToFile(fs, key, value) + } + } +} diff --git a/src/renderer/helpers/cordova.js b/src/renderer/helpers/cordova.js new file mode 100644 index 0000000000000..156b77f035fe5 --- /dev/null +++ b/src/renderer/helpers/cordova.js @@ -0,0 +1,126 @@ +import cordova from 'cordova' + +/** + * @typedef FileOptions + * @property {boolean} create + * @property {boolean} exclusive + */ + +/** + * @typedef Writer + * @property {Function} onerror + * @property {Function} write + */ + +/** + * @callback CreateWriterCallback + * @param {Writer} writer + * @returns {void} + */ + +/** + * @callback CreateWriter + * @param {CreateWriterCallback} callback + * @returns {void} + */ + +/** + * @callback GetBlobCallback + * @param {Blob} blob + * @returns {void} + */ + +/** + * @callback GetBlob + * @param {GetBlobCallback} callback + */ + +/** + * @typedef FileReference + * @property {CreateWriter} createWriter + * @property {GetBlob} file + */ + +/** + * @callback GetFileCallback + * @param {FileReference} file + */ + +/** + * @callback GetFile + * @param {string} patch + * @param {FileOptions} options + * @param {GetFileCallback} callback + * @returns {void} + */ + +/** + * @typedef FileSystemHook + * @property {GetFile} getFile + */ + +/** + * + * @param {string} directory this *must* be one of the accepted cordova file keys "cordova.file.*"" + * @returns {Promise} a filesystem hook that starts in the given directory + */ +export function requestDirectory(directory = cordova.file.externalApplicationStorageDirectory) { + return new Promise((resolve, _reject) => { + window.requestFileSystem(window.LocalFileSystem.PERSISTENT, 10000, function (_) { + window.resolveLocalFileSystemURL(directory, resolve) + }) + }) +} + +/** + * + * @param {FileSystemHook} fsHook + * @param {string} path + * @param {string} content + * @param {FileOptions} options + * @returns {Promise} + */ +export function writeToFile(fsHook, path, content, options = { create: true, exclusive: false }) { + return new Promise((resolve, reject) => { + fsHook.getFile(path, options, (file) => { + file.createWriter((writer) => { + writer.onerror = reject + writer.write(content) + resolve() + }) + }) + }) +} + +/** + * + * @param {FileSystemHook} fsHook + * @param {string} path + * @param {string} content + * @param {FileOptions} options + * @returns {Promise} + */ +export function readFromFile(fsHook, path) { + return new Promise((resolve, reject) => { + fsHook.getFile(path, { create: true, exclusive: false }, (fileEntry) => { + fileEntry.file((file) => { + const reader = new FileReader() + reader.onloadend = () => resolve(reader.result) + reader.onerror = (e) => reject(e) + reader.readAsText(file) + }) + }) + }) +} + +export function removeFile(fsHook, path) { + return new Promise((resolve, reject) => { + fsHook.remove(() => { + resolve() + }, (error) => { + reject(error) + }, () => { + reject(new Error('File does not exist')) + }) + }) +}