From 2e892c923b51b3139b4b81c744b4673a00c124a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Tue, 20 Apr 2021 18:44:53 +0200 Subject: [PATCH] Support opening folders with media files D&D folder with videos onto Clapper window to play them as video playlist. If folder contains exactly one video and subtitle file, then that video will be played with subtitles automatically applied. --- src/app.js | 3 +- src/appBase.js | 14 ++++++-- src/dialogs.js | 2 +- src/fileOps.js | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/app.js b/src/app.js index 7df9c3e5..1f2b2a3d 100644 --- a/src/app.js +++ b/src/app.js @@ -38,8 +38,7 @@ class ClapperApp extends AppBase { super.vfunc_open(files, hint); - this._openFiles(files); - this.activate(); + this._openFilesAsync(files).then(() => this.activate()).catch(debug); } _onWindowMap(window) diff --git a/src/appBase.js b/src/appBase.js index 565beffd..8ad55da4 100644 --- a/src/appBase.js +++ b/src/appBase.js @@ -1,5 +1,6 @@ const { Gio, GLib, GObject, Gtk } = imports.gi; const Debug = imports.src.debug; +const FileOps = imports.src.fileOps; const Menu = imports.src.menu; const Misc = imports.src.misc; @@ -59,9 +60,18 @@ class ClapperAppBase extends Gtk.Application ); } - _openFiles(files) + async _openFilesAsync(files) { - const [playlist, subs] = Misc.parsePlaylistFiles(files); + const urisArr = []; + + for(let file of files) { + /* If file is not a dir its URI will be returned in an array */ + const uris = await FileOps.getDirFilesUrisPromise(file).catch(debug); + if(uris && uris.length) + urisArr.push(...uris); + } + + const [playlist, subs] = Misc.parsePlaylistFiles(urisArr); const { player } = this.active_window.get_child(); if(playlist && playlist.length) diff --git a/src/dialogs.js b/src/dialogs.js index 8b378a93..c1f7b654 100644 --- a/src/dialogs.js +++ b/src/dialogs.js @@ -62,7 +62,7 @@ class ClapperFileChooser extends Gtk.FileChooserNative if(isHandlesOpen) application.open(filesArray, ""); else - application._openFiles(filesArray); + application._openFilesAsync(filesArray); } this.unref(); diff --git a/src/fileOps.js b/src/fileOps.js index e5cc839e..af4d4617 100644 --- a/src/fileOps.js +++ b/src/fileOps.js @@ -12,6 +12,11 @@ const LocalFilePrototype = Gio.File.new_for_path('/').constructor.prototype; Gio._promisify(LocalFilePrototype, 'load_bytes_async', 'load_bytes_finish'); Gio._promisify(LocalFilePrototype, 'make_directory_async', 'make_directory_finish'); Gio._promisify(LocalFilePrototype, 'replace_contents_bytes_async', 'replace_contents_finish'); +Gio._promisify(LocalFilePrototype, 'query_info_async', 'query_info_finish'); +Gio._promisify(LocalFilePrototype, 'enumerate_children_async', 'enumerate_children_finish'); + +Gio._promisify(Gio.FileEnumerator.prototype, 'close_async', 'close_finish'); +Gio._promisify(Gio.FileEnumerator.prototype, 'next_files_async', 'next_files_finish'); function createCacheDirPromise() { @@ -126,3 +131,91 @@ function getFileContentsPromise(place, subdirName, fileName) .catch(err => reject(err)); }); } + +function _getDirUrisPromise(dir, isDeep) +{ + return new Promise(async (resolve, reject) => { + const enumerator = await dir.enumerate_children_async( + 'standard::name,standard::type', + Gio.FileQueryInfoFlags.NONE, + GLib.PRIORITY_DEFAULT, + null + ).catch(debug); + + if(!enumerator) + return reject(new Error('could not create file enumerator')); + + const dirPath = dir.get_path(); + const arr = []; + + debug(`enumerating files in dir: ${dirPath}`); + + while(true) { + const infos = await enumerator.next_files_async( + 1, + GLib.PRIORITY_DEFAULT, + null + ).catch(debug); + + if(!infos || !infos.length) + break; + + const fileUri = dir.get_uri() + '/' + infos[0].get_name(); + + if(infos[0].get_file_type() !== Gio.FileType.DIRECTORY) { + arr.push(fileUri); + continue; + } + if(!isDeep) + continue; + + const subDir = Misc.getFileFromLocalUri(fileUri); + const subDirUris = await _getDirUrisPromise(subDir, isDeep).catch(debug); + + if(subDirUris && subDirUris.length) + arr.push(...subDirUris); + } + + const isClosed = await enumerator.close_async( + GLib.PRIORITY_DEFAULT, + null + ).catch(debug); + + if(isClosed) + debug(`closed enumerator for dir: ${dirPath}`); + else + debug(new Error(`could not close file enumerator for dir: ${dirPath}`)); + + resolve(arr); + }); +} + +/* Either GioFile or URI for dir arg */ +function getDirFilesUrisPromise(dir, isDeep) +{ + return new Promise(async (resolve, reject) => { + if(!dir.get_path) + dir = Misc.getFileFromLocalUri(dir); + if(!dir) + return reject(new Error('invalid directory')); + + const fileInfo = await dir.query_info_async( + 'standard::type', + Gio.FileQueryInfoFlags.NONE, + GLib.PRIORITY_DEFAULT, + null + ).catch(debug); + + if(!fileInfo) + return reject(new Error('no file type info')); + + if(fileInfo.get_file_type() !== Gio.FileType.DIRECTORY) + return resolve([dir.get_uri()]); + + const arr = await _getDirUrisPromise(dir, isDeep).catch(debug); + if(!arr || !arr.length) + return reject(new Error('enumerated files list is empty')); + + resolve(arr.sort()); + }); +}