diff --git a/src/actions.js b/src/actions.js index 206d5799..326e01b9 100644 --- a/src/actions.js +++ b/src/actions.js @@ -2,6 +2,7 @@ const Dialogs = imports.src.dialogs; var actions = { open_local: ['O'], + export_playlist: ['E'], open_uri: ['U'], prefs: null, about: null, @@ -30,7 +31,8 @@ function handleAction(action, window) switch(action.name) { case 'open_local': - new Dialogs.FileChooser(window); + case 'export_playlist': + new Dialogs.FileChooser(window, action.name); break; case 'open_uri': new Dialogs.UriDialog(window); diff --git a/src/dialogs.js b/src/dialogs.js index 8a3f38a1..5ad97859 100644 --- a/src/dialogs.js +++ b/src/dialogs.js @@ -1,6 +1,7 @@ const { Gio, GObject, Gtk, Gst } = imports.gi; const System = imports.system; const Debug = imports.src.debug; +const FileOps = imports.src.fileOps; const Misc = imports.src.misc; const Prefs = imports.src.prefs; const PrefsBase = imports.src.prefsBase; @@ -10,14 +11,37 @@ const { debug } = Debug; var FileChooser = GObject.registerClass( class ClapperFileChooser extends Gtk.FileChooserNative { - _init(window) + _init(window, purpose) { super._init({ transient_for: window, modal: true, - select_multiple: true, }); + switch(purpose) { + case 'open_local': + this._prepareOpenLocal(); + break; + case 'export_playlist': + this._prepareExportPlaylist(); + break; + default: + debug(new Error(`unknown file chooser purpose: ${purpose}`)); + break; + } + + this.chooserPurpose = purpose; + this.responseSignal = this.connect('response', this._onResponse.bind(this)); + + /* File chooser closes itself when nobody is holding its ref */ + this.ref(); + this.show(); + } + + _prepareOpenLocal() + { + this.select_multiple = true; + const filter = new Gtk.FileFilter({ name: 'Media Files', }); @@ -26,12 +50,18 @@ class ClapperFileChooser extends Gtk.FileChooserNative filter.add_mime_type('application/claps'); Misc.subsMimes.forEach(mime => filter.add_mime_type(mime)); this.add_filter(filter); + } - this.responseSignal = this.connect('response', this._onResponse.bind(this)); + _prepareExportPlaylist() + { + this.action = Gtk.FileChooserAction.SAVE; + this.set_current_name('playlist.claps'); - /* File chooser closes itself when nobody is holding its ref */ - this.ref(); - this.show(); + const filter = new Gtk.FileFilter({ + name: 'Playlist Files', + }); + filter.add_mime_type('application/claps'); + this.add_filter(filter); } _onResponse(filechooser, response) @@ -42,31 +72,58 @@ class ClapperFileChooser extends Gtk.FileChooserNative this.responseSignal = null; if(response === Gtk.ResponseType.ACCEPT) { - const files = this.get_files(); - const filesArray = []; - - let index = 0; - let file; - - while((file = files.get_item(index))) { - filesArray.push(file); - index++; + switch(this.chooserPurpose) { + case 'open_local': + this._handleOpenLocal(); + break; + case 'export_playlist': + this._handleExportPlaylist(); + break; } - - const { application } = this.transient_for; - const isHandlesOpen = Boolean( - application.flags & Gio.ApplicationFlags.HANDLES_OPEN - ); - - /* Remote app does not handle open */ - if(isHandlesOpen) - application.open(filesArray, ""); - else - application._openFilesAsync(filesArray); } this.unref(); } + + _handleOpenLocal() + { + const files = this.get_files(); + const filesArray = []; + + let index = 0; + let file; + + while((file = files.get_item(index))) { + filesArray.push(file); + index++; + } + + const { application } = this.transient_for; + const isHandlesOpen = Boolean( + application.flags & Gio.ApplicationFlags.HANDLES_OPEN + ); + + /* Remote app does not handle open */ + if(isHandlesOpen) + application.open(filesArray, ""); + else + application._openFilesAsync(filesArray); + } + + _handleExportPlaylist() + { + const file = this.get_file(); + const { playlistWidget } = this.transient_for.child.player; + const playlist = playlistWidget.getPlaylist(true); + + FileOps.saveFileSimplePromise(file, playlist.join('\n')) + .then(() => { + debug(`exported playlist to file: ${file.get_path()}`); + }) + .catch(err => { + debug(err); + }); + } }); var UriDialog = GObject.registerClass( diff --git a/src/fileOps.js b/src/fileOps.js index af4d4617..b3c3d7b7 100644 --- a/src/fileOps.js +++ b/src/fileOps.js @@ -57,6 +57,18 @@ function createDirPromise(dir) }); } +/* Simple save data to GioFile */ +function saveFileSimplePromise(file, data) +{ + return file.replace_contents_bytes_async( + GLib.Bytes.new_take(data), + null, + false, + Gio.FileCreateFlags.NONE, + null + ); +} + /* Saves file in optional subdirectory and resolves with it */ function saveFilePromise(place, subdirName, fileName, data) { @@ -82,18 +94,12 @@ function saveFilePromise(place, subdirName, fileName, data) } 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)); + saveFileSimplePromise(destFile, data) + .then(() => { + debug(`saved file: ${destPath}`); + resolve(destFile); + }) + .catch(err => reject(err)); }); } diff --git a/src/playlist.js b/src/playlist.js index d42ce083..626468dd 100644 --- a/src/playlist.js +++ b/src/playlist.js @@ -76,6 +76,24 @@ class ClapperPlaylistWidget extends Gtk.ListBox return this.get_row_at_index(this.activeRowId); } + getPlaylist(useFilePaths) + { + const playlist = []; + let index = 0; + let item; + + while((item = this.get_row_at_index(index))) { + const path = (useFilePaths && item.isLocalFile) + ? GLib.filename_from_uri(item.uri)[0] + : item.uri; + + playlist.push(path); + index++; + } + + return playlist; + } + getActiveFilename() { const row = this.getActiveRow();