mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-06 03:31:02 -06:00
[client] copy conflict handling
This commit is contained in:
@@ -25,6 +25,7 @@ class ResourceCopyAction extends ResourceCreateAction with JsonApiAction {
|
||||
final int contentLength;
|
||||
final String contentSha256;
|
||||
final DateTime timestamp;
|
||||
final String? deletedId;
|
||||
|
||||
ResourceCopyAction({
|
||||
required this.srcId,
|
||||
@@ -39,6 +40,7 @@ class ResourceCopyAction extends ResourceCreateAction with JsonApiAction {
|
||||
required this.contentSha256,
|
||||
required this.description,
|
||||
required this.timestamp,
|
||||
required this.deletedId,
|
||||
});
|
||||
|
||||
factory ResourceCopyAction.fromMap(Map<String, dynamic> map) {
|
||||
@@ -55,6 +57,7 @@ class ResourceCopyAction extends ResourceCreateAction with JsonApiAction {
|
||||
contentSha256: map['contentSha256'],
|
||||
description: map['description'],
|
||||
timestamp: DateTime.fromMillisecondsSinceEpoch(map['timestamp']),
|
||||
deletedId: map['deletedId'],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,6 +75,7 @@ class ResourceCopyAction extends ResourceCreateAction with JsonApiAction {
|
||||
'contentSha256': contentSha256,
|
||||
'description': description,
|
||||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||||
'deletedId': deletedId,
|
||||
};
|
||||
|
||||
@override
|
||||
@@ -101,11 +105,18 @@ class ResourceCopyAction extends ResourceCreateAction with JsonApiAction {
|
||||
),
|
||||
);
|
||||
}
|
||||
if (deletedId != null) {
|
||||
await account.db.updateResource(deletedId!, (u) => u(parent: Value(null)));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> revertOptimisticUpdate() {
|
||||
return account.db.deleteResource(resourceId);
|
||||
Future<void> revertOptimisticUpdate() async {
|
||||
await account.db.deleteResource(resourceId);
|
||||
if (deletedId != null) {
|
||||
// TODO: This should be reverted based on server data
|
||||
await account.db.updateResource(deletedId!, (u) => u(parent: Value(parent)));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -43,41 +43,6 @@ class ResourceRepository {
|
||||
});
|
||||
}
|
||||
|
||||
Future<String> copy({
|
||||
required Resource resource,
|
||||
required String destination,
|
||||
}) async {
|
||||
String name = resource.name;
|
||||
if (resource.parent == destination) {
|
||||
name = '${basenameWithoutExtension(name)} - copy${extension(name)}';
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
String localName = name;
|
||||
var existing = await account.db.getResourcesByName(parent: destination, name: name);
|
||||
while (existing.isNotEmpty) {
|
||||
counter++;
|
||||
localName = '${basenameWithoutExtension(name)} ${counter++}${extension(name)}';
|
||||
existing = await account.db.getResourcesByName(parent: destination, name: localName);
|
||||
}
|
||||
String id = generateUuid();
|
||||
await account.addAction(ResourceCopyAction(
|
||||
srcId: resource.id,
|
||||
resourceId: id,
|
||||
parent: destination,
|
||||
resourceName: name,
|
||||
conflictResolution: nameConflictRename,
|
||||
dir: resource.dir,
|
||||
localName: localName,
|
||||
contentType: resource.contentType,
|
||||
contentLength: resource.contentLength,
|
||||
contentSha256: resource.contentSha256,
|
||||
description: 'Copying ${resource.name}',
|
||||
timestamp: DateTime.now(),
|
||||
));
|
||||
return id;
|
||||
}
|
||||
|
||||
Future<String> mkdir({
|
||||
required String parent,
|
||||
required String name,
|
||||
@@ -123,6 +88,52 @@ class ResourceRepository {
|
||||
return id;
|
||||
}
|
||||
|
||||
Future<String> upload({
|
||||
required String parent,
|
||||
required String path,
|
||||
String? name,
|
||||
NameConflictResolution conflictResolution = nameConflictError,
|
||||
}) async {
|
||||
name ??= basename(path);
|
||||
var existing = await account.db.getResourcesByName(parent: parent, name: name);
|
||||
String localName = name;
|
||||
String id = generateUuid();
|
||||
if (existing.isNotEmpty) {
|
||||
switch (conflictResolution) {
|
||||
case nameConflictError:
|
||||
throw const NameConflictException();
|
||||
case nameConflictRename:
|
||||
int counter = 0;
|
||||
while (existing.isNotEmpty) {
|
||||
counter++;
|
||||
localName = '${basenameWithoutExtension(name)} ${counter++}${extension(name)}';
|
||||
existing = await account.db.getResourcesByName(parent: parent, name: localName);
|
||||
}
|
||||
break;
|
||||
case nameConflictOverwrite:
|
||||
id = existing.first.id;
|
||||
// TODO: Fix if existing resource is directory
|
||||
default:
|
||||
throw UnsupportedError('Unsupported conflict resolution');
|
||||
}
|
||||
}
|
||||
final f = File(path);
|
||||
final size = await f.length();
|
||||
final contentType = lookupMimeType(path) ?? 'application/octet-stream';
|
||||
await account.addAction(ResourceUploadAction(
|
||||
resourceId: id,
|
||||
parent: parent,
|
||||
resourceName: name,
|
||||
conflictResolution: conflictResolution,
|
||||
timestamp: DateTime.now(),
|
||||
path: path,
|
||||
size: size,
|
||||
contentType: contentType,
|
||||
localName: localName,
|
||||
));
|
||||
return id;
|
||||
}
|
||||
|
||||
Future<String> move({
|
||||
required Resource resource,
|
||||
required NameConflictResolution conflictResolution,
|
||||
@@ -169,16 +180,21 @@ class ResourceRepository {
|
||||
return resource.id;
|
||||
}
|
||||
|
||||
Future<String> upload({
|
||||
Future<String> copy({
|
||||
required Resource resource,
|
||||
required String parent,
|
||||
required String path,
|
||||
required NameConflictResolution conflictResolution,
|
||||
String? name,
|
||||
NameConflictResolution conflictResolution = nameConflictError,
|
||||
}) async {
|
||||
name ??= basename(path);
|
||||
var existing = await account.db.getResourcesByName(parent: parent, name: name);
|
||||
String localName = name;
|
||||
name ??= resource.name;
|
||||
if (parent == resource.parent && name == resource.name) {
|
||||
name = '${basenameWithoutExtension(name)} - copy${extension(name)}';
|
||||
}
|
||||
|
||||
String id = generateUuid();
|
||||
var existing = await account.db.getResourcesByName(parent: parent, name: name);
|
||||
String? deletedId;
|
||||
String localName = name;
|
||||
if (existing.isNotEmpty) {
|
||||
switch (conflictResolution) {
|
||||
case nameConflictError:
|
||||
@@ -192,24 +208,28 @@ class ResourceRepository {
|
||||
}
|
||||
break;
|
||||
case nameConflictOverwrite:
|
||||
id = existing.first.id;
|
||||
case nameConflictDelete:
|
||||
conflictResolution = nameConflictDelete;
|
||||
deletedId = existing.first.id;
|
||||
break;
|
||||
default:
|
||||
throw UnsupportedError('Unsupported conflict resolution');
|
||||
}
|
||||
}
|
||||
final f = File(path);
|
||||
final size = await f.length();
|
||||
final contentType = lookupMimeType(path) ?? 'application/octet-stream';
|
||||
await account.addAction(ResourceUploadAction(
|
||||
await account.addAction(ResourceCopyAction(
|
||||
srcId: resource.id,
|
||||
resourceId: id,
|
||||
parent: parent,
|
||||
resourceName: name,
|
||||
conflictResolution: conflictResolution,
|
||||
timestamp: DateTime.now(),
|
||||
path: path,
|
||||
size: size,
|
||||
contentType: contentType,
|
||||
conflictResolution: nameConflictRename,
|
||||
dir: resource.dir,
|
||||
localName: localName,
|
||||
contentType: resource.contentType,
|
||||
contentLength: resource.contentLength,
|
||||
contentSha256: resource.contentSha256,
|
||||
description: 'Copying ${resource.name}',
|
||||
timestamp: DateTime.now(),
|
||||
deletedId: deletedId,
|
||||
));
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ class ExplorerActions extends StatelessWidget {
|
||||
handleNameConflict(
|
||||
context,
|
||||
name,
|
||||
deleteButtonText: 'Overwrite',
|
||||
(name, conflictResolution) => account.resourceRepository.move(
|
||||
resource: r,
|
||||
name: name,
|
||||
|
||||
@@ -60,6 +60,7 @@ void handlePasteAction(PasteFromClipboardIntent i, BuildContext context) async {
|
||||
handleNameConflict(
|
||||
context,
|
||||
r.name,
|
||||
deleteButtonText: 'Overwrite',
|
||||
(name, conflictResolution) => account.resourceRepository.move(
|
||||
resource: r,
|
||||
name: name,
|
||||
@@ -69,7 +70,17 @@ void handlePasteAction(PasteFromClipboardIntent i, BuildContext context) async {
|
||||
);
|
||||
}
|
||||
for (final r in copyResources) {
|
||||
account.resourceRepository.copy(resource: r, destination: folderId);
|
||||
handleNameConflict(
|
||||
context,
|
||||
r.name,
|
||||
deleteButtonText: 'Overwrite',
|
||||
(name, conflictResolution) => account.resourceRepository.copy(
|
||||
resource: r,
|
||||
name: name,
|
||||
parent: folderId,
|
||||
conflictResolution: conflictResolution,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await SystemClipboard.instance?.write([]);
|
||||
|
||||
@@ -55,6 +55,7 @@ class _ResourceDragTargetState extends State<ResourceDragTarget> {
|
||||
handleNameConflict(
|
||||
context,
|
||||
r.name,
|
||||
deleteButtonText: 'Overwrite',
|
||||
(name, conflictResolution) => account.resourceRepository.move(
|
||||
resource: r,
|
||||
name: name,
|
||||
|
||||
@@ -50,6 +50,7 @@ void handleOption(BuildContext context, Iterable<Resource> resources, MenuOption
|
||||
await handleNameConflict(
|
||||
context,
|
||||
r.name,
|
||||
deleteButtonText: 'Overwrite',
|
||||
(name, conflictResolution) => account.resourceRepository.move(
|
||||
resource: r,
|
||||
name: name,
|
||||
@@ -70,6 +71,7 @@ void handleOption(BuildContext context, Iterable<Resource> resources, MenuOption
|
||||
await handleNameConflict(
|
||||
context,
|
||||
r.name,
|
||||
deleteButtonText: 'Overwrite',
|
||||
(name, conflictResolution) => account.resourceRepository.move(
|
||||
resource: r,
|
||||
name: name,
|
||||
@@ -86,10 +88,18 @@ void handleOption(BuildContext context, Iterable<Resource> resources, MenuOption
|
||||
if (dest == null) {
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
for (final r in resources) {
|
||||
account.resourceRepository.copy(
|
||||
resource: r,
|
||||
destination: dest,
|
||||
await handleNameConflict(
|
||||
context,
|
||||
r.name,
|
||||
deleteButtonText: 'Overwrite',
|
||||
(name, conflictResolution) => account.resourceRepository.copy(
|
||||
resource: r,
|
||||
name: name,
|
||||
parent: dest,
|
||||
conflictResolution: conflictResolution,
|
||||
),
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -28,7 +28,7 @@ Future<void> createDirectory(BuildContext context, String folderId, {String? pre
|
||||
validate: validateName,
|
||||
);
|
||||
if (name == null || !context.mounted) return;
|
||||
handleNameConflict(
|
||||
await handleNameConflict(
|
||||
context,
|
||||
name,
|
||||
deleteButtonText: 'Overwrite',
|
||||
|
||||
Reference in New Issue
Block a user