diff --git a/src/dash.js b/src/dash.js index 84a9a4ef..4e715412 100644 --- a/src/dash.js +++ b/src/dash.js @@ -1,4 +1,3 @@ -const { Gio, GLib } = imports.gi; const Debug = imports.src.debug; const FileOps = imports.src.fileOps; const Misc = imports.src.misc; @@ -50,32 +49,6 @@ function generateDash(info) return dash; } -function saveDashPromise(dash) -{ - debug('saving dash file'); - - return new Promise(async (resolve, reject) => { - const tempDir = await FileOps.createTempDirPromise().catch(debug); - if(!tempDir) - return reject(new Error('could not create folder in temp directory')); - - const dashFile = tempDir.get_child('.clapper.mpd'); - - dashFile.replace_contents_bytes_async( - GLib.Bytes.new_take(dash), - null, - false, - Gio.FileCreateFlags.NONE, - null - ) - .then(() => { - debug('saved dash file'); - resolve(dashFile.get_uri()); - }) - .catch(err => reject(err)); - }); -} - function _addAdaptationSet(streamsArr) { const mimeInfo = _getMimeInfo(streamsArr[0].mimeType); diff --git a/src/fileOps.js b/src/fileOps.js index 7d3dd9bb..6233cd6b 100644 --- a/src/fileOps.js +++ b/src/fileOps.js @@ -4,7 +4,9 @@ const Misc = imports.src.misc; const { debug } = Debug; +Gio._promisify(Gio._LocalFilePrototype, 'load_bytes_async', 'load_bytes_finish'); Gio._promisify(Gio._LocalFilePrototype, 'make_directory_async', 'make_directory_finish'); +Gio._promisify(Gio._LocalFilePrototype, 'replace_contents_bytes_async', 'replace_contents_finish'); function createCacheDirPromise() { @@ -24,6 +26,7 @@ function createTempDirPromise() return createDirPromise(dir); } +/* Creates dir and resolves with it */ function createDirPromise(dir) { return new Promise((resolve, reject) => { @@ -32,7 +35,7 @@ function createDirPromise(dir) dir.make_directory_async( GLib.PRIORITY_DEFAULT, - null, + null ) .then(success => { if(success) @@ -43,3 +46,76 @@ function createDirPromise(dir) .catch(err => reject(err)); }); } + +/* Saves file in optional subdirectory and resolves with it */ +function saveFilePromise(place, subdirName, fileName, data) +{ + return new Promise(async (resolve, reject) => { + let destPath = GLib[`get_${place}_dir`]() + '/' + Misc.appId; + + if(subdirName) + destPath += `/${subdirName}`; + + const destDir = Gio.File.new_for_path(destPath); + debug(`saving file: ${destPath}`); + + const checkFolders = (subdirName) + ? [destDir.get_parent(), destDir] + : [destDir]; + + for(let dir of checkFolders) { + const createdDir = await createDirPromise(dir).catch(debug); + if(!createdDir) + return reject(new Error(`could not create dir: ${dir.get_path()}`)); + } + + const destFile = destDir.get_child(fileName); + destFile.replace_contents_bytes_async( + GLib.Bytes.new_take(data), + null, + false, + Gio.FileCreateFlags.NONE, + null + ) + .then(() => { + debug(`saved file: ${destPath}`); + resolve(destFile); + }) + .catch(err => reject(err)); + }); +} + +function getFileContentsPromise(place, folderName, fileName) +{ + return new Promise((resolve, reject) => { + const destPath = [ + GLib[`get_${place}_dir`](), + Misc.appId, + folderName, + fileName + ].join('/'); + + const file = Gio.File.new_for_path(destPath); + debug(`reading data from: ${destPath}`); + + if(!file.query_exists(null)) { + debug(`no such file: ${file.get_path()}`); + return resolve(null); + } + + file.load_bytes_async(null) + .then(result => { + const data = result[0].get_data(); + if(!data || !data.length) + return reject(new Error('source file is empty')); + + debug(`read data from: ${destPath}`); + + if(data instanceof Uint8Array) + resolve(ByteArray.toString(data)); + else + resolve(data); + }) + .catch(err => reject(err)); + }); +} diff --git a/src/main.js b/src/main.js index a6f6b6d4..76d169c4 100644 --- a/src/main.js +++ b/src/main.js @@ -1,11 +1,8 @@ imports.gi.versions.Gdk = '4.0'; imports.gi.versions.Gtk = '4.0'; -const { Gio, Gst } = imports.gi; - +const { Gst } = imports.gi; Gst.init(null); -Gio._promisify(Gio._LocalFilePrototype, 'load_bytes_async', 'load_bytes_finish'); -Gio._promisify(Gio._LocalFilePrototype, 'replace_contents_bytes_async', 'replace_contents_finish'); const { App } = imports.src.app; diff --git a/src/player.js b/src/player.js index 49b1d617..0438564f 100644 --- a/src/player.js +++ b/src/player.js @@ -2,6 +2,7 @@ const { Gdk, Gio, GObject, Gst, GstClapper, Gtk } = imports.gi; const ByteArray = imports.byteArray; const Dash = imports.src.dash; const Debug = imports.src.debug; +const FileOps = imports.src.fileOps; const Misc = imports.src.misc; const YouTube = imports.src.youtube; const { PlayerBase } = imports.src.playerBase; @@ -88,9 +89,19 @@ class ClapperPlayer extends PlayerBase throw new Error('no YouTube video info'); const dash = Dash.generateDash(info); - const videoUri = (dash) - ? await Dash.saveDashPromise(dash).catch(debug) - : this.ytClient.getBestCombinedUri(info); + let videoUri = null; + + if(dash) { + const dashFile = await FileOps.saveFilePromise( + 'tmp', null, 'clapper.mpd', dash + ).catch(debug); + + if(dashFile) + videoUri = dashFile.get_uri(); + } + + if(!videoUri) + videoUri = this.ytClient.getBestCombinedUri(info); if(!videoUri) throw new Error('no YouTube video URI'); diff --git a/src/youtube.js b/src/youtube.js index 2babcb4f..c79f51f4 100644 --- a/src/youtube.js +++ b/src/youtube.js @@ -1,4 +1,4 @@ -const { Gio, GLib, GObject, Gst, Soup } = imports.gi; +const { GObject, Gst, Soup } = imports.gi; const ByteArray = imports.byteArray; const Debug = imports.src.debug; const FileOps = imports.src.fileOps; @@ -96,7 +96,7 @@ var YouTubeClient = GObject.registerClass({ let isFoundInTemp = false; let isUsingPlayerResp = false; - const tempInfo = await this._getFileContentsPromise('tmp', 'yt-info', videoId).catch(debug); + const tempInfo = await FileOps.getFileContentsPromise('tmp', 'yt-info', videoId).catch(debug); if(tempInfo) { debug('checking temp info for requested video'); let parsedTempInfo; @@ -191,7 +191,7 @@ var YouTubeClient = GObject.registerClass({ debug(`found player URI: ${ytUri}`); const ytId = ytPath.split('/').find(el => Misc.isHex(el)); - let ytSigData = await this._getFileContentsPromise('user_cache', 'yt-sig', ytId).catch(debug); + let ytSigData = await FileOps.getFileContentsPromise('user_cache', 'yt-sig', ytId).catch(debug); if(ytSigData) { ytSigData = ytSigData.split(';'); @@ -229,7 +229,8 @@ var YouTubeClient = GObject.registerClass({ if(actions) { debug('deciphered, saving cipher actions to cache file'); const saveData = sts + ';' + actions; - this._createSubdirFileAsync('user_cache', 'yt-sig', ytId, saveData); + /* We do not need to wait for it */ + FileOps.saveFilePromise('user_cache', 'yt-sig', ytId, saveData); } } if(!actions || !actions.length) { @@ -275,7 +276,8 @@ var YouTubeClient = GObject.registerClass({ /* Estimated safe time for rewatching video */ info.streamingData.expireDate = dateSeconds + Number(exp); - this._createSubdirFileAsync( + /* Last info is stored in variable, so don't wait here */ + FileOps.saveFilePromise( 'tmp', 'yt-info', videoId, JSON.stringify(info) ); } @@ -643,69 +645,6 @@ var YouTubeClient = GObject.registerClass({ return `${url}&${sig}=${encodeURIComponent(key)}`; } - async _createSubdirFileAsync(place, folderName, fileName, data) - { - const destPath = [ - GLib[`get_${place}_dir`](), - Misc.appId, - folderName - ].join('/'); - - const destDir = Gio.File.new_for_path(destPath); - debug(`saving file: ${destPath}`); - - for(let dir of [destDir.get_parent(), destDir]) { - const createdDir = await FileOps.createDirPromise(dir).catch(debug); - if(!createdDir) return; - } - - const destFile = destDir.get_child(fileName); - destFile.replace_contents_bytes_async( - GLib.Bytes.new_take(data), - null, - false, - Gio.FileCreateFlags.NONE, - null - ) - .then(() => debug(`saved file: ${destPath}`)) - .catch(debug); - } - - _getFileContentsPromise(place, folderName, fileName) - { - return new Promise((resolve, reject) => { - const destPath = [ - GLib[`get_${place}_dir`](), - Misc.appId, - folderName, - fileName - ].join('/'); - - const file = Gio.File.new_for_path(destPath); - debug(`reading data from: ${destPath}`); - - if(!file.query_exists(null)) { - debug(`no such file: ${file.get_path()}`); - return resolve(null); - } - - file.load_bytes_async(null) - .then(result => { - const data = result[0].get_data(); - if(!data || !data.length) - return reject(new Error('source file is empty')); - - debug(`read data from: ${destPath}`); - - if(data instanceof Uint8Array) - resolve(ByteArray.toString(data)); - else - resolve(data); - }) - .catch(err => reject(err)); - }); - } - _getPlayerPostData(videoId) { const cliVer = this.postInfo.clientVersion;