Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nextcloud): Add low-level helpers for WebDavResponse #2561

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ sealed class FilesBrowserBloc implements InteractiveBloc {
webdav.PathUri? hideUri,
}) = _FilesBrowserBloc;

BehaviorSubject<Result<BuiltList<webdav.WebDavFile>>> get files;
BehaviorSubject<Result<BuiltList<webdav.WebDavResponse>>> get files;

/// Mode to operate the `FilesBrowserView` in.
FilesBrowserMode get mode;
Expand Down Expand Up @@ -95,8 +95,12 @@ class _FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBloc {
depth: webdav.WebDavDepth.one,
),
converter: const webdav.WebDavResponseConverter(),
unwrap: (response) => BuiltList<webdav.WebDavFile>.build((b) {
for (final file in response.toWebDavFiles()) {
unwrap: (response) => BuiltList<webdav.WebDavResponse>.build((b) {
for (final file in response.responses) {
if (file.href == null) {
continue;
}

// Do not show itself
if (uri == file.path) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ class FileDetails {
}) : task = null;

FileDetails.fromWebDav({
required webdav.WebDavFile file,
required webdav.WebDavResponse file,
}) : uri = file.path,
size = file.size,
etag = file.etag,
mimeType = file.mimeType,
lastModified = file.lastModified,
isFavorite = file.favorite,
blurHash = file.blurHash,
size = file.props.ocSize,
etag = file.props.davGetetag,
mimeType = file.props.davGetcontenttype,
lastModified = file.props.davGetlastmodified,
isFavorite = file.props.ocFavorite,
blurHash = file.props.ncMetadataBlurhash,
task = null;

FileDetails.fromUploadTask({
Expand All @@ -38,18 +38,18 @@ class FileDetails {

FileDetails.fromDownloadTask({
required FilesDownloadTask this.task,
required webdav.WebDavFile file,
required webdav.WebDavResponse file,
}) : uri = task.uri,
size = file.size,
etag = file.etag,
mimeType = file.mimeType,
lastModified = file.lastModified,
isFavorite = file.favorite,
blurHash = file.blurHash;
size = file.props.ocSize,
etag = file.props.davGetetag,
mimeType = file.props.davGetcontenttype,
lastModified = file.props.davGetlastmodified,
isFavorite = file.props.ocFavorite,
blurHash = file.props.ncMetadataBlurhash;

factory FileDetails.fromTask({
required FilesTask task,
required webdav.WebDavFile file,
required webdav.WebDavResponse file,
}) {
switch (task) {
case FilesUploadTask():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import 'package:neon_framework/sort_box.dart';
import 'package:nextcloud/utils.dart';
import 'package:nextcloud/webdav.dart' as webdav;

final filesSortBox = SortBox<FilesSortProperty, webdav.WebDavFile>(
final filesSortBox = SortBox<FilesSortProperty, webdav.WebDavResponse>(
properties: {
FilesSortProperty.name: (file) => file.name.toLowerCase(),
FilesSortProperty.modifiedDate: (file) => file.lastModified?.secondsSinceEpoch ?? 0,
FilesSortProperty.size: (file) => file.size ?? 0,
FilesSortProperty.modifiedDate: (file) => file.props.davGetlastmodified?.secondsSinceEpoch ?? 0,
FilesSortProperty.size: (file) => file.props.ocSize ?? 0,
FilesSortProperty.isFolder: (file) => file.isDirectory ? 0 : 1,
},
boxes: const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
);
}

Iterable<Widget> buildUploadTasks(BuiltList<FilesTask> tasks, List<webdav.WebDavFile> files) sync* {
Iterable<Widget> buildUploadTasks(BuiltList<FilesTask> tasks, List<webdav.WebDavResponse> files) sync* {
for (final task in tasks) {
if (task is! FilesUploadTask) {
continue;
Expand Down
5 changes: 5 additions & 0 deletions packages/nextcloud/lib/src/api/webdav/models/webdav_file.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:nextcloud/src/api/webdav/webdav.dart';
import 'package:timezone/timezone.dart' as tz;

@Deprecated('Use WebDavResponseHelpers instead.')
// ignore: public_member_api_docs
extension WebDavMultistatusFile on WebDavMultistatus {
/// Convert the [WebDavMultistatus] into a [WebDavFile] for easier handling
@Deprecated('Use WebDavResponseHelpers instead.')
List<WebDavFile> toWebDavFiles() =>
responses.where((response) => response.href != null).map((response) => WebDavFile(response: response)).toList();
}

/// WebDavFile class
@Deprecated('Use WebDavResponseHelpers instead.')
class WebDavFile {
/// Creates a new WebDavFile object with the given path
@Deprecated('Use WebDavResponseHelpers instead.')
WebDavFile({
required WebDavResponse response,
}) : _response = response;
Expand Down
1 change: 1 addition & 0 deletions packages/nextcloud/lib/src/api/webdav/utils/utils.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export 'options_parser.dart';
export 'webdav_response_converter.dart';
export 'webdav_response_helpers.dart';
export 'webdav_uri.dart';
export 'xml_date_time_converter.dart';
export 'xml_duration_converter.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'package:nextcloud/src/api/webdav/models/models.dart';

/// Helpers for WebDavResponse
extension WebDavResponseHelpers on WebDavResponse {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we just make these helpers available on the WebDavResponse directly?
I understand that this will make the generation a bit harder, but it also allows us to cache these operations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I can give it a try. Caching is indeed a good reason to move it inline.

/// All available props.
WebDavProp get props => propstats.singleWhere((propstat) => propstat.status.contains('200 OK')).prop;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the typical prop count on a file? How expensive is this operation?

The second commit calls file.props multiple times subsequently.
I don't have any issue in keeping it like this and require the user to cache the prop themselves.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are usually two propstats (one 200, one 404). This could also be changed to a firstWhere to be slightly cheaper.


/// The path of the resource.
PathUri get path {
final href = PathUri.parse(Uri.decodeFull(this.href!));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the null assertion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There can be responses without a href which are not valid. Consumers need to filter these out (we already did in toWebDavFiles()) and then we need to prevent null hrefs here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this means that unfiltered responses will throw here.
I'd rather return a null when there is no href.

Then the filtering can be done based on the path.

return PathUri(
isAbsolute: false,
isDirectory: href.isDirectory,
// The server might be hosted at a subpath, so we don't have a fixed number of path segments to remove
pathSegments: href.pathSegments.sublist(href.pathSegments.indexOf('remote.php') + 2),
);
}

/// The name of the resource.
String get name => path.name;

/// Whether the file is hidden.
bool get isHidden => props.ncHidden ?? name.startsWith('.');

/// Whether the file is a directory
bool get isDirectory => props.davResourcetype?.collection != null || path.isDirectory;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:nextcloud/src/api/webdav/models/models.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import 'package:nextcloud/src/api/webdav/models/models.dart';
import 'package:nextcloud/src/api/webdav/utils/utils.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';

void main() {
test('props', () {
expect(
const WebDavResponse(
href: null,
propstats: [
WebDavPropstat(
status: 'HTTP/1.1 404 Not Found',
prop: WebDavProp(ocSize: 1),
),
WebDavPropstat(
status: 'HTTP/1.1 200 OK',
prop: WebDavProp(ocSize: 2),
),
],
).props.ocSize,
2,
);
});

group('path', () {
group('Root', () {
test('File', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/def.txt',
propstats: [],
).path,
PathUri.parse('abc/def.txt'),
);
});

test('Directory', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/def/',
propstats: [],
).path,
PathUri.parse('abc/def/'),
);
});
});

group('Subpath', () {
test('File', () {
expect(
const WebDavResponse(
href: '/subpath/remote.php/webdav/abc/def.txt',
propstats: [],
).path,
PathUri.parse('abc/def.txt'),
);
});

test('Directory', () {
expect(
const WebDavResponse(
href: '/subpath/remote.php/webdav/abc/def/',
propstats: [],
).path,
PathUri.parse('abc/def/'),
);
});
});
});

group('name', () {
test('File', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/def.txt',
propstats: [],
).name,
'def.txt',
);
});

test('Directory', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/def/',
propstats: [],
).name,
'def',
);
});
});

group('isHidden', () {
group('From prop', () {
test('File', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/def.txt',
propstats: [
WebDavPropstat(
status: 'HTTP/1.1 200 OK',
prop: WebDavProp(
ncHidden: true,
),
),
],
).isHidden,
true,
);
});

test('Directory', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/def/',
propstats: [
WebDavPropstat(
status: 'HTTP/1.1 200 OK',
prop: WebDavProp(
ncHidden: true,
),
),
],
).isHidden,
true,
);
});
});

group('From path', () {
test('File', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/.def.txt',
propstats: [
WebDavPropstat(
status: 'HTTP/1.1 200 OK',
prop: WebDavProp(),
),
],
).isHidden,
true,
);
});

