Nailathalang source
I-download ang source ZIP
CH-J SM Mobile
I-browse ang mga direktoryo at file ng isang partikular na release ng application.
CH-J / SM Mobile
175 file
sm-mobile-20261016T185235Z-8e93ecc1b59a
Idinagdag: · Binuo:
1
import 'dart:typed_data';2
import 'package:dartssh2/dartssh2.dart';3
import '../../core/failure.dart';4
import '../documents.dart';5
import '../sftp_service.dart';7
/// Size limits: text is edited in memory up to [maxEditableBytes]; viewers8
/// (images, PDF, archives, hex) read up to [maxViewBytes].9
const maxEditableBytes = 20 * 1024 * 1024;10
const maxViewBytes = 100 * 1024 * 1024;12
/// Where an open file comes from and how it is saved back.13
abstract class FileSource {14
String get name;16
/// Shown to the user: server and path, or the device location.17
String get location;19
/// 'remote', 'device' or 'new'.20
String get kind;22
/// A stable key for recent files and favorites.23
String get key;25
/// False for files without a place to save to (new, read-only).26
bool get canSave;28
/// Why saving is not possible, as a localization key.29
String? get readOnlyReason;31
Future<Uint8List> load({int maxBytes = maxEditableBytes});33
/// Saves [bytes]. Throws 'remoteChanged' / 'documentChanged' when the34
/// file changed since it was loaded, unless [force].35
Future<void> save(36
Uint8List bytes, {37
bool force = false,38
bool inPlace = false,39
});41
Map<String, dynamic> toJson();42
}44
/// A file on the connected server, read and written over SFTP.45
class RemoteSource implements FileSource {46
/// The current SFTP service; throws when the connection is gone.47
final Future<SftpService> Function() service;48
final String profileId, profileName, path;49
SftpFileAttrs? baseline;50
List<int>? baselineHash;51
bool writable = true;52
RemoteSource({53
required this.service,54
required this.profileId,55
required this.profileName,56
required this.path,57
this.baseline,58
});60
@override61
String get name => path.substring(path.lastIndexOf('/') + 1);62
@override63
String get location => '$profileName:$path';64
@override65
String get kind => 'remote';66
@override67
String get key => 'remote:$profileId:$path';68
@override69
bool get canSave => writable;70
@override71
String? get readOnlyReason => writable ? null : 'readOnlyRemote';73
/// Permission bits for display, e.g. "rw-r--r--".74
String? get permissions {75
final mode = baseline?.mode?.value;76
if (mode == null) return null;77
const chars = 'rwxrwxrwx';78
return [79
for (var i = 0; i < 9; i++) mode & (1 << (8 - i)) != 0 ? chars[i] : '-',80
].join();81
}83
@override84
Future<Uint8List> load({int maxBytes = maxEditableBytes}) async {85
final s = await service();86
final (bytes, attrs) = await s.readFile(path, maxBytes: maxBytes);87
baseline = attrs;88
baselineHash = await SftpService.sha256Of(bytes);89
// Whether writing is allowed is decided by the server on save; a90
// refusal is reported as missing permission.91
return bytes;92
}94
@override95
Future<void> save(96
Uint8List bytes, {97
bool force = false,98
bool inPlace = false,99
}) async {100
final s = await service();101
// Without a known state (a draft restored offline), an existing file is102
// never overwritten silently.103
if (baseline == null && !force && await s.statOrNull(path) != null) {104
throw const Failure('remoteChanged');105
}106
final attrs = await s.saveFile(107
path,108
bytes,109
expected: baseline,110
expectedHash: baselineHash,111
force: force,112
inPlace: inPlace,113
);114
baseline = attrs;115
baselineHash = await SftpService.sha256Of(bytes);116
}118
/// Whether the file on the server changed since it was loaded or saved.119
Future<bool> changedOnServer() async {120
final b = baseline;121
if (b == null) return false;122
try {123
await (await service()).checkUnchanged(path, b, baselineHash);124
return false;125
} on Failure catch (e) {126
if (e.code == 'remoteChanged' || e.code == 'remoteDeleted') return true;127
rethrow;128
}129
}131
@override132
Map<String, dynamic> toJson() => {133
'kind': kind,134
'profileId': profileId,135
'profileName': profileName,136
'path': path,137
if (baseline != null)138
'baseline': {'size': baseline!.size, 'mtime': baseline!.modifyTime},139
};141
static RemoteSource fromJson(142
Map<String, dynamic> m,143
Future<SftpService> Function() service,144
) {145
final b = m['baseline'] as Map?;146
return RemoteSource(147
service: service,148
profileId: m['profileId'] as String,149
profileName: m['profileName'] as String? ?? '',150
path: m['path'] as String,151
baseline: b == null152
? null153
: SftpFileAttrs(154
size: b['size'] as int?,155
modifyTime: b['mtime'] as int?,156
),157
);158
}159
}161
/// A file on the device, opened through the system document picker.162
class DeviceSource implements FileSource {163
final DocumentPlatform documents;164
DeviceDocument doc;165
DateTime? _modified;166
int? _size;167
DeviceSource(this.documents, this.doc);169
/// A document that was just written with [size] bytes.170
DeviceSource.saved(this.documents, this.doc, int size)171
: _modified = doc.modified,172
_size = size;174
@override175
String get name => doc.name;176
@override177
String get location => doc.location ?? doc.name;178
@override179
String get kind => 'device';180
@override181
String get key => 'device:${doc.id}';182
@override183
bool get canSave => doc.writable;184
@override185
String? get readOnlyReason => doc.writable ? null : 'readOnlyDevice';187
@override188
Future<Uint8List> load({int maxBytes = maxEditableBytes}) async {189
doc = await documents.stat(doc);190
if ((doc.size ?? 0) > maxBytes) throw const Failure('fileTooLarge');191
final bytes = await documents.read(doc);192
if (bytes.length > maxBytes) throw const Failure('fileTooLarge');193
_modified = doc.modified;194
_size = bytes.length;195
return bytes;196
}198
@override199
Future<void> save(200
Uint8List bytes, {201
bool force = false,202
bool inPlace = false,203
}) async {204
if (!force && _modified != null) {205
final now = await documents.stat(doc);206
if (now.modified != _modified || now.size != _size) {207
throw const Failure('documentChanged');208
}209
}210
doc = await documents.write(doc, bytes);211
_modified = doc.modified;212
_size = bytes.length;213
}215
@override216
Map<String, dynamic> toJson() => {'kind': kind, 'doc': doc.toJson()};218
static DeviceSource fromJson(Map<String, dynamic> m, DocumentPlatform d) =>219
DeviceSource(d, DeviceDocument.fromMap(m['doc'] as Map));220
}222
/// Rebuilds a source saved in a draft or the recent list.223
FileSource sourceFromJson(224
Map<String, dynamic> m, {225
required DocumentPlatform documents,226
required Future<SftpService> Function(String profileId) sftpFor,227
String fallbackName = 'untitled.txt',228
}) => switch (m['kind']) {229
'remote' => RemoteSource.fromJson(m, () => sftpFor(m['profileId'] as String)),230
'device' => DeviceSource.fromJson(m, documents),231
_ => NewSource(m['name'] as String? ?? fallbackName),232
};234
/// A new file that has not been saved anywhere yet.235
class NewSource implements FileSource {236
@override237
final String name;238
NewSource(this.name);239
@override240
String get location => name;241
@override242
String get kind => 'new';243
@override244
String get key => 'new:$name';245
@override246
bool get canSave => false;247
@override248
String? get readOnlyReason => null;249
@override250
Future<Uint8List> load({int maxBytes = maxEditableBytes}) async =>251
Uint8List(0);252
@override253
Future<void> save(254
Uint8List bytes, {255
bool force = false,256
bool inPlace = false,257
}) async => throw const Failure('saveAsRequired');258
@override259
Map<String, dynamic> toJson() => {'kind': kind, 'name': name};260
}SHA-256
e349f41188fac88f2f132ca14fdd6d25170c9ddeaddd28d1af2fadf0711acd1fCH-J Proprietary Software License 1.14 · Ibinibigay ang source sa ilalim ng CH-J Proprietary Software License 1.14. Hindi binabago ng pagiging available nito ang mga tuntunin ng lisensya at hindi ito nagbibigay ng karagdagang karapatan.
SHA-256 ng archive
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655