fixed client-reported crashes in the pdf viewer, image picker and prosemirror images, mapped webdav errors to user messages and hardened keychain, cache and push stores against io failures
This commit is contained in:
1 parent
c5592f117b
commit
23e4510bf8
19 files changed
+545
-55
No files matched your search
@@ -52,10 +52,16 @@ class CacheStore {
|
||||
Future<Directory> _directory() {
|
||||
final name = directoryName(SessionManager().activeAccount?.namespace);
|
||||
return _dirs[name] ??= () async {
|
||||
final base = await getApplicationCacheDirectory();
|
||||
final dir = Directory('${base.path}/$name');
|
||||
await dir.create(recursive: true);
|
||||
return dir;
|
||||
try {
|
||||
final base = await getApplicationCacheDirectory();
|
||||
final dir = Directory('${base.path}/$name');
|
||||
await dir.create(recursive: true);
|
||||
return dir;
|
||||
} on Object {
|
||||
// Not memoized (disk full at launch): the next call tries again.
|
||||
unawaited(_dirs.remove(name));
|
||||
rethrow;
|
||||
}
|
||||
}();
|
||||
}
|
||||
|
||||
|
||||
@@ -54,11 +54,61 @@ AppException? _dioToAppException(DioException error) {
|
||||
}
|
||||
}
|
||||
|
||||
final RegExp _davException = RegExp(
|
||||
r'<s:exception>\s*([^<]*?)\s*</s:exception>',
|
||||
);
|
||||
final RegExp _davMessage = RegExp(r'<s:message>\s*([^<]*?)\s*</s:message>');
|
||||
|
||||
/// The Sabre DAV error body of a failed WebDAV request: the exception class
|
||||
/// (without its `Sabre\DAV\Exception\` namespace) and the server's message.
|
||||
/// The class says what actually went wrong where the status is ambiguous —
|
||||
/// MKCOL on an existing folder is a plain 405.
|
||||
({String exception, String message})? parseDavError(String body) {
|
||||
final exception = _davException.firstMatch(body)?.group(1);
|
||||
if (exception == null || exception.isEmpty) return null;
|
||||
return (
|
||||
exception: exception.split('\\').last,
|
||||
message: _davMessage.firstMatch(body)?.group(1) ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
AppException? _davToAppException(
|
||||
int status,
|
||||
({String exception, String message}) dav,
|
||||
String detail,
|
||||
) {
|
||||
final userMessage = switch (dav.exception) {
|
||||
'MethodNotAllowed' when status == 405 =>
|
||||
'Ein Ordner oder eine Datei mit diesem Namen existiert bereits.',
|
||||
'PreconditionFailed' =>
|
||||
'Am Zielort existiert bereits ein Eintrag mit diesem Namen.',
|
||||
'Conflict' => 'Der übergeordnete Ordner existiert nicht mehr.',
|
||||
'Locked' =>
|
||||
'Der Eintrag wird gerade bearbeitet. Bitte versuche es gleich erneut.',
|
||||
'InsufficientStorage' =>
|
||||
'Der verfügbare Speicherplatz reicht dafür nicht aus.',
|
||||
_ => null,
|
||||
};
|
||||
if (userMessage == null) return null;
|
||||
return ServerException(
|
||||
statusCode: status,
|
||||
userMessage: userMessage,
|
||||
technicalDetails: detail,
|
||||
allowRetry: dav.exception == 'Locked',
|
||||
);
|
||||
}
|
||||
|
||||
/// The nextcloud/dynamite clients throw [DynamiteApiException] on non-2xx.
|
||||
/// Its toString() dumps every response header, so the details keep only the
|
||||
/// status plus a trimmed body preview (same format as the Talk API errors).
|
||||
AppException _dynamiteToAppException(DynamiteApiException error) {
|
||||
final status = error.statusCode;
|
||||
final dav = parseDavError(error.body);
|
||||
if (dav != null) {
|
||||
final detail = 'HTTP $status ${dav.exception}: ${dav.message}';
|
||||
return _davToAppException(status, dav, detail) ??
|
||||
exceptionForStatus(status, detail);
|
||||
}
|
||||
final preview = previewBody(error.body);
|
||||
final detail = preview.isEmpty
|
||||
? 'HTTP $status'
|
||||
|
||||
@@ -7,6 +7,7 @@ class ServerException extends AppException {
|
||||
required this.statusCode,
|
||||
String? userMessage,
|
||||
super.technicalDetails,
|
||||
super.allowRetry,
|
||||
}) : super(
|
||||
// Status code intentionally not in the user message — it lives in
|
||||
// technicalDetails behind "Details anzeigen".
|
||||
@@ -14,6 +15,5 @@ class ServerException extends AppException {
|
||||
userMessage ??
|
||||
'Der Server konnte die Anfrage gerade nicht verarbeiten. '
|
||||
'Bitte versuche es in einem Moment erneut.',
|
||||
allowRetry: true,
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
import '../../../storage/keychain_write.dart';
|
||||
import '../../errors/auth_exception.dart';
|
||||
|
||||
/// `first_unlock` accessibility so the token can be read during background
|
||||
@@ -52,9 +53,9 @@ class MarianumConnectTokenStorage {
|
||||
required String tokenId,
|
||||
required DateTime? expiresAt,
|
||||
}) async {
|
||||
await _storage.write(key: _tokenKey, value: token);
|
||||
await _storage.write(key: _tokenIdKey, value: tokenId);
|
||||
await _storage.write(
|
||||
await _storage.writeRetrying(key: _tokenKey, value: token);
|
||||
await _storage.writeRetrying(key: _tokenIdKey, value: tokenId);
|
||||
await _storage.writeRetrying(
|
||||
key: _expiresAtKey,
|
||||
value: expiresAt?.toIso8601String() ?? '',
|
||||
);
|
||||
@@ -75,7 +76,7 @@ class MarianumConnectTokenStorage {
|
||||
if (value == null) {
|
||||
await _storage.delete(key: key);
|
||||
} else {
|
||||
await _storage.write(key: key, value: value);
|
||||
await _storage.writeRetrying(key: key, value: value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
import '../../../../storage/keychain_write.dart';
|
||||
import '../../../../utils/random_id.dart';
|
||||
|
||||
/// A stable, anonymous per-install identifier for telemetry. Generated once on
|
||||
@@ -22,7 +23,7 @@ class TelemetryDeviceId {
|
||||
return existing;
|
||||
}
|
||||
final generated = randomHexId();
|
||||
await _storage.write(key: _key, value: generated);
|
||||
await _storage.writeRetrying(key: _key, value: generated);
|
||||
_cached = generated;
|
||||
return generated;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user