window.App = window.App || {}; App.hottubBackup = App.hottubBackup || {}; // Reads a Hot Tub app backup (an exported SQLite database) and turns the videos // it has flagged as favorites into this client's favorites. // // The app and this client don't agree on identity: the app keys a video by a // hash it computes locally, while the server -- and so this client -- keys it by // something like "reddit-1rdudss". So an imported favorite is matched to an // existing one by URL, and carries no id of its own; see App.favorites.mergeImported. (function() { // The app stores a comma-separated set here ("favorite", "recent", ...). // It also keeps a `favoriteDate` on rows it no longer flags -- a leftover // from unfavoriting -- so the flag, not the date, is what counts. const FAVORITE_FLAG = 'favorite'; // Only what a favorite needs. Skipping the rest matters: `allFormats` alone // is kilobytes of resolved-format JSON per row, and it is exactly the kind // of thing this client must not store -- those URLs are signed and expire // (see App.favorites.normalize). const COLUMNS = ['url', 'title', 'thumb', 'duration', 'uploader', 'flags', 'favoriteDate']; const hasFavoriteFlag = function(flags) { if (!flags) return false; return String(flags).split(',').some((flag) => flag.trim().toLowerCase() === FAVORITE_FLAG); }; // Newest first, matching how favorites are ordered when added by hand. const byNewest = function(a, b) { return String(b.favoriteDate || '').localeCompare(String(a.favoriteDate || '')); }; App.hottubBackup.readFavorites = function(buffer) { const db = App.sqlite.open(buffer); if (db.tableNames().indexOf('video_details') < 0) { throw new Error('This database has no video_details table -- is it a Hot Tub backup?'); } const rows = db.readTable('video_details', { columns: COLUMNS }); return rows .filter((row) => row.url && hasFavoriteFlag(row.flags)) .sort(byNewest) .map((row) => ({ // No id: the app's own is meaningless to this client, and the // URL is what both sides agree on. key: row.url, id: null, url: row.url, title: row.title || '', thumb: row.thumb || '', channel: '', uploader: row.uploader || '', duration: Number(row.duration) || 0, isLive: false })); }; App.hottubBackup.readFile = function(file) { return file.arrayBuffer().then((buffer) => App.hottubBackup.readFavorites(buffer)); }; // Reads the file and merges what it finds. Resolves to the merge summary // ({found, added, skipped, total}) so the caller can report it. App.hottubBackup.importFile = function(file) { return App.hottubBackup.readFile(file).then((entries) => { const result = App.favorites.mergeImported(entries); return Object.assign({ found: entries.length }, result); }); }; })();