Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ lists the two artwork folders once per load rather than stat a poster each.
An empty slot makes its source skip itself. The scanner reads them
out of GSettings, falling back to `$LIBRARY_TMDB_KEY` for an
empty slot 1 (never under `start --stand-in`, whose `EXT_STAND_IN_UNSET` drops it).
Each key row's Import button reads `~/Documents/keys/<SERVICE>/`, the
user's key drop shared with other projects, and its Check runs the scanner's
Each key row's Import button reads only the file the user picks in a file dialog,
which opens on the field's file in `~/Documents/keys/<SERVICE>/` (the user's key
drop shared with other projects) when there is one, and its Check runs the scanner's
`--check`, so the key is read from the settings and never on a command line.
3. `extension.js` builds a `LibraryApp` (`lib/app.js`) and enables it. The app
reads `library.json` and builds whichever places the two "opens in" settings name.
Expand Down
66 changes: 37 additions & 29 deletions src/prefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {SECTIONS as LIBRARY_SECTIONS, openCommandKey, readSections} from './lib/
import {ACTIONS, NATIVE_KEYS, padLabel} from './lib/actions.js';

// A credential's `fields` are tab-joined in one slot of the `credentials` setting;
// a field's Import reads ~/Documents/keys/<service>/<field.file>.
// a field's Import reads the file the user picks, offering <field.file> from the
// key drop, ~/Documents/keys/<service>/.
const SOURCES = {
tvmaze: {
title: 'TVmaze',
Expand Down Expand Up @@ -770,21 +771,15 @@ export default class LibraryPreferences extends ExtensionPreferences {
show_apply_button: true,
});
value.connect('apply', () => this._setField(settings, slot, i, value.get_text().trim(), fields.length));
const drop = this._keyDropFile(spec.service, field.file);
if (drop) {
const importButton = new Gtk.Button({
label: 'Import',
valign: Gtk.Align.CENTER,
tooltip_text: `Read it from ${drop}`,
css_classes: ['flat'],
});
importButton.connect('clicked', () => {
const imported = this._readKeyDrop(drop);
if (imported)
this._setField(settings, slot, i, imported, fields.length);
});
value.add_suffix(importButton);
}
const importButton = new Gtk.Button({
label: 'Import',
valign: Gtk.Align.CENTER,
tooltip_text: 'Read it from a file',
css_classes: ['flat'],
});
importButton.connect('clicked', () => this._importKey(window, spec, field, imported =>
this._setField(settings, slot, i, imported, fields.length)));
value.add_suffix(importButton);
source.add_row(value);
return value;
});
Expand Down Expand Up @@ -845,20 +840,33 @@ export default class LibraryPreferences extends ExtensionPreferences {
settings.set_strv(key, [...list, entry]);
}

// The value in the key drop, or '' if it cannot be read. Never logged.
_readKeyDrop(path) {
try {
return new TextDecoder().decode(GLib.file_get_contents(path)[1]).trim();
} catch (e) {
console.warn(`[Library Menu] Could not read ${path}: ${e.message}`);
return '';
}
}

_keyDropFile(service, field) {
// Only the file the user picks is read. The dialog opens on the key drop's
// file for this field, ~/Documents/keys/<service>/<field.file>, if there is one.
_importKey(window, spec, field, onRead) {
const docs = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS) ?? GLib.get_home_dir();
const path = GLib.build_filenamev([docs, 'keys', service, field]);
return GLib.file_test(path, GLib.FileTest.IS_REGULAR) ? path : null;
const drop = Gio.File.new_for_path(GLib.build_filenamev([docs, 'keys', spec.service, field.file]));
const dialog = new Gtk.FileDialog({title: `Import the ${spec.title} ${field.title}`, modal: true});
if (drop.query_exists(null))
dialog.initial_file = drop;
else
dialog.initial_folder = Gio.File.new_for_path(docs);
dialog.open(window, null, (source, result) => {
let file;
try {
file = source.open_finish(result);
} catch {
return; // Cancelled.
}
file.load_contents_async(null, (_file, loaded) => {
try {
const key = new TextDecoder().decode(file.load_contents_finish(loaded)[1]).trim();
if (key)
onRead(key);
} catch (e) {
console.warn(`[Library Menu] Could not read ${file.get_parse_name()}: ${e.message}`);
}
});
});
}

// One folder that overrides auto-detection; empty is auto-detected.
Expand Down
Loading