diff --git a/packages/neon/neon_files/lib/blocs/browser.dart b/packages/neon/neon_files/lib/blocs/browser.dart index 80c97062..d6c26515 100644 --- a/packages/neon/neon_files/lib/blocs/browser.dart +++ b/packages/neon/neon_files/lib/blocs/browser.dart @@ -1,25 +1,25 @@ part of '../neon_files.dart'; abstract interface class FilesBrowserBlocEvents { - void setPath(final List path); + void setPath(final PathUri uri); - void createFolder(final List path); + void createFolder(final PathUri uri); } abstract interface class FilesBrowserBlocStates { BehaviorSubject>> get files; - BehaviorSubject> get path; + BehaviorSubject get uri; } class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents, FilesBrowserBlocStates { FilesBrowserBloc( this.options, this.account, { - final List? initialPath, + final PathUri? initialPath, }) { if (initialPath != null) { - path.add(initialPath); + uri.add(initialPath); } unawaited(refresh()); @@ -31,7 +31,7 @@ class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents @override void dispose() { unawaited(files.close()); - unawaited(path.close()); + unawaited(uri.close()); super.dispose(); } @@ -39,16 +39,16 @@ class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents BehaviorSubject>> files = BehaviorSubject>>(); @override - BehaviorSubject> path = BehaviorSubject>.seeded([]); + BehaviorSubject uri = BehaviorSubject.seeded(PathUri.cwd()); @override Future refresh() async { await RequestManager.instance.wrapWebDav>( account.id, - 'files-${path.value.join('/')}', + 'files-${uri.value.path}', files, () => account.client.webdav.propfind( - Uri(pathSegments: path.value), + uri.value, prop: WebDavPropWithoutValues.fromBools( davgetcontenttype: true, davgetetag: true, @@ -65,13 +65,13 @@ class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents } @override - void setPath(final List p) { - path.add(p); + void setPath(final PathUri uri) { + this.uri.add(uri); unawaited(refresh()); } @override - void createFolder(final List path) { - wrapAction(() async => account.client.webdav.mkcol(Uri(pathSegments: path))); + void createFolder(final PathUri uri) { + wrapAction(() async => account.client.webdav.mkcol(uri)); } } diff --git a/packages/neon/neon_files/lib/blocs/files.dart b/packages/neon/neon_files/lib/blocs/files.dart index 2711c40b..5ec0f9d5 100644 --- a/packages/neon/neon_files/lib/blocs/files.dart +++ b/packages/neon/neon_files/lib/blocs/files.dart @@ -1,25 +1,25 @@ part of '../neon_files.dart'; abstract interface class FilesBlocEvents { - void uploadFile(final List path, final String localPath); + void uploadFile(final PathUri uri, final String localPath); - void syncFile(final List path); + void syncFile(final PathUri uri); - void openFile(final List path, final String etag, final String? mimeType); + void openFile(final PathUri uri, final String etag, final String? mimeType); - void shareFileNative(final List path, final String etag); + void shareFileNative(final PathUri uri, final String etag); - void delete(final List path); + void delete(final PathUri uri); - void rename(final List path, final String name); + void rename(final PathUri uri, final String name); - void move(final List path, final List destination); + void move(final PathUri uri, final PathUri destination); - void copy(final List path, final List destination); + void copy(final PathUri uri, final PathUri destination); - void addFavorite(final List path); + void addFavorite(final PathUri uri); - void removeFavorite(final List path); + void removeFavorite(final PathUri uri); } abstract interface class FilesBlocStates { @@ -58,35 +58,35 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta BehaviorSubject> tasks = BehaviorSubject>.seeded([]); @override - void addFavorite(final List path) { + void addFavorite(final PathUri uri) { wrapAction( () async => account.client.webdav.proppatch( - Uri(pathSegments: path), + uri, set: WebDavProp(ocfavorite: 1), ), ); } @override - void copy(final List path, final List destination) { - wrapAction(() async => account.client.webdav.copy(Uri(pathSegments: path), Uri(pathSegments: destination))); + void copy(final PathUri uri, final PathUri destination) { + wrapAction(() async => account.client.webdav.copy(uri, destination)); } @override - void delete(final List path) { - wrapAction(() async => account.client.webdav.delete(Uri(pathSegments: path))); + void delete(final PathUri uri) { + wrapAction(() async => account.client.webdav.delete(uri)); } @override - void move(final List path, final List destination) { - wrapAction(() async => account.client.webdav.move(Uri(pathSegments: path), Uri(pathSegments: destination))); + void move(final PathUri uri, final PathUri destination) { + wrapAction(() async => account.client.webdav.move(uri, destination)); } @override - void openFile(final List path, final String etag, final String? mimeType) { + void openFile(final PathUri uri, final String etag, final String? mimeType) { wrapAction( () async { - final file = await _cacheFile(path, etag); + final file = await _cacheFile(uri, etag); final result = await OpenFile.open(file.path, type: mimeType); if (result.type != ResultType.done) { @@ -98,10 +98,10 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta } @override - void shareFileNative(final List path, final String etag) { + void shareFileNative(final PathUri uri, final String etag) { wrapAction( () async { - final file = await _cacheFile(path, etag); + final file = await _cacheFile(uri, etag); await Share.shareXFiles([XFile(file.path)]); }, @@ -115,52 +115,52 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta } @override - void removeFavorite(final List path) { + void removeFavorite(final PathUri uri) { wrapAction( () async => account.client.webdav.proppatch( - Uri(pathSegments: path), + uri, set: WebDavProp(ocfavorite: 0), ), ); } @override - void rename(final List path, final String name) { + void rename(final PathUri uri, final String name) { wrapAction( () async => account.client.webdav.move( - Uri(pathSegments: path), - Uri(pathSegments: List.from(path)..last = name), + uri, + uri.rename(name), ), ); } @override - void syncFile(final List path) { + void syncFile(final PathUri uri) { wrapAction( () async { final file = File( - p.join( + p.joinAll([ await NeonPlatform.instance.userAccessibleAppDataPath, account.humanReadableID, 'files', - path.join(Platform.pathSeparator), - ), + ...uri.pathSegments, + ]), ); if (!file.parent.existsSync()) { file.parent.createSync(recursive: true); } - await _downloadFile(path, file); + await _downloadFile(uri, file); }, disableTimeout: true, ); } @override - void uploadFile(final List path, final String localPath) { + void uploadFile(final PathUri uri, final String localPath) { wrapAction( () async { final task = FilesUploadTask( - path: path, + uri: uri, file: File(localPath), ); tasks.add(tasks.value..add(task)); @@ -171,27 +171,27 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta ); } - Future _cacheFile(final List path, final String etag) async { + Future _cacheFile(final PathUri uri, final String etag) async { final cacheDir = await getApplicationCacheDirectory(); - final file = File(p.join(cacheDir.path, 'files', etag.replaceAll('"', ''), path.last)); + final file = File(p.join(cacheDir.path, 'files', etag.replaceAll('"', ''), uri.name)); if (!file.existsSync()) { - debugPrint('Downloading ${Uri(pathSegments: path)} since it does not exist'); + debugPrint('Downloading $uri since it does not exist'); if (!file.parent.existsSync()) { await file.parent.create(recursive: true); } - await _downloadFile(path, file); + await _downloadFile(uri, file); } return file; } Future _downloadFile( - final List path, + final PathUri uri, final File file, ) async { final task = FilesDownloadTask( - path: path, + uri: uri, file: file, ); tasks.add(tasks.value..add(task)); @@ -200,12 +200,12 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta } FilesBrowserBloc getNewFilesBrowserBloc({ - final List? initialPath, + final PathUri? initialUri, }) => FilesBrowserBloc( options, account, - initialPath: initialPath, + initialPath: initialUri, ); void _downloadParallelismListener() { diff --git a/packages/neon/neon_files/lib/dialogs/choose_create.dart b/packages/neon/neon_files/lib/dialogs/choose_create.dart index 38ad27d5..683fc80d 100644 --- a/packages/neon/neon_files/lib/dialogs/choose_create.dart +++ b/packages/neon/neon_files/lib/dialogs/choose_create.dart @@ -8,7 +8,7 @@ class FilesChooseCreateDialog extends StatefulWidget { }); final FilesBloc bloc; - final List basePath; + final PathUri basePath; @override State createState() => _FilesChooseCreateDialogState(); @@ -43,7 +43,10 @@ class _FilesChooseCreateDialogState extends State { } } } - widget.bloc.uploadFile([...widget.basePath, p.basename(file.path)], file.path); + widget.bloc.uploadFile( + widget.basePath.join(PathUri.parse(p.basename(file.path))), + file.path, + ); } @override @@ -104,12 +107,12 @@ class _FilesChooseCreateDialogState extends State { onTap: () async { Navigator.of(context).pop(); - final result = await showDialog>( + final result = await showDialog( context: context, builder: (final context) => const FilesCreateFolderDialog(), ); if (result != null) { - widget.bloc.browser.createFolder([...widget.basePath, ...result]); + widget.bloc.browser.createFolder(widget.basePath.join(PathUri.parse(result))); } }, ), diff --git a/packages/neon/neon_files/lib/dialogs/choose_folder.dart b/packages/neon/neon_files/lib/dialogs/choose_folder.dart index da5f2e7a..d0b79cc9 100644 --- a/packages/neon/neon_files/lib/dialogs/choose_folder.dart +++ b/packages/neon/neon_files/lib/dialogs/choose_folder.dart @@ -11,7 +11,7 @@ class FilesChooseFolderDialog extends StatelessWidget { final FilesBrowserBloc bloc; final FilesBloc filesBloc; - final List originalPath; + final PathUri originalPath; @override Widget build(final BuildContext context) => AlertDialog( @@ -28,9 +28,9 @@ class FilesChooseFolderDialog extends StatelessWidget { mode: FilesBrowserMode.selectDirectory, ), ), - StreamBuilder>( - stream: bloc.path, - builder: (final context, final pathSnapshot) => pathSnapshot.hasData + StreamBuilder( + stream: bloc.uri, + builder: (final context, final uriSnapshot) => uriSnapshot.hasData ? Container( margin: const EdgeInsets.all(10), child: Row( @@ -38,19 +38,19 @@ class FilesChooseFolderDialog extends StatelessWidget { children: [ ElevatedButton( onPressed: () async { - final result = await showDialog>( + final result = await showDialog( context: context, builder: (final context) => const FilesCreateFolderDialog(), ); if (result != null) { - bloc.createFolder([...pathSnapshot.requireData, ...result]); + bloc.createFolder(uriSnapshot.requireData.join(PathUri.parse(result))); } }, child: Text(FilesLocalizations.of(context).folderCreate), ), ElevatedButton( - onPressed: !(const ListEquality().equals(originalPath, pathSnapshot.data)) - ? () => Navigator.of(context).pop(pathSnapshot.data) + onPressed: originalPath != uriSnapshot.requireData + ? () => Navigator.of(context).pop(uriSnapshot.requireData) : null, child: Text(FilesLocalizations.of(context).folderChoose), ), diff --git a/packages/neon/neon_files/lib/dialogs/create_folder.dart b/packages/neon/neon_files/lib/dialogs/create_folder.dart index 45083184..3a00d968 100644 --- a/packages/neon/neon_files/lib/dialogs/create_folder.dart +++ b/packages/neon/neon_files/lib/dialogs/create_folder.dart @@ -22,7 +22,7 @@ class _FilesCreateFolderDialogState extends State { void submit() { if (formKey.currentState!.validate()) { - Navigator.of(context).pop(controller.text.split('/')); + Navigator.of(context).pop(controller.text); } } diff --git a/packages/neon/neon_files/lib/models/file_details.dart b/packages/neon/neon_files/lib/models/file_details.dart index c0d61b28..151c905a 100644 --- a/packages/neon/neon_files/lib/models/file_details.dart +++ b/packages/neon/neon_files/lib/models/file_details.dart @@ -3,8 +3,7 @@ part of '../neon_files.dart'; @immutable class FileDetails { const FileDetails({ - required this.path, - required this.isDirectory, + required this.uri, required this.size, required this.etag, required this.mimeType, @@ -15,9 +14,7 @@ class FileDetails { FileDetails.fromWebDav({ required final WebDavFile file, - required final List path, - }) : path = List.from(path)..add(file.name), - isDirectory = file.isDirectory, + }) : uri = file.path, size = file.size, etag = file.etag, mimeType = file.mimeType, @@ -28,10 +25,9 @@ class FileDetails { FileDetails.fromUploadTask({ required FilesUploadTask this.task, - }) : path = task.path, + }) : uri = task.uri, size = task.stat.size, lastModified = task.stat.modified, - isDirectory = false, etag = null, mimeType = null, hasPreview = null, @@ -40,8 +36,7 @@ class FileDetails { FileDetails.fromDownloadTask({ required FilesDownloadTask this.task, required final WebDavFile file, - }) : path = task.path, - isDirectory = file.isDirectory, + }) : uri = task.uri, size = file.size, etag = file.etag, mimeType = file.mimeType, @@ -64,11 +59,11 @@ class FileDetails { } } - String get name => path.last; + String get name => uri.name; - final List path; + bool get isDirectory => uri.isDirectory; - final bool isDirectory; + final PathUri uri; final int? size; diff --git a/packages/neon/neon_files/lib/pages/details.dart b/packages/neon/neon_files/lib/pages/details.dart index 36d50c1f..1fb62443 100644 --- a/packages/neon/neon_files/lib/pages/details.dart +++ b/packages/neon/neon_files/lib/pages/details.dart @@ -43,8 +43,8 @@ class FilesDetailsPage extends StatelessWidget { details.isDirectory ? FilesLocalizations.of(context).detailsFolderName : FilesLocalizations.of(context).detailsFileName: details.name, - FilesLocalizations.of(context).detailsParentFolder: - details.path.length == 1 ? '/' : details.path.sublist(0, details.path.length - 1).join('/'), + if (details.uri.parent != null) + FilesLocalizations.of(context).detailsParentFolder: details.uri.parent!.path, if (details.size != null) ...{ details.isDirectory ? FilesLocalizations.of(context).detailsFolderSize diff --git a/packages/neon/neon_files/lib/pages/main.dart b/packages/neon/neon_files/lib/pages/main.dart index 4e55a0c0..abe835a7 100644 --- a/packages/neon/neon_files/lib/pages/main.dart +++ b/packages/neon/neon_files/lib/pages/main.dart @@ -34,7 +34,7 @@ class _FilesMainPageState extends State { context: context, builder: (final context) => FilesChooseCreateDialog( bloc: bloc, - basePath: bloc.browser.path.value, + basePath: bloc.browser.uri.value, ), ); }, diff --git a/packages/neon/neon_files/lib/utils/task.dart b/packages/neon/neon_files/lib/utils/task.dart index f4324237..f2248294 100644 --- a/packages/neon/neon_files/lib/utils/task.dart +++ b/packages/neon/neon_files/lib/utils/task.dart @@ -2,11 +2,11 @@ part of '../neon_files.dart'; sealed class FilesTask { FilesTask({ - required this.path, + required this.uri, required this.file, }); - final List path; + final PathUri uri; final File file; @@ -19,13 +19,13 @@ sealed class FilesTask { class FilesDownloadTask extends FilesTask { FilesDownloadTask({ - required super.path, + required super.uri, required super.file, }); Future execute(final NextcloudClient client) async { await client.webdav.getFile( - Uri(pathSegments: path), + uri, file, onProgress: streamController.add, ); @@ -35,7 +35,7 @@ class FilesDownloadTask extends FilesTask { class FilesUploadTask extends FilesTask { FilesUploadTask({ - required super.path, + required super.uri, required super.file, }); @@ -46,7 +46,7 @@ class FilesUploadTask extends FilesTask { await client.webdav.putFile( file, stat, - Uri(pathSegments: path), + uri, lastModified: stat.modified, onProgress: streamController.add, ); diff --git a/packages/neon/neon_files/lib/widgets/actions.dart b/packages/neon/neon_files/lib/widgets/actions.dart index aa9eb789..bbe1656b 100644 --- a/packages/neon/neon_files/lib/widgets/actions.dart +++ b/packages/neon/neon_files/lib/widgets/actions.dart @@ -4,6 +4,7 @@ import 'package:neon/platform.dart'; import 'package:neon/utils.dart'; import 'package:neon_files/l10n/localizations.dart'; import 'package:neon_files/neon_files.dart'; +import 'package:nextcloud/webdav.dart'; class FileActions extends StatelessWidget { const FileActions({ @@ -18,12 +19,12 @@ class FileActions extends StatelessWidget { final browserBloc = bloc.browser; switch (action) { case FilesFileAction.share: - bloc.shareFileNative(details.path, details.etag!); + bloc.shareFileNative(details.uri, details.etag!); case FilesFileAction.toggleFavorite: if (details.isFavorite ?? false) { - bloc.removeFavorite(details.path); + bloc.removeFavorite(details.uri); } else { - bloc.addFavorite(details.path); + bloc.addFavorite(details.uri); } case FilesFileAction.details: await Navigator.of(context).push( @@ -46,15 +47,15 @@ class FileActions extends StatelessWidget { value: details.name, ); if (result != null) { - bloc.rename(details.path, result); + bloc.rename(details.uri, result); } case FilesFileAction.move: if (!context.mounted) { return; } - final originalPath = details.path.sublist(0, details.path.length - 1); - final b = bloc.getNewFilesBrowserBloc(initialPath: originalPath); - final result = await showDialog?>( + final originalPath = details.uri.parent!; + final b = bloc.getNewFilesBrowserBloc(initialUri: originalPath); + final result = await showDialog( context: context, builder: (final context) => FilesChooseFolderDialog( bloc: b, @@ -64,15 +65,15 @@ class FileActions extends StatelessWidget { ); b.dispose(); if (result != null) { - bloc.move(details.path, result..add(details.name)); + bloc.move(details.uri, result.join(PathUri.parse(details.name))); } case FilesFileAction.copy: if (!context.mounted) { return; } - final originalPath = details.path.sublist(0, details.path.length - 1); - final b = bloc.getNewFilesBrowserBloc(initialPath: originalPath); - final result = await showDialog?>( + final originalPath = details.uri.parent!; + final b = bloc.getNewFilesBrowserBloc(initialUri: originalPath); + final result = await showDialog( context: context, builder: (final context) => FilesChooseFolderDialog( bloc: b, @@ -82,7 +83,7 @@ class FileActions extends StatelessWidget { ); b.dispose(); if (result != null) { - bloc.copy(details.path, result..add(details.name)); + bloc.copy(details.uri, result.join(PathUri.parse(details.name))); } case FilesFileAction.sync: if (!context.mounted) { @@ -100,7 +101,7 @@ class FileActions extends StatelessWidget { return; } } - bloc.syncFile(details.path); + bloc.syncFile(details.uri); case FilesFileAction.delete: if (!context.mounted) { return; @@ -111,7 +112,7 @@ class FileActions extends StatelessWidget { ? FilesLocalizations.of(context).folderDeleteConfirm(details.name) : FilesLocalizations.of(context).fileDeleteConfirm(details.name), )) { - bloc.delete(details.path); + bloc.delete(details.uri); } } } diff --git a/packages/neon/neon_files/lib/widgets/browser_view.dart b/packages/neon/neon_files/lib/widgets/browser_view.dart index e8f13861..1a2c4114 100644 --- a/packages/neon/neon_files/lib/widgets/browser_view.dart +++ b/packages/neon/neon_files/lib/widgets/browser_view.dart @@ -43,12 +43,12 @@ class _FilesBrowserViewState extends State { @override Widget build(final BuildContext context) => ResultBuilder>.behaviorSubject( subject: widget.bloc.files, - builder: (final context, final filesSnapshot) => StreamBuilder>( - stream: widget.bloc.path, - builder: (final context, final pathSnapshot) => StreamBuilder>( + builder: (final context, final filesSnapshot) => StreamBuilder( + stream: widget.bloc.uri, + builder: (final context, final uriSnapshot) => StreamBuilder>( stream: widget.filesBloc.tasks, builder: (final context, final tasksSnapshot) { - if (!pathSnapshot.hasData || !tasksSnapshot.hasData) { + if (!uriSnapshot.hasData || !tasksSnapshot.hasData) { return const SizedBox(); } return ValueListenableBuilder( @@ -68,9 +68,9 @@ class _FilesBrowserViewState extends State { return BackButtonListener( onBackButtonPressed: () async { - final path = pathSnapshot.requireData; - if (path.isNotEmpty) { - widget.bloc.setPath(path.sublist(0, path.length - 1)); + final parent = uriSnapshot.requireData.parent; + if (parent != null) { + widget.bloc.setPath(parent); return true; } return false; @@ -87,12 +87,13 @@ class _FilesBrowserViewState extends State { final uploadingTaskTiles = buildUploadTasks(tasksSnapshot.requireData, sorted); return NeonListView( - scrollKey: 'files-${pathSnapshot.requireData.join('/')}', + scrollKey: 'files-${uriSnapshot.requireData.path}', itemCount: sorted.length, itemBuilder: (final context, final index) { final file = sorted[index]; - final matchingTask = tasksSnapshot.requireData - .firstWhereOrNull((final task) => _pathMatchesFile(task.path, file.name)); + final matchingTask = tasksSnapshot.requireData.firstWhereOrNull( + (final task) => file.name == task.uri.name && widget.bloc.uri.value == task.uri.parent, + ); final details = matchingTask != null ? FileDetails.fromTask( @@ -101,7 +102,6 @@ class _FilesBrowserViewState extends State { ) : FileDetails.fromWebDav( file: file, - path: widget.bloc.path.value, ); return FileListTile( @@ -116,7 +116,7 @@ class _FilesBrowserViewState extends State { onRefresh: widget.bloc.refresh, topScrollingChildren: [ FilesBrowserNavigator( - path: pathSnapshot.requireData, + uri: uriSnapshot.requireData, bloc: widget.bloc, ), ...uploadingTaskTiles, @@ -147,9 +147,4 @@ class _FilesBrowserViewState extends State { ); } } - - bool _pathMatchesFile(final List path, final String name) => const ListEquality().equals( - [...widget.bloc.path.value, name], - path, - ); } diff --git a/packages/neon/neon_files/lib/widgets/file_list_tile.dart b/packages/neon/neon_files/lib/widgets/file_list_tile.dart index 85426f9f..5d9a04b2 100644 --- a/packages/neon/neon_files/lib/widgets/file_list_tile.dart +++ b/packages/neon/neon_files/lib/widgets/file_list_tile.dart @@ -24,7 +24,7 @@ class FileListTile extends StatelessWidget { Future _onTap(final BuildContext context, final FileDetails details) async { if (details.isDirectory) { - browserBloc.setPath(details.path); + browserBloc.setPath(details.uri); } else if (mode == FilesBrowserMode.browser) { final sizeWarning = bloc.options.downloadSizeWarning.value; if (sizeWarning != null && details.size != null && details.size! > sizeWarning) { @@ -38,7 +38,7 @@ class FileListTile extends StatelessWidget { return; } } - bloc.openFile(details.path, details.etag!, details.mimeType); + bloc.openFile(details.uri, details.etag!, details.mimeType); } } diff --git a/packages/neon/neon_files/lib/widgets/file_preview.dart b/packages/neon/neon_files/lib/widgets/file_preview.dart index 02865546..6e96dc13 100644 --- a/packages/neon/neon_files/lib/widgets/file_preview.dart +++ b/packages/neon/neon_files/lib/widgets/file_preview.dart @@ -76,14 +76,12 @@ class FilePreviewImage extends NeonApiImage { }) { final width = size.width.toInt(); final height = size.height.toInt(); - final path = file.path.join('/'); - final cacheKey = 'preview-$path-$width-$height'; + final cacheKey = 'preview-${file.uri.path}-$width-$height'; return FilePreviewImage._( file: file, size: size, cacheKey: cacheKey, - path: path, width: width, height: height, ); @@ -93,12 +91,11 @@ class FilePreviewImage extends NeonApiImage { required final FileDetails file, required Size super.size, required super.cacheKey, - required final String path, required final int width, required final int height, }) : super( getImage: (final client) async => client.core.preview.getPreview( - file: path, + file: file.uri.path, x: width, y: height, ), diff --git a/packages/neon/neon_files/lib/widgets/navigator.dart b/packages/neon/neon_files/lib/widgets/navigator.dart index 2e079b43..24c95dd7 100644 --- a/packages/neon/neon_files/lib/widgets/navigator.dart +++ b/packages/neon/neon_files/lib/widgets/navigator.dart @@ -2,12 +2,12 @@ part of '../neon_files.dart'; class FilesBrowserNavigator extends StatelessWidget { const FilesBrowserNavigator({ - required this.path, + required this.uri, required this.bloc, super.key, }); - final List path; + final PathUri uri; final FilesBrowserBloc bloc; @override @@ -18,7 +18,7 @@ class FilesBrowserNavigator extends StatelessWidget { horizontal: 10, ), scrollDirection: Axis.horizontal, - itemCount: path.length + 1, + itemCount: uri.pathSegments.length + 1, itemBuilder: (final context, final index) { if (index == 0) { return IconButton( @@ -30,21 +30,23 @@ class FilesBrowserNavigator extends StatelessWidget { tooltip: FilesLocalizations.of(context).goToPath(''), icon: const Icon(Icons.house), onPressed: () { - bloc.setPath([]); + bloc.setPath(PathUri.cwd()); }, ); } - final path = this.path.sublist(0, index); - final label = path.join('/'); - + final partialPath = PathUri( + isAbsolute: uri.isAbsolute, + isDirectory: uri.isDirectory, + pathSegments: uri.pathSegments.sublist(0, index), + ); return TextButton( onPressed: () { - bloc.setPath(path); + bloc.setPath(partialPath); }, child: Text( - path.last, - semanticsLabel: FilesLocalizations.of(context).goToPath(label), + partialPath.name, + semanticsLabel: FilesLocalizations.of(context).goToPath(partialPath.name), ), ); }, diff --git a/packages/nextcloud/lib/src/webdav/client.dart b/packages/nextcloud/lib/src/webdav/client.dart index 52ce8bcf..5288c506 100644 --- a/packages/nextcloud/lib/src/webdav/client.dart +++ b/packages/nextcloud/lib/src/webdav/client.dart @@ -4,13 +4,14 @@ import 'dart:typed_data'; import 'package:dynamite_runtime/http_client.dart'; import 'package:meta/meta.dart'; +import 'package:nextcloud/src/webdav/path_uri.dart'; import 'package:nextcloud/src/webdav/props.dart'; import 'package:nextcloud/src/webdav/webdav.dart'; import 'package:universal_io/io.dart'; import 'package:xml/xml.dart' as xml; /// Base path used on the server -final webdavBase = Uri(path: '/remote.php/webdav'); +final webdavBase = PathUri.parse('/remote.php/webdav'); /// WebDavClient class class WebDavClient { @@ -60,11 +61,11 @@ class WebDavClient { return response; } - Uri _constructUri([final Uri? path]) => constructUri(rootClient.baseURL, path); + Uri _constructUri([final PathUri? path]) => constructUri(rootClient.baseURL, path); @visibleForTesting // ignore: public_member_api_docs - static Uri constructUri(final Uri baseURL, [final Uri? path]) { + static Uri constructUri(final Uri baseURL, [final PathUri? path]) { final segments = baseURL.pathSegments.toList()..addAll(webdavBase.pathSegments); if (path != null) { segments.addAll(path.pathSegments); @@ -103,7 +104,7 @@ class WebDavClient { /// Creates a collection at [path]. /// /// See http://www.webdav.org/specs/rfc2518.html#METHOD_MKCOL for more information. - Future mkcol(final Uri path) async => _send( + Future mkcol(final PathUri path) async => _send( 'MKCOL', _constructUri(path), ); @@ -111,7 +112,7 @@ class WebDavClient { /// Deletes the resource at [path]. /// /// See http://www.webdav.org/specs/rfc2518.html#METHOD_DELETE for more information. - Future delete(final Uri path) => _send( + Future delete(final PathUri path) => _send( 'DELETE', _constructUri(path), ); @@ -123,7 +124,7 @@ class WebDavClient { /// See http://www.webdav.org/specs/rfc2518.html#METHOD_PUT for more information. Future put( final Uint8List localData, - final Uri path, { + final PathUri path, { final DateTime? lastModified, final DateTime? created, }) => @@ -147,7 +148,7 @@ class WebDavClient { /// See http://www.webdav.org/specs/rfc2518.html#METHOD_PUT for more information. Future putStream( final Stream localData, - final Uri path, { + final PathUri path, { final DateTime? lastModified, final DateTime? created, final int? contentLength, @@ -181,7 +182,7 @@ class WebDavClient { Future putFile( final File file, final FileStat fileStat, - final Uri path, { + final PathUri path, { final DateTime? lastModified, final DateTime? created, final void Function(double progress)? onProgress, @@ -196,17 +197,17 @@ class WebDavClient { ); /// Gets the content of the file at [path]. - Future get(final Uri path) async => (await getStream(path)).bytes; + Future get(final PathUri path) async => (await getStream(path)).bytes; /// Gets the content of the file at [path]. - Future getStream(final Uri path) async => _send( + Future getStream(final PathUri path) async => _send( 'GET', _constructUri(path), ); /// Gets the content of the file at [path]. Future getFile( - final Uri path, + final PathUri path, final File file, { final void Function(double progress)? onProgress, }) async { @@ -236,7 +237,7 @@ class WebDavClient { /// [depth] can be used to limit scope of the returned resources. /// See http://www.webdav.org/specs/rfc2518.html#METHOD_PROPFIND for more information. Future propfind( - final Uri path, { + final PathUri path, { final WebDavPropWithoutValues? prop, final WebDavDepth? depth, }) async => @@ -256,7 +257,7 @@ class WebDavClient { /// Optionally populates the [prop]s on the returned resources. /// See https://github.com/owncloud/docs/issues/359 for more information. Future report( - final Uri path, + final PathUri path, final WebDavOcFilterRules filterRules, { final WebDavPropWithoutValues? prop, }) async => @@ -280,7 +281,7 @@ class WebDavClient { /// Returns true if the update was successful. /// See http://www.webdav.org/specs/rfc2518.html#METHOD_PROPPATCH for more information. Future proppatch( - final Uri path, { + final PathUri path, { final WebDavProp? set, final WebDavPropWithoutValues? remove, }) async { @@ -310,8 +311,8 @@ class WebDavClient { /// If [overwrite] is set any existing resource will be replaced. /// See http://www.webdav.org/specs/rfc2518.html#METHOD_MOVE for more information. Future move( - final Uri sourcePath, - final Uri destinationPath, { + final PathUri sourcePath, + final PathUri destinationPath, { final bool overwrite = false, }) => _send( @@ -328,8 +329,8 @@ class WebDavClient { /// If [overwrite] is set any existing resource will be replaced. /// See http://www.webdav.org/specs/rfc2518.html#METHOD_COPY for more information. Future copy( - final Uri sourcePath, - final Uri destinationPath, { + final PathUri sourcePath, + final PathUri destinationPath, { final bool overwrite = false, }) => _send( diff --git a/packages/nextcloud/lib/src/webdav/file.dart b/packages/nextcloud/lib/src/webdav/file.dart index 7013c898..4c9bee53 100644 --- a/packages/nextcloud/lib/src/webdav/file.dart +++ b/packages/nextcloud/lib/src/webdav/file.dart @@ -1,4 +1,5 @@ import 'package:nextcloud/src/webdav/client.dart'; +import 'package:nextcloud/src/webdav/path_uri.dart'; import 'package:nextcloud/src/webdav/props.dart'; import 'package:nextcloud/src/webdav/webdav.dart'; @@ -25,8 +26,14 @@ class WebDavFile { _response.propstats.singleWhere((final propstat) => propstat.status.contains('200')).prop; /// The path of file - late final Uri path = - Uri(pathSegments: Uri(path: _response.href).pathSegments.sublist(webdavBase.pathSegments.length)); + late final PathUri path = () { + final href = PathUri.parse(Uri.decodeFull(_response.href!)); + return PathUri( + isAbsolute: false, + isDirectory: href.isDirectory, + pathSegments: href.pathSegments.sublist(webdavBase.pathSegments.length), + ); + }(); /// The fileid namespaced by the instance id, globally unique late final String? id = props.ocid; @@ -79,11 +86,11 @@ class WebDavFile { late final bool? hasPreview = props.nchaspreview; /// Returns the decoded name of the file / folder without the whole path - late final String name = path.pathSegments.where((final s) => s.isNotEmpty).lastOrNull ?? ''; + late final String name = path.name; /// Whether the file is hidden. late final bool isHidden = name.startsWith('.'); /// Whether the file is a directory - late final bool isDirectory = (isCollection ?? false) || path.pathSegments.last.isEmpty; + late final bool isDirectory = (isCollection ?? false) || path.isDirectory; } diff --git a/packages/nextcloud/lib/src/webdav/path_uri.dart b/packages/nextcloud/lib/src/webdav/path_uri.dart new file mode 100644 index 00000000..51c0a174 --- /dev/null +++ b/packages/nextcloud/lib/src/webdav/path_uri.dart @@ -0,0 +1,138 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; + +/// A `Uri` like object that is specialized in file path handling. +@immutable +class PathUri { + /// Creates a new path URI. + const PathUri({ + required this.isAbsolute, + required this.isDirectory, + required this.pathSegments, + }); + + /// Creates a new `PathUri` object by parsing a [path] string. + /// + /// An empty [path] is considered to be the current working directory. + factory PathUri.parse(final String path) { + final parts = path.split('/'); + if (parts.length == 1 && parts.single.isEmpty) { + return PathUri( + isAbsolute: false, + isDirectory: true, + pathSegments: BuiltList(), + ); + } + return PathUri( + isAbsolute: parts.first.isEmpty, + isDirectory: parts.last.isEmpty, + pathSegments: BuiltList(parts.where((final element) => element.isNotEmpty)), + ); + } + + /// Creates a new empty path URI representing the current working directory. + factory PathUri.cwd() => PathUri( + isAbsolute: false, + isDirectory: true, + pathSegments: BuiltList(), + ); + + /// Whether the path is an absolute path. + /// + /// If `true` [path] will start with a slash. + final bool isAbsolute; + + /// Whether the path is a directory. + /// + /// If `true` [path] will end with a slash. + final bool isDirectory; + + /// Returns the path as a list of its segments. + /// + /// See [path] for getting the path as a string. + final BuiltList pathSegments; + + /// Returns the path as a string. + /// + /// See [pathSegments] for getting the path as a list of its segments. + String get path { + final buffer = StringBuffer(); + if (isAbsolute) { + buffer.write('/'); + } + buffer.writeAll(pathSegments, '/'); + if (isDirectory && pathSegments.isNotEmpty) { + buffer.write('/'); + } + return buffer.toString(); + } + + /// Returns the name of the last element in path. + String get name => pathSegments.lastOrNull ?? ''; + + /// Returns the parent of the path. + PathUri? get parent { + if (pathSegments.isNotEmpty) { + return PathUri( + isAbsolute: isAbsolute, + isDirectory: true, + pathSegments: pathSegments.rebuild((final b) => b.removeLast()), + ); + } + + return null; + } + + /// Joins the current path with another [path]. + /// + /// If the current path is not a directory a [StateError] will be thrown. + /// See [isDirectory] for checking if the current path is a directory. + PathUri join(final PathUri other) { + if (!isDirectory) { + throw StateError('$this is not a directory.'); + } + + return PathUri( + isAbsolute: isAbsolute, + isDirectory: other.isDirectory, + pathSegments: pathSegments.rebuild((final b) => b.addAll(other.pathSegments)), + ); + } + + /// Renames the last path segment and returns a new path URI. + PathUri rename(final String name) { + if (name.contains('/')) { + throw Exception('Path names must not contain /'); + } + + return PathUri( + isAbsolute: isAbsolute, + isDirectory: isDirectory, + pathSegments: pathSegments.isNotEmpty + ? pathSegments.rebuild( + (final b) => b + ..removeLast() + ..add(name), + ) + : BuiltList(), + ); + } + + @override + bool operator ==(final Object other) => + other is PathUri && + isAbsolute == other.isAbsolute && + isDirectory == other.isDirectory && + pathSegments == other.pathSegments; + + @override + int get hashCode => Object.hashAll([ + isAbsolute, + isDirectory, + pathSegments, + ]); + + @override + String toString() => path; +} diff --git a/packages/nextcloud/lib/webdav.dart b/packages/nextcloud/lib/webdav.dart index ecce9168..c604cc3e 100644 --- a/packages/nextcloud/lib/webdav.dart +++ b/packages/nextcloud/lib/webdav.dart @@ -4,6 +4,7 @@ import 'package:nextcloud/src/webdav/client.dart'; export 'src/webdav/client.dart'; export 'src/webdav/file.dart'; +export 'src/webdav/path_uri.dart'; export 'src/webdav/props.dart'; export 'src/webdav/webdav.dart'; diff --git a/packages/nextcloud/test/webdav_test.dart b/packages/nextcloud/test/webdav_test.dart index 0cba20be..a77abc34 100644 --- a/packages/nextcloud/test/webdav_test.dart +++ b/packages/nextcloud/test/webdav_test.dart @@ -19,17 +19,122 @@ void main() { final baseURL = Uri.parse(values.$1); final sanitizedBaseURL = Uri.parse(values.$2); - test('$baseURL', () { - expect(WebDavClient.constructUri(baseURL).toString(), '$sanitizedBaseURL$webdavBase'); - expect(WebDavClient.constructUri(baseURL, Uri(path: '/')).toString(), '$sanitizedBaseURL$webdavBase'); - expect(WebDavClient.constructUri(baseURL, Uri(path: 'test')).toString(), '$sanitizedBaseURL$webdavBase/test'); - expect(WebDavClient.constructUri(baseURL, Uri(path: 'test/')).toString(), '$sanitizedBaseURL$webdavBase/test'); - expect(WebDavClient.constructUri(baseURL, Uri(path: '/test')).toString(), '$sanitizedBaseURL$webdavBase/test'); - expect(WebDavClient.constructUri(baseURL, Uri(path: '/test/')).toString(), '$sanitizedBaseURL$webdavBase/test'); + test(baseURL, () { + expect( + WebDavClient.constructUri(baseURL).toString(), + '$sanitizedBaseURL$webdavBase', + ); + expect( + WebDavClient.constructUri(baseURL, PathUri.parse('/')).toString(), + '$sanitizedBaseURL$webdavBase', + ); + expect( + WebDavClient.constructUri(baseURL, PathUri.parse('test')).toString(), + '$sanitizedBaseURL$webdavBase/test', + ); + expect( + WebDavClient.constructUri(baseURL, PathUri.parse('test/')).toString(), + '$sanitizedBaseURL$webdavBase/test', + ); + expect( + WebDavClient.constructUri(baseURL, PathUri.parse('/test')).toString(), + '$sanitizedBaseURL$webdavBase/test', + ); + expect( + WebDavClient.constructUri(baseURL, PathUri.parse('/test/')).toString(), + '$sanitizedBaseURL$webdavBase/test', + ); }); } }); + group('PathUri', () { + test('isAbsolute', () { + expect(PathUri.parse('').isAbsolute, false); + expect(PathUri.parse('/').isAbsolute, true); + expect(PathUri.parse('test').isAbsolute, false); + expect(PathUri.parse('test/').isAbsolute, false); + expect(PathUri.parse('/test').isAbsolute, true); + expect(PathUri.parse('/test/').isAbsolute, true); + }); + + test('isDirectory', () { + expect(PathUri.parse('').isDirectory, true); + expect(PathUri.parse('/').isDirectory, true); + expect(PathUri.parse('test').isDirectory, false); + expect(PathUri.parse('test/').isDirectory, true); + expect(PathUri.parse('/test').isDirectory, false); + expect(PathUri.parse('/test/').isDirectory, true); + }); + + test('pathSegments', () { + expect(PathUri.parse('').pathSegments, isEmpty); + expect(PathUri.parse('/').pathSegments, isEmpty); + expect(PathUri.parse('test').pathSegments, ['test']); + expect(PathUri.parse('test/').pathSegments, ['test']); + expect(PathUri.parse('/test').pathSegments, ['test']); + expect(PathUri.parse('/test/').pathSegments, ['test']); + }); + + test('path', () { + expect(PathUri.parse('').path, ''); + expect(PathUri.parse('/').path, '/'); + expect(PathUri.parse('test').path, 'test'); + expect(PathUri.parse('test/').path, 'test/'); + expect(PathUri.parse('/test').path, '/test'); + expect(PathUri.parse('/test/').path, '/test/'); + }); + + test('normalization', () { + expect(PathUri.parse('/test/abc/').path, '/test/abc/'); + expect(PathUri.parse('//test//abc//').path, '/test/abc/'); + expect(PathUri.parse('///test///abc///').path, '/test/abc/'); + }); + + test('name', () { + expect(PathUri.parse('').name, ''); + expect(PathUri.parse('test').name, 'test'); + expect(PathUri.parse('/test/').name, 'test'); + expect(PathUri.parse('abc/test').name, 'test'); + expect(PathUri.parse('/abc/test/').name, 'test'); + }); + + test('parent', () { + expect(PathUri.parse('').parent, null); + expect(PathUri.parse('/').parent, null); + expect(PathUri.parse('test').parent, PathUri.parse('')); + expect(PathUri.parse('test/abc').parent, PathUri.parse('test/')); + expect(PathUri.parse('test/abc/').parent, PathUri.parse('test/')); + expect(PathUri.parse('/test/abc').parent, PathUri.parse('/test/')); + expect(PathUri.parse('/test/abc/').parent, PathUri.parse('/test/')); + }); + + test('join', () { + expect(PathUri.parse('').join(PathUri.parse('test')), PathUri.parse('test')); + expect(PathUri.parse('/').join(PathUri.parse('test')), PathUri.parse('/test')); + expect(() => PathUri.parse('test').join(PathUri.parse('abc')), throwsA(isA())); + expect(PathUri.parse('test/').join(PathUri.parse('abc')), PathUri.parse('test/abc')); + expect(PathUri.parse('test/').join(PathUri.parse('abc/123')), PathUri.parse('test/abc/123')); + expect(PathUri.parse('/test/').join(PathUri.parse('abc')), PathUri.parse('/test/abc')); + expect(PathUri.parse('/test/').join(PathUri.parse('/abc')), PathUri.parse('/test/abc')); + expect(PathUri.parse('/test/').join(PathUri.parse('/abc/')), PathUri.parse('/test/abc/')); + }); + + test('rename', () { + expect(PathUri.parse('').rename('test'), PathUri.parse('')); + expect(PathUri.parse('test').rename('abc'), PathUri.parse('abc')); + expect(PathUri.parse('test/').rename('abc'), PathUri.parse('abc/')); + expect(PathUri.parse('test/abc').rename('123'), PathUri.parse('test/123')); + expect(PathUri.parse('test/abc/').rename('123'), PathUri.parse('test/123/')); + expect(() => PathUri.parse('test').rename('abc/'), throwsA(isA())); + expect(() => PathUri.parse('test/').rename('abc/'), throwsA(isA())); + expect(() => PathUri.parse('test').rename('/abc'), throwsA(isA())); + expect(() => PathUri.parse('test/').rename('/abc'), throwsA(isA())); + expect(() => PathUri.parse('test').rename('abc/123'), throwsA(isA())); + expect(() => PathUri.parse('test/').rename('abc/123'), throwsA(isA())); + }); + }); + group( 'webdav', () { @@ -44,7 +149,7 @@ void main() { test('List directory', () async { final responses = (await client.webdav.propfind( - Uri(path: '/'), + PathUri.parse('/'), prop: WebDavPropWithoutValues.fromBools( nchaspreview: true, davgetcontenttype: true, @@ -64,7 +169,7 @@ void main() { test('List directory recursively', () async { final responses = (await client.webdav.propfind( - Uri(path: '/'), + PathUri.parse('/'), depth: WebDavDepth.infinity, )) .responses; @@ -73,7 +178,7 @@ void main() { test('Get file props', () async { final response = (await client.webdav.propfind( - Uri(path: 'Nextcloud.png'), + PathUri.parse('Nextcloud.png'), prop: WebDavPropWithoutValues.fromBools( davgetlastmodified: true, davgetetag: true, @@ -107,7 +212,7 @@ void main() { .toWebDavFiles() .single; - expect(response.path, Uri(path: 'Nextcloud.png')); + expect(response.path, PathUri.parse('Nextcloud.png')); expect(response.id, isNotEmpty); expect(response.fileId, isNotEmpty); expect(response.isCollection, isFalse); @@ -156,11 +261,11 @@ void main() { test('Get directory props', () async { final data = utf8.encode('test') as Uint8List; - await client.webdav.mkcol(Uri(path: 'test')); - await client.webdav.put(data, Uri(path: 'test/test.txt')); + await client.webdav.mkcol(PathUri.parse('test')); + await client.webdav.put(data, PathUri.parse('test/test.txt')); final response = (await client.webdav.propfind( - Uri(path: 'test'), + PathUri.parse('test'), prop: WebDavPropWithoutValues.fromBools( davgetcontenttype: true, davgetlastmodified: true, @@ -172,7 +277,7 @@ void main() { .toWebDavFiles() .single; - expect(response.path, Uri(path: 'test/')); + expect(response.path, PathUri.parse('test/')); expect(response.isCollection, isTrue); expect(response.mimeType, isNull); expect(response.size, data.lengthInBytes); @@ -187,17 +292,17 @@ void main() { }); test('Filter files', () async { - final response = await client.webdav.put(utf8.encode('test') as Uint8List, Uri(path: 'test.txt')); + final response = await client.webdav.put(utf8.encode('test') as Uint8List, PathUri.parse('test.txt')); final id = response.headers['oc-fileid']!.first; await client.webdav.proppatch( - Uri(path: 'test.txt'), + PathUri.parse('test.txt'), set: WebDavProp( ocfavorite: 1, ), ); final responses = (await client.webdav.report( - Uri(path: '/'), + PathUri.parse('/'), WebDavOcFilterRules( ocfavorite: 1, ), @@ -221,13 +326,13 @@ void main() { await client.webdav.put( utf8.encode('test') as Uint8List, - Uri(path: 'test.txt'), + PathUri.parse('test.txt'), lastModified: lastModifiedDate, created: createdDate, ); final updated = await client.webdav.proppatch( - Uri(path: 'test.txt'), + PathUri.parse('test.txt'), set: WebDavProp( ocfavorite: 1, ), @@ -235,7 +340,7 @@ void main() { expect(updated, isTrue); final props = (await client.webdav.propfind( - Uri(path: 'test.txt'), + PathUri.parse('test.txt'), prop: WebDavPropWithoutValues.fromBools( ocfavorite: true, davgetlastmodified: true, @@ -255,10 +360,10 @@ void main() { }); test('Remove properties', () async { - await client.webdav.put(utf8.encode('test') as Uint8List, Uri(path: 'test.txt')); + await client.webdav.put(utf8.encode('test') as Uint8List, PathUri.parse('test.txt')); var updated = await client.webdav.proppatch( - Uri(path: 'test.txt'), + PathUri.parse('test.txt'), set: WebDavProp( ocfavorite: 1, ), @@ -266,7 +371,7 @@ void main() { expect(updated, isTrue); var props = (await client.webdav.propfind( - Uri(path: 'test.txt'), + PathUri.parse('test.txt'), prop: WebDavPropWithoutValues.fromBools( ocfavorite: true, nccreationtime: true, @@ -281,7 +386,7 @@ void main() { expect(props.ocfavorite, 1); updated = await client.webdav.proppatch( - Uri(path: 'test.txt'), + PathUri.parse('test.txt'), remove: WebDavPropWithoutValues.fromBools( ocfavorite: true, ), @@ -289,7 +394,7 @@ void main() { expect(updated, isFalse); props = (await client.webdav.propfind( - Uri(path: 'test.txt'), + PathUri.parse('test.txt'), prop: WebDavPropWithoutValues.fromBools( ocfavorite: true, ), @@ -311,11 +416,11 @@ void main() { await client.webdav.putFile( source, source.statSync(), - Uri(path: 'test.png'), + PathUri.parse('test.png'), onProgress: progressValues.add, ); await client.webdav.getFile( - Uri(path: 'test.png'), + PathUri.parse('test.png'), destination, onProgress: progressValues.add, ); @@ -343,32 +448,32 @@ void main() { test(name, () async { final content = utf8.encode('This is a test file') as Uint8List; - final response = await client.webdav.put(content, Uri(path: path)); + final response = await client.webdav.put(content, PathUri.parse(path)); expect(response.statusCode, 201); - final downloadedContent = await client.webdav.get(Uri(path: path)); + final downloadedContent = await client.webdav.get(PathUri.parse(path)); expect(downloadedContent, equals(content)); }); } test('put_no_parent', () async { expect( - () => client.webdav.put(Uint8List(0), Uri(path: '409me/noparent.txt')), + () => client.webdav.put(Uint8List(0), PathUri.parse('409me/noparent.txt')), // https://github.com/nextcloud/server/issues/39625 throwsA(predicate((final e) => e.statusCode == 409)), ); }); test('delete', () async { - await client.webdav.put(Uint8List(0), Uri(path: 'test.txt')); + await client.webdav.put(Uint8List(0), PathUri.parse('test.txt')); - final response = await client.webdav.delete(Uri(path: 'test.txt')); + final response = await client.webdav.delete(PathUri.parse('test.txt')); expect(response.statusCode, 204); }); test('delete_null', () async { expect( - () => client.webdav.delete(Uri(path: 'test.txt')), + () => client.webdav.delete(PathUri.parse('test.txt')), throwsA(predicate((final e) => e.statusCode == 404)), ); }); @@ -376,29 +481,29 @@ void main() { // delete_fragment: This test is not applicable because the fragment is already removed on the client side test('mkcol', () async { - final response = await client.webdav.mkcol(Uri(path: 'test')); + final response = await client.webdav.mkcol(PathUri.parse('test')); expect(response.statusCode, 201); }); test('mkcol_again', () async { - await client.webdav.mkcol(Uri(path: 'test')); + await client.webdav.mkcol(PathUri.parse('test')); expect( - () => client.webdav.mkcol(Uri(path: 'test')), + () => client.webdav.mkcol(PathUri.parse('test')), throwsA(predicate((final e) => e.statusCode == 405)), ); }); test('delete_coll', () async { - var response = await client.webdav.mkcol(Uri(path: 'test')); + var response = await client.webdav.mkcol(PathUri.parse('test')); - response = await client.webdav.delete(Uri(path: 'test')); + response = await client.webdav.delete(PathUri.parse('test')); expect(response.statusCode, 204); }); test('mkcol_no_parent', () async { expect( - () => client.webdav.mkcol(Uri(path: '409me/noparent')), + () => client.webdav.mkcol(PathUri.parse('409me/noparent')), throwsA(predicate((final e) => e.statusCode == 409)), ); }); @@ -408,110 +513,110 @@ void main() { group('copymove', () { test('copy_simple', () async { - await client.webdav.mkcol(Uri(path: 'src')); + await client.webdav.mkcol(PathUri.parse('src')); - final response = await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst')); + final response = await client.webdav.copy(PathUri.parse('src'), PathUri.parse('dst')); expect(response.statusCode, 201); }); test('copy_overwrite', () async { - await client.webdav.mkcol(Uri(path: 'src')); - await client.webdav.mkcol(Uri(path: 'dst')); + await client.webdav.mkcol(PathUri.parse('src')); + await client.webdav.mkcol(PathUri.parse('dst')); expect( - () => client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst')), + () => client.webdav.copy(PathUri.parse('src'), PathUri.parse('dst')), throwsA(predicate((final e) => e.statusCode == 412)), ); - final response = await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst'), overwrite: true); + final response = await client.webdav.copy(PathUri.parse('src'), PathUri.parse('dst'), overwrite: true); expect(response.statusCode, 204); }); test('copy_nodestcoll', () async { - await client.webdav.mkcol(Uri(path: 'src')); + await client.webdav.mkcol(PathUri.parse('src')); expect( - () => client.webdav.copy(Uri(path: 'src'), Uri(path: 'nonesuch/dst')), + () => client.webdav.copy(PathUri.parse('src'), PathUri.parse('nonesuch/dst')), throwsA(predicate((final e) => e.statusCode == 409)), ); }); test('copy_coll', () async { - await client.webdav.mkcol(Uri(path: 'src')); - await client.webdav.mkcol(Uri(path: 'src/sub')); + await client.webdav.mkcol(PathUri.parse('src')); + await client.webdav.mkcol(PathUri.parse('src/sub')); for (var i = 0; i < 10; i++) { - await client.webdav.put(Uint8List(0), Uri(path: 'src/$i.txt')); + await client.webdav.put(Uint8List(0), PathUri.parse('src/$i.txt')); } - await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst1')); - await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst2')); + await client.webdav.copy(PathUri.parse('src'), PathUri.parse('dst1')); + await client.webdav.copy(PathUri.parse('src'), PathUri.parse('dst2')); expect( - () => client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst1')), + () => client.webdav.copy(PathUri.parse('src'), PathUri.parse('dst1')), throwsA(predicate((final e) => e.statusCode == 412)), ); - var response = await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst2'), overwrite: true); + var response = await client.webdav.copy(PathUri.parse('src'), PathUri.parse('dst2'), overwrite: true); expect(response.statusCode, 204); for (var i = 0; i < 10; i++) { - response = await client.webdav.delete(Uri(path: 'dst1/$i.txt')); + response = await client.webdav.delete(PathUri.parse('dst1/$i.txt')); expect(response.statusCode, 204); } - response = await client.webdav.delete(Uri(path: 'dst1/sub')); + response = await client.webdav.delete(PathUri.parse('dst1/sub')); expect(response.statusCode, 204); - response = await client.webdav.delete(Uri(path: 'dst2')); + response = await client.webdav.delete(PathUri.parse('dst2')); expect(response.statusCode, 204); }); // copy_shallow: Does not work on litmus, let's wait for https://github.com/nextcloud/server/issues/39627 test('move', () async { - await client.webdav.put(Uint8List(0), Uri(path: 'src1.txt')); - await client.webdav.put(Uint8List(0), Uri(path: 'src2.txt')); - await client.webdav.mkcol(Uri(path: 'coll')); + await client.webdav.put(Uint8List(0), PathUri.parse('src1.txt')); + await client.webdav.put(Uint8List(0), PathUri.parse('src2.txt')); + await client.webdav.mkcol(PathUri.parse('coll')); - var response = await client.webdav.move(Uri(path: 'src1.txt'), Uri(path: 'dst.txt')); + var response = await client.webdav.move(PathUri.parse('src1.txt'), PathUri.parse('dst.txt')); expect(response.statusCode, 201); expect( - () => client.webdav.move(Uri(path: 'src2.txt'), Uri(path: 'dst.txt')), + () => client.webdav.move(PathUri.parse('src2.txt'), PathUri.parse('dst.txt')), throwsA(predicate((final e) => e.statusCode == 412)), ); - response = await client.webdav.move(Uri(path: 'src2.txt'), Uri(path: 'dst.txt'), overwrite: true); + response = await client.webdav.move(PathUri.parse('src2.txt'), PathUri.parse('dst.txt'), overwrite: true); expect(response.statusCode, 204); }); test('move_coll', () async { - await client.webdav.mkcol(Uri(path: 'src')); - await client.webdav.mkcol(Uri(path: 'src/sub')); + await client.webdav.mkcol(PathUri.parse('src')); + await client.webdav.mkcol(PathUri.parse('src/sub')); for (var i = 0; i < 10; i++) { - await client.webdav.put(Uint8List(0), Uri(path: 'src/$i.txt')); + await client.webdav.put(Uint8List(0), PathUri.parse('src/$i.txt')); } - await client.webdav.put(Uint8List(0), Uri(path: 'noncoll')); - await client.webdav.copy(Uri(path: 'src'), Uri(path: 'dst2')); - await client.webdav.move(Uri(path: 'src'), Uri(path: 'dst1')); + await client.webdav.put(Uint8List(0), PathUri.parse('noncoll')); + await client.webdav.copy(PathUri.parse('src'), PathUri.parse('dst2')); + await client.webdav.move(PathUri.parse('src'), PathUri.parse('dst1')); expect( - () => client.webdav.move(Uri(path: 'dst1'), Uri(path: 'dst2')), + () => client.webdav.move(PathUri.parse('dst1'), PathUri.parse('dst2')), throwsA(predicate((final e) => e.statusCode == 412)), ); - await client.webdav.move(Uri(path: 'dst2'), Uri(path: 'dst1'), overwrite: true); - await client.webdav.copy(Uri(path: 'dst1'), Uri(path: 'dst2')); + await client.webdav.move(PathUri.parse('dst2'), PathUri.parse('dst1'), overwrite: true); + await client.webdav.copy(PathUri.parse('dst1'), PathUri.parse('dst2')); for (var i = 0; i < 10; i++) { - final response = await client.webdav.delete(Uri(path: 'dst1/$i.txt')); + final response = await client.webdav.delete(PathUri.parse('dst1/$i.txt')); expect(response.statusCode, 204); } - final response = await client.webdav.delete(Uri(path: 'dst1/sub')); + final response = await client.webdav.delete(PathUri.parse('dst1/sub')); expect(response.statusCode, 204); expect( - () => client.webdav.move(Uri(path: 'dst2'), Uri(path: 'noncoll')), + () => client.webdav.move(PathUri.parse('dst2'), PathUri.parse('noncoll')), throwsA(predicate((final e) => e.statusCode == 412)), ); }); @@ -523,10 +628,10 @@ void main() { // large_put: Already covered by large_get test('large_get', () async { - final response = await client.webdav.put(Uint8List(largefileSize), Uri(path: 'test.txt')); + final response = await client.webdav.put(Uint8List(largefileSize), PathUri.parse('test.txt')); expect(response.statusCode, 201); - final downloadedContent = await client.webdav.get(Uri(path: 'test.txt')); + final downloadedContent = await client.webdav.get(PathUri.parse('test.txt')); expect(downloadedContent, hasLength(largefileSize)); }); });