test('Directory', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/.def/',
propstats: [
WebDavPropstat(
status: 'HTTP/1.1 200 OK',
prop: WebDavProp(),
),
],
).isHidden,
true,
);
});
});
});

group('isDirectory', () {
group('From prop', () {
test('File', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/def.txt',
propstats: [
WebDavPropstat(
status: 'HTTP/1.1 200 OK',
prop: WebDavProp(
davResourcetype: WebDavResourcetype(
collection: null,
),
),
),
],
).isDirectory,
false,
);
});

test('Directory', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/def/',
propstats: [
WebDavPropstat(
status: 'HTTP/1.1 200 OK',
prop: WebDavProp(
davResourcetype: WebDavResourcetype(
collection: [],
),
),
),
],
).isDirectory,
true,
);
});
});

group('From path', () {
test('File', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/def.txt',
propstats: [
WebDavPropstat(
status: 'HTTP/1.1 200 OK',
prop: WebDavProp(),
),
],
).isDirectory,
false,
);
});

test('Directory', () {
expect(
const WebDavResponse(
href: '/remote.php/webdav/abc/def/',
propstats: [
WebDavPropstat(
status: 'HTTP/1.1 200 OK',
prop: WebDavProp(),
),
],
).isDirectory,
true,
);
});
});
});
}
Loading