feat: add Gemini and Spartan protocol support
This commit is contained in:
parent
a4a49f41d7
commit
fa7aa5557c
48 changed files with 4992 additions and 161 deletions
|
|
@ -1,9 +1,17 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../database/database.dart';
|
||||
import '../l10n/strings.dart';
|
||||
import 'controllers/bookmarks_controller.dart';
|
||||
import 'controllers/reader_settings_controller.dart'
|
||||
show ReaderSettingsController;
|
||||
import 'controllers/subscriptions_controller.dart';
|
||||
import 'key_store.dart';
|
||||
import 'pages/mews_urls.dart';
|
||||
import 'pem.dart';
|
||||
|
||||
/// Applies the change an action address encodes, e.g. mews:do/set-theme.
|
||||
/// Returns whether anything changed, so the navigator can skip a rerender that
|
||||
|
|
@ -12,6 +20,10 @@ import 'pages/mews_urls.dart';
|
|||
/// An unknown verb or a missing argument does nothing and reports nothing: the
|
||||
/// only links carrying these addresses are the ones the client builds, so a
|
||||
/// miss is a bug rather than something to explain to the reader.
|
||||
/// A PEM file the reader picked, or null when the pick was cancelled. [name]
|
||||
/// names the identity after its file, which is what the reader chose.
|
||||
typedef PickedPem = ({String name, String content});
|
||||
|
||||
class InternalActions {
|
||||
InternalActions({
|
||||
required MewsDatabase db,
|
||||
|
|
@ -19,17 +31,42 @@ class InternalActions {
|
|||
required ReaderSettingsController settings,
|
||||
required BookmarksController bookmarks,
|
||||
required SubscriptionsController subscriptions,
|
||||
required KeyStore keys,
|
||||
required Strings strings,
|
||||
Future<PickedPem?> Function(String title)? pickPemFile,
|
||||
}) : _db = db,
|
||||
_applyLanguage = applyLanguage,
|
||||
_settings = settings,
|
||||
_bookmarks = bookmarks,
|
||||
_subscriptions = subscriptions;
|
||||
_subscriptions = subscriptions,
|
||||
_keys = keys,
|
||||
_strings = strings,
|
||||
_pickPemFile = pickPemFile ?? _pickPemFromFileSystem;
|
||||
|
||||
final MewsDatabase _db;
|
||||
final Future<void> Function() _applyLanguage;
|
||||
final ReaderSettingsController _settings;
|
||||
final BookmarksController _bookmarks;
|
||||
final SubscriptionsController _subscriptions;
|
||||
final KeyStore _keys;
|
||||
final Strings _strings;
|
||||
final Future<PickedPem?> Function(String title) _pickPemFile;
|
||||
|
||||
/// Opens the platform file picker for one PEM file, titled with what the
|
||||
/// step asks for. A single file may hold both the certificate and its key,
|
||||
/// or either one alone. A cancelled pick comes back as nothing.
|
||||
static Future<PickedPem?> _pickPemFromFileSystem(String title) async {
|
||||
final files = await FilePicker.pickFiles(
|
||||
dialogTitle: title,
|
||||
type: FileType.custom,
|
||||
allowedExtensions: const ['pem', 'crt', 'cer', 'key']);
|
||||
final path = files.singleOrNull?.path;
|
||||
if (path == null) return null;
|
||||
return (
|
||||
name: p.basenameWithoutExtension(path),
|
||||
content: await File(path).readAsString(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> apply(String url) async {
|
||||
final verb = MewsUrls.verbOf(url);
|
||||
|
|
@ -52,10 +89,55 @@ class InternalActions {
|
|||
'add-tag' => _changeTag(args, add: true),
|
||||
'remove-tag' => _changeTag(args, add: false),
|
||||
'delete-tag' => _deleteTag(args['tag']),
|
||||
'import-certificate' => _importCertificate(),
|
||||
'rename-certificate' => _renameCertificate(args),
|
||||
'delete-certificate' => _deleteCertificate(args),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// Reads a certificate and its key from picked PEM files. The first pick
|
||||
/// may hold both; when it holds only one, the other is asked for again, by
|
||||
/// name, so a key picked on its own is not silently dropped. A cancelled
|
||||
/// pick imports nothing.
|
||||
Future<bool> _importCertificate() async {
|
||||
final first = await _pickPemFile(_strings.t('certificates-pick-title'));
|
||||
if (first == null) return false;
|
||||
String? cert = certificateOf(first.content);
|
||||
String? key = privateKeyOf(first.content);
|
||||
if (cert == null || key == null) {
|
||||
final asking = cert == null
|
||||
? _strings.t('certificates-pick-cert-title')
|
||||
: _strings.t('certificates-pick-key-title');
|
||||
final second = await _pickPemFile(asking);
|
||||
if (second == null) return false;
|
||||
cert ??= certificateOf(second.content);
|
||||
key ??= privateKeyOf(second.content);
|
||||
}
|
||||
if (cert == null || key == null) return false;
|
||||
final name = first.name.trim();
|
||||
final id = await _db.addClientCertificate(
|
||||
name.isEmpty ? _strings.t('certificates-default-name') : name, cert);
|
||||
await _keys.save(id, key);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> _renameCertificate(Map<String, String> args) async {
|
||||
final id = int.tryParse(args['id'] ?? '');
|
||||
final name = args['name']?.trim();
|
||||
if (id == null || name == null || name.isEmpty) return false;
|
||||
await _db.renameClientCertificate(id, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> _deleteCertificate(Map<String, String> args) async {
|
||||
final id = int.tryParse(args['id'] ?? '');
|
||||
if (id == null) return false;
|
||||
await _keys.delete(id);
|
||||
await _db.deleteClientCertificate(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Changing the language has to load its messages before the page is built
|
||||
/// again, or the rerender would still read the old ones.
|
||||
Future<bool> _setLanguage(String? tag) async {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue