From 5459919fab0ce04c03f20dba1d6c307c9fa51e9e Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Sat, 7 Jan 2023 17:57:57 +0800 Subject: [PATCH] Tidy up source files --- app/lib/api/api.dart | 741 +------------------------- app/lib/api/api.g.dart | 14 +- app/lib/api/direct_api.dart | 30 ++ app/lib/api/face_recognition_api.dart | 72 +++ app/lib/api/files_api.dart | 339 ++++++++++++ app/lib/api/files_sharing_api.dart | 158 ++++++ app/lib/api/systemtag_api.dart | 142 +++++ 7 files changed, 753 insertions(+), 743 deletions(-) create mode 100644 app/lib/api/direct_api.dart create mode 100644 app/lib/api/face_recognition_api.dart create mode 100644 app/lib/api/files_api.dart create mode 100644 app/lib/api/files_sharing_api.dart create mode 100644 app/lib/api/systemtag_api.dart diff --git a/app/lib/api/api.dart b/app/lib/api/api.dart index 128564ce..7b76a547 100644 --- a/app/lib/api/api.dart +++ b/app/lib/api/api.dart @@ -10,6 +10,11 @@ import 'package:to_string/to_string.dart'; import 'package:xml/xml.dart'; part 'api.g.dart'; +part 'direct_api.dart'; +part 'face_recognition_api.dart'; +part 'files_api.dart'; +part 'files_sharing_api.dart'; +part 'systemtag_api.dart'; @toString class Response { @@ -133,344 +138,6 @@ class Api { bool _isHttpStatusGood(int status) => status ~/ 100 == 2; -@npLog -class ApiFiles { - ApiFiles(this._api); - - final Api _api; - - Future delete({ - required String path, - }) async { - try { - return await _api.request("DELETE", path); - } catch (e) { - _log.severe("[delete] Failed while delete", e); - rethrow; - } - } - - Future get({ - required String path, - }) async { - try { - return await _api.request("GET", path, isResponseString: false); - } catch (e) { - _log.severe("[get] Failed while get", e); - rethrow; - } - } - - Future put({ - required String path, - String mime = "application/octet-stream", - required Uint8List content, - }) async { - try { - return await _api.request( - "PUT", - path, - header: { - "Content-Type": mime, - }, - bodyBytes: content, - ); - } catch (e) { - _log.severe("[put] Failed while put", e); - rethrow; - } - } - - Future propfind({ - required String path, - int? depth, - getlastmodified, - getetag, - getcontenttype, - resourcetype, - getcontentlength, - id, - fileid, - favorite, - commentsHref, - commentsCount, - commentsUnread, - ownerId, - ownerDisplayName, - shareTypes, - checksums, - hasPreview, - size, - richWorkspace, - trashbinFilename, - trashbinOriginalLocation, - trashbinDeletionTime, - Map? customNamespaces, - List? customProperties, - }) async { - try { - final bool hasDavNs = (getlastmodified != null || - getetag != null || - getcontenttype != null || - resourcetype != null || - getcontentlength != null); - final bool hasOcNs = (id != null || - fileid != null || - favorite != null || - commentsHref != null || - commentsCount != null || - commentsUnread != null || - ownerId != null || - ownerDisplayName != null || - shareTypes != null || - checksums != null || - size != null); - final bool hasNcNs = (hasPreview != null || - richWorkspace != null || - trashbinFilename != null || - trashbinOriginalLocation != null || - trashbinDeletionTime != null); - if (!hasDavNs && !hasOcNs && !hasNcNs) { - // no body - return await _api.request("PROPFIND", path); - } - - final namespaces = { - "DAV:": "d", - if (hasOcNs) "http://owncloud.org/ns": "oc", - if (hasNcNs) "http://nextcloud.org/ns": "nc", - }..addAll(customNamespaces ?? {}); - final builder = XmlBuilder(); - builder - ..processing("xml", "version=\"1.0\"") - ..element("d:propfind", namespaces: namespaces, nest: () { - builder.element("d:prop", nest: () { - if (getlastmodified != null) { - builder.element("d:getlastmodified"); - } - if (getetag != null) { - builder.element("d:getetag"); - } - if (getcontenttype != null) { - builder.element("d:getcontenttype"); - } - if (resourcetype != null) { - builder.element("d:resourcetype"); - } - if (getcontentlength != null) { - builder.element("d:getcontentlength"); - } - if (id != null) { - builder.element("oc:id"); - } - if (fileid != null) { - builder.element("oc:fileid"); - } - if (favorite != null) { - builder.element("oc:favorite"); - } - if (commentsHref != null) { - builder.element("oc:comments-href"); - } - if (commentsCount != null) { - builder.element("oc:comments-count"); - } - if (commentsUnread != null) { - builder.element("oc:comments-unread"); - } - if (ownerId != null) { - builder.element("oc:owner-id"); - } - if (ownerDisplayName != null) { - builder.element("oc:owner-display-name"); - } - if (shareTypes != null) { - builder.element("oc:share-types"); - } - if (checksums != null) { - builder.element("oc:checksums"); - } - if (size != null) { - builder.element("oc:size"); - } - if (hasPreview != null) { - builder.element("nc:has-preview"); - } - if (richWorkspace != null) { - builder.element("nc:rich-workspace"); - } - if (trashbinFilename != null) { - builder.element("nc:trashbin-filename"); - } - if (trashbinOriginalLocation != null) { - builder.element("nc:trashbin-original-location"); - } - if (trashbinDeletionTime != null) { - builder.element("nc:trashbin-deletion-time"); - } - for (final p in customProperties ?? []) { - builder.element(p); - } - }); - }); - return await _api.request("PROPFIND", path, - header: { - "Content-Type": "application/xml", - if (depth != null) "Depth": depth.toString(), - }, - body: builder.buildDocument().toXmlString()); - } catch (e) { - _log.severe("[propfind] Failed while propfind", e); - rethrow; - } - } - - /// Set or remove custom properties - /// - /// [namespaces] should be specified in the format {"URI": "prefix"}, eg, - /// {"DAV:": "d"} - Future proppatch({ - required String path, - Map? namespaces, - Map? set, - List? remove, - }) async { - try { - final ns = { - "DAV:": "d", - }..addAll(namespaces ?? {}); - final builder = XmlBuilder(); - builder - ..processing("xml", "version=\"1.0\"") - ..element("d:propertyupdate", namespaces: ns, nest: () { - if (set != null && set.isNotEmpty) { - builder.element("d:set", nest: () { - builder.element("d:prop", nest: () { - for (final e in set.entries) { - builder.element(e.key, nest: () { - builder.text("${e.value}"); - }); - } - }); - }); - } - if (remove != null && remove.isNotEmpty) { - builder.element("d:remove", nest: () { - builder.element("d:prop", nest: () { - for (final e in remove) { - builder.element(e); - } - }); - }); - } - }); - return await _api.request( - "PROPPATCH", - path, - header: { - "Content-Type": "application/xml", - }, - body: builder.buildDocument().toXmlString(), - ); - } catch (e) { - _log.severe("[proppatch] Failed while proppatch", e); - rethrow; - } - } - - /// A folder can be created by sending a MKCOL request to the folder - Future mkcol({ - required String path, - }) async { - try { - return await _api.request("MKCOL", path); - } catch (e) { - _log.severe("[mkcol] Failed while get", e); - rethrow; - } - } - - /// A file or folder can be copied by sending a COPY request to the file or - /// folder and specifying the [destinationUrl] as full url - Future copy({ - required String path, - required String destinationUrl, - bool? overwrite, - }) async { - try { - return await _api.request("COPY", path, header: { - "Destination": Uri.parse(destinationUrl).toString(), - if (overwrite != null) "Overwrite": overwrite ? "T" : "F", - }); - } catch (e) { - _log.severe("[copy] Failed while delete", e); - rethrow; - } - } - - /// A file or folder can be moved by sending a MOVE request to the file or - /// folder and specifying the [destinationUrl] as full url - Future move({ - required String path, - required String destinationUrl, - bool? overwrite, - }) async { - try { - return await _api.request("MOVE", path, header: { - "Destination": Uri.parse(destinationUrl).toString(), - if (overwrite != null) "Overwrite": overwrite ? "T" : "F", - }); - } catch (e) { - _log.severe("[move] Failed while delete", e); - rethrow; - } - } - - Future report({ - required String path, - bool? favorite, - List? systemtag, - }) async { - try { - final namespaces = { - "DAV:": "d", - "http://owncloud.org/ns": "oc", - }; - final builder = XmlBuilder(); - builder - ..processing("xml", "version=\"1.0\"") - ..element("oc:filter-files", namespaces: namespaces, nest: () { - builder.element("oc:filter-rules", nest: () { - if (favorite != null) { - builder.element("oc:favorite", nest: () { - builder.text(favorite ? "1" : "0"); - }); - } - for (final t in systemtag ?? []) { - builder.element("oc:systemtag", nest: () { - builder.text(t); - }); - } - }); - builder.element("d:prop", nest: () { - builder.element("oc:fileid"); - }); - }); - return await _api.request( - "REPORT", - path, - header: { - "Content-Type": "application/xml", - }, - body: builder.buildDocument().toXmlString(), - ); - } catch (e) { - _log.severe("[report] Failed while report", e); - rethrow; - } - } -} - class ApiOcs { ApiOcs(this._api); @@ -490,401 +157,3 @@ class ApiOcsDav { final ApiOcs _ocs; } - -@npLog -class ApiOcsDavDirect { - ApiOcsDavDirect(this._dav); - - Future post({ - required int fileId, - }) async { - try { - return await _dav._ocs._api.request( - "POST", - "ocs/v2.php/apps/dav/api/v1/direct", - header: { - "OCS-APIRequest": "true", - "Content-Type": "application/x-www-form-urlencoded", - }, - queryParameters: { - "format": "json", - }, - body: "fileId=$fileId", - ); - } catch (e) { - _log.severe("[post] Failed while post", e); - rethrow; - } - } - - final ApiOcsDav _dav; -} - -class ApiOcsFacerecognition { - ApiOcsFacerecognition(this._ocs); - - ApiOcsFacerecognitionPersons persons() => ApiOcsFacerecognitionPersons(this); - - ApiOcsFacerecognitionPerson person(String name) => - ApiOcsFacerecognitionPerson(this, name); - - final ApiOcs _ocs; -} - -@npLog -class ApiOcsFacerecognitionPersons { - ApiOcsFacerecognitionPersons(this._facerecognition); - - Future get() async { - try { - return await _facerecognition._ocs._api.request( - "GET", - "ocs/v2.php/apps/facerecognition/api/v1/persons", - header: { - "OCS-APIRequest": "true", - }, - queryParameters: { - "format": "json", - }, - ); - } catch (e) { - _log.severe("[get] Failed while get", e); - rethrow; - } - } - - final ApiOcsFacerecognition _facerecognition; -} - -class ApiOcsFacerecognitionPerson { - ApiOcsFacerecognitionPerson(this._facerecognition, this._name); - - ApiOcsFacerecognitionPersonFaces faces() => - ApiOcsFacerecognitionPersonFaces(this); - - final ApiOcsFacerecognition _facerecognition; - final String _name; -} - -@npLog -class ApiOcsFacerecognitionPersonFaces { - ApiOcsFacerecognitionPersonFaces(this._person); - - Future get() async { - try { - return await _person._facerecognition._ocs._api.request( - "GET", - "ocs/v2.php/apps/facerecognition/api/v1/person/${_person._name}/faces", - header: { - "OCS-APIRequest": "true", - }, - queryParameters: { - "format": "json", - }, - ); - } catch (e) { - _log.severe("[get] Failed while get", e); - rethrow; - } - } - - final ApiOcsFacerecognitionPerson _person; -} - -class ApiOcsFilesSharing { - ApiOcsFilesSharing(this._ocs); - - ApiOcsFilesSharingShares shares() => ApiOcsFilesSharingShares(this); - - ApiOcsFilesSharingShare share(String shareId) => - ApiOcsFilesSharingShare(this, shareId); - - ApiOcsFilesSharingSharees sharees() => ApiOcsFilesSharingSharees(this); - - final ApiOcs _ocs; -} - -@npLog -class ApiOcsFilesSharingShares { - ApiOcsFilesSharingShares(this._filesSharing); - - /// Get Shares from a specific file or folder - /// - /// If [sharedWithMe] is not null, [subfiles] and [path] are ignored. This is - /// a limitation of the server API. - /// - /// See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#get-shares-from-a-specific-file-or-folder - /// See: https://doc.owncloud.com/server/latest/developer_manual/core/apis/ocs-share-api.html#get-all-shares - Future get({ - String? path, - bool? reshares, - bool? subfiles, - bool? sharedWithMe, - }) async { - try { - return await _filesSharing._ocs._api.request( - "GET", - "ocs/v2.php/apps/files_sharing/api/v1/shares", - header: { - "OCS-APIRequest": "true", - }, - queryParameters: { - "format": "json", - if (path != null) "path": path, - if (reshares != null) "reshares": reshares.toString(), - if (subfiles != null) "subfiles": subfiles.toString(), - if (sharedWithMe != null) "shared_with_me": sharedWithMe.toString(), - }, - ); - } catch (e) { - _log.severe("[get] Failed while get", e); - rethrow; - } - } - - /// Create a new Share - /// - /// See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#create-a-new-share - Future post({ - required String path, - required int shareType, - String? shareWith, - String? publicUpload, - String? password, - int? permissions, - String? expireDate, - }) async { - try { - return await _filesSharing._ocs._api.request( - "POST", - "ocs/v2.php/apps/files_sharing/api/v1/shares", - header: { - "OCS-APIRequest": "true", - "Content-Type": "application/x-www-form-urlencoded", - }, - queryParameters: { - "format": "json", - "path": path, - "shareType": shareType.toString(), - if (shareWith != null) "shareWith": shareWith, - if (publicUpload != null) "publicUpload": publicUpload, - if (password != null) "password": password, - if (password != null) "password": password, - if (expireDate != null) "expireDate": expireDate.toString(), - }, - ); - } catch (e) { - _log.severe("[post] Failed while post", e); - rethrow; - } - } - - final ApiOcsFilesSharing _filesSharing; -} - -@npLog -class ApiOcsFilesSharingShare { - ApiOcsFilesSharingShare(this._filesSharing, this._shareId); - - /// Remove the given share - /// - /// See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#delete-share - /// * The type of share ID is listed as int in the document, however, the - /// share ID returned in [ApiOcsFilesSharingShares.get] is actually a string. To - /// keep it consistent, we'll use string instead - Future delete() async { - try { - return await _filesSharing._ocs._api.request( - "DELETE", - "ocs/v2.php/apps/files_sharing/api/v1/shares/$_shareId", - header: { - "OCS-APIRequest": "true", - }, - ); - } catch (e) { - _log.severe("[delete] Failed while delete", e); - rethrow; - } - } - - final ApiOcsFilesSharing _filesSharing; - final String _shareId; -} - -@npLog -class ApiOcsFilesSharingSharees { - ApiOcsFilesSharingSharees(this._filesSharing); - - /// Get all sharees matching a search term - /// - /// See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-sharee-api.html#search-sharees - Future get({ - String? search, - bool? lookup, - int? perPage, - String? itemType, - }) async { - try { - return await _filesSharing._ocs._api.request( - "GET", - "ocs/v1.php/apps/files_sharing/api/v1/sharees", - header: { - "OCS-APIRequest": "true", - }, - queryParameters: { - "format": "json", - if (search != null) "search": search, - if (lookup != null) "lookup": lookup.toString(), - if (perPage != null) "perPage": perPage.toString(), - if (itemType != null) "itemType": itemType, - }, - ); - } catch (e) { - _log.severe("[get] Failed while get", e); - rethrow; - } - } - - final ApiOcsFilesSharing _filesSharing; -} - -@npLog -class ApiSystemtags { - const ApiSystemtags(this.api); - - /// Retrieve a list of all tags - /// - /// See: https://doc.owncloud.com/server/10.10/developer_manual/webdav_api/tags.html#list-tags - Future propfind({ - id, - displayName, - userVisible, - userAssignable, - }) async { - const endpoint = "remote.php/dav/systemtags"; - try { - if (id == null && - displayName == null && - userVisible == null && - userAssignable == null) { - // no body - return await api.request("PROPFIND", endpoint); - } - - final namespaces = { - "DAV:": "d", - "http://owncloud.org/ns": "oc", - }; - final builder = XmlBuilder(); - builder - ..processing("xml", "version=\"1.0\"") - ..element("d:propfind", namespaces: namespaces, nest: () { - builder.element("d:prop", nest: () { - if (id != null) { - builder.element("oc:id"); - } - if (displayName != null) { - builder.element("oc:display-name"); - } - if (userVisible != null) { - builder.element("oc:user-visible"); - } - if (userAssignable != null) { - builder.element("oc:user-assignable"); - } - }); - }); - return await api.request( - "PROPFIND", - endpoint, - header: { - "Content-Type": "application/xml", - }, - body: builder.buildDocument().toXmlString(), - ); - } catch (e) { - _log.severe("[propfind] Failed while propfind", e); - rethrow; - } - } - - final Api api; -} - -class ApiSystemtagsRelations { - const ApiSystemtagsRelations(this.api); - - ApiSystemtagsRelationsFiles files(int fileId) => - ApiSystemtagsRelationsFiles(this, fileId); - - final Api api; -} - -@npLog -class ApiSystemtagsRelationsFiles { - const ApiSystemtagsRelationsFiles(this.systemtagsRelations, this.fileId); - - /// Retrieve the tag ids and metadata of a given file - /// - /// See: https://doc.owncloud.com/server/10.10/developer_manual/webdav_api/tags.html#retrieve-the-tag-ids-and-metadata-of-a-given-file - Future propfind({ - id, - displayName, - userVisible, - userAssignable, - canAssign, - }) async { - final endpoint = "remote.php/dav/systemtags-relations/files/$fileId"; - try { - if (id == null && - displayName == null && - userVisible == null && - userAssignable == null && - canAssign == null) { - // no body - return await systemtagsRelations.api.request("PROPFIND", endpoint); - } - - final namespaces = { - "DAV:": "d", - "http://owncloud.org/ns": "oc", - }; - final builder = XmlBuilder(); - builder - ..processing("xml", "version=\"1.0\"") - ..element("d:propfind", namespaces: namespaces, nest: () { - builder.element("d:prop", nest: () { - if (id != null) { - builder.element("oc:id"); - } - if (displayName != null) { - builder.element("oc:display-name"); - } - if (userVisible != null) { - builder.element("oc:user-visible"); - } - if (userAssignable != null) { - builder.element("oc:user-assignable"); - } - if (canAssign != null) { - builder.element("oc:can-assign"); - } - }); - }); - return await systemtagsRelations.api.request( - "PROPFIND", - endpoint, - header: { - "Content-Type": "application/xml", - }, - body: builder.buildDocument().toXmlString(), - ); - } catch (e) { - _log.severe("[propfind] Failed while propfind", e); - rethrow; - } - } - - final ApiSystemtagsRelations systemtagsRelations; - final int fileId; -} diff --git a/app/lib/api/api.g.dart b/app/lib/api/api.g.dart index d5bc6772..1e1abe96 100644 --- a/app/lib/api/api.g.dart +++ b/app/lib/api/api.g.dart @@ -13,13 +13,6 @@ extension _$ApiNpLog on Api { static final log = Logger("api.api.Api"); } -extension _$ApiFilesNpLog on ApiFiles { - // ignore: unused_element - Logger get _log => log; - - static final log = Logger("api.api.ApiFiles"); -} - extension _$ApiOcsDavDirectNpLog on ApiOcsDavDirect { // ignore: unused_element Logger get _log => log; @@ -42,6 +35,13 @@ extension _$ApiOcsFacerecognitionPersonFacesNpLog static final log = Logger("api.api.ApiOcsFacerecognitionPersonFaces"); } +extension _$ApiFilesNpLog on ApiFiles { + // ignore: unused_element + Logger get _log => log; + + static final log = Logger("api.api.ApiFiles"); +} + extension _$ApiOcsFilesSharingSharesNpLog on ApiOcsFilesSharingShares { // ignore: unused_element Logger get _log => log; diff --git a/app/lib/api/direct_api.dart b/app/lib/api/direct_api.dart new file mode 100644 index 00000000..224e17c1 --- /dev/null +++ b/app/lib/api/direct_api.dart @@ -0,0 +1,30 @@ +part of 'api.dart'; + +@npLog +class ApiOcsDavDirect { + ApiOcsDavDirect(this._dav); + + Future post({ + required int fileId, + }) async { + try { + return await _dav._ocs._api.request( + "POST", + "ocs/v2.php/apps/dav/api/v1/direct", + header: { + "OCS-APIRequest": "true", + "Content-Type": "application/x-www-form-urlencoded", + }, + queryParameters: { + "format": "json", + }, + body: "fileId=$fileId", + ); + } catch (e) { + _log.severe("[post] Failed while post", e); + rethrow; + } + } + + final ApiOcsDav _dav; +} diff --git a/app/lib/api/face_recognition_api.dart b/app/lib/api/face_recognition_api.dart new file mode 100644 index 00000000..f384b49a --- /dev/null +++ b/app/lib/api/face_recognition_api.dart @@ -0,0 +1,72 @@ +part of 'api.dart'; + +class ApiOcsFacerecognition { + ApiOcsFacerecognition(this._ocs); + + ApiOcsFacerecognitionPersons persons() => ApiOcsFacerecognitionPersons(this); + + ApiOcsFacerecognitionPerson person(String name) => + ApiOcsFacerecognitionPerson(this, name); + + final ApiOcs _ocs; +} + +@npLog +class ApiOcsFacerecognitionPersons { + ApiOcsFacerecognitionPersons(this._facerecognition); + + Future get() async { + try { + return await _facerecognition._ocs._api.request( + "GET", + "ocs/v2.php/apps/facerecognition/api/v1/persons", + header: { + "OCS-APIRequest": "true", + }, + queryParameters: { + "format": "json", + }, + ); + } catch (e) { + _log.severe("[get] Failed while get", e); + rethrow; + } + } + + final ApiOcsFacerecognition _facerecognition; +} + +class ApiOcsFacerecognitionPerson { + ApiOcsFacerecognitionPerson(this._facerecognition, this._name); + + ApiOcsFacerecognitionPersonFaces faces() => + ApiOcsFacerecognitionPersonFaces(this); + + final ApiOcsFacerecognition _facerecognition; + final String _name; +} + +@npLog +class ApiOcsFacerecognitionPersonFaces { + ApiOcsFacerecognitionPersonFaces(this._person); + + Future get() async { + try { + return await _person._facerecognition._ocs._api.request( + "GET", + "ocs/v2.php/apps/facerecognition/api/v1/person/${_person._name}/faces", + header: { + "OCS-APIRequest": "true", + }, + queryParameters: { + "format": "json", + }, + ); + } catch (e) { + _log.severe("[get] Failed while get", e); + rethrow; + } + } + + final ApiOcsFacerecognitionPerson _person; +} diff --git a/app/lib/api/files_api.dart b/app/lib/api/files_api.dart new file mode 100644 index 00000000..7e255b3b --- /dev/null +++ b/app/lib/api/files_api.dart @@ -0,0 +1,339 @@ +part of 'api.dart'; + +@npLog +class ApiFiles { + ApiFiles(this._api); + + final Api _api; + + Future delete({ + required String path, + }) async { + try { + return await _api.request("DELETE", path); + } catch (e) { + _log.severe("[delete] Failed while delete", e); + rethrow; + } + } + + Future get({ + required String path, + }) async { + try { + return await _api.request("GET", path, isResponseString: false); + } catch (e) { + _log.severe("[get] Failed while get", e); + rethrow; + } + } + + Future put({ + required String path, + String mime = "application/octet-stream", + required Uint8List content, + }) async { + try { + return await _api.request( + "PUT", + path, + header: { + "Content-Type": mime, + }, + bodyBytes: content, + ); + } catch (e) { + _log.severe("[put] Failed while put", e); + rethrow; + } + } + + Future propfind({ + required String path, + int? depth, + getlastmodified, + getetag, + getcontenttype, + resourcetype, + getcontentlength, + id, + fileid, + favorite, + commentsHref, + commentsCount, + commentsUnread, + ownerId, + ownerDisplayName, + shareTypes, + checksums, + hasPreview, + size, + richWorkspace, + trashbinFilename, + trashbinOriginalLocation, + trashbinDeletionTime, + Map? customNamespaces, + List? customProperties, + }) async { + try { + final bool hasDavNs = (getlastmodified != null || + getetag != null || + getcontenttype != null || + resourcetype != null || + getcontentlength != null); + final bool hasOcNs = (id != null || + fileid != null || + favorite != null || + commentsHref != null || + commentsCount != null || + commentsUnread != null || + ownerId != null || + ownerDisplayName != null || + shareTypes != null || + checksums != null || + size != null); + final bool hasNcNs = (hasPreview != null || + richWorkspace != null || + trashbinFilename != null || + trashbinOriginalLocation != null || + trashbinDeletionTime != null); + if (!hasDavNs && !hasOcNs && !hasNcNs) { + // no body + return await _api.request("PROPFIND", path); + } + + final namespaces = { + "DAV:": "d", + if (hasOcNs) "http://owncloud.org/ns": "oc", + if (hasNcNs) "http://nextcloud.org/ns": "nc", + }..addAll(customNamespaces ?? {}); + final builder = XmlBuilder(); + builder + ..processing("xml", "version=\"1.0\"") + ..element("d:propfind", namespaces: namespaces, nest: () { + builder.element("d:prop", nest: () { + if (getlastmodified != null) { + builder.element("d:getlastmodified"); + } + if (getetag != null) { + builder.element("d:getetag"); + } + if (getcontenttype != null) { + builder.element("d:getcontenttype"); + } + if (resourcetype != null) { + builder.element("d:resourcetype"); + } + if (getcontentlength != null) { + builder.element("d:getcontentlength"); + } + if (id != null) { + builder.element("oc:id"); + } + if (fileid != null) { + builder.element("oc:fileid"); + } + if (favorite != null) { + builder.element("oc:favorite"); + } + if (commentsHref != null) { + builder.element("oc:comments-href"); + } + if (commentsCount != null) { + builder.element("oc:comments-count"); + } + if (commentsUnread != null) { + builder.element("oc:comments-unread"); + } + if (ownerId != null) { + builder.element("oc:owner-id"); + } + if (ownerDisplayName != null) { + builder.element("oc:owner-display-name"); + } + if (shareTypes != null) { + builder.element("oc:share-types"); + } + if (checksums != null) { + builder.element("oc:checksums"); + } + if (size != null) { + builder.element("oc:size"); + } + if (hasPreview != null) { + builder.element("nc:has-preview"); + } + if (richWorkspace != null) { + builder.element("nc:rich-workspace"); + } + if (trashbinFilename != null) { + builder.element("nc:trashbin-filename"); + } + if (trashbinOriginalLocation != null) { + builder.element("nc:trashbin-original-location"); + } + if (trashbinDeletionTime != null) { + builder.element("nc:trashbin-deletion-time"); + } + for (final p in customProperties ?? []) { + builder.element(p); + } + }); + }); + return await _api.request("PROPFIND", path, + header: { + "Content-Type": "application/xml", + if (depth != null) "Depth": depth.toString(), + }, + body: builder.buildDocument().toXmlString()); + } catch (e) { + _log.severe("[propfind] Failed while propfind", e); + rethrow; + } + } + + /// Set or remove custom properties + /// + /// [namespaces] should be specified in the format {"URI": "prefix"}, eg, + /// {"DAV:": "d"} + Future proppatch({ + required String path, + Map? namespaces, + Map? set, + List? remove, + }) async { + try { + final ns = { + "DAV:": "d", + }..addAll(namespaces ?? {}); + final builder = XmlBuilder(); + builder + ..processing("xml", "version=\"1.0\"") + ..element("d:propertyupdate", namespaces: ns, nest: () { + if (set != null && set.isNotEmpty) { + builder.element("d:set", nest: () { + builder.element("d:prop", nest: () { + for (final e in set.entries) { + builder.element(e.key, nest: () { + builder.text("${e.value}"); + }); + } + }); + }); + } + if (remove != null && remove.isNotEmpty) { + builder.element("d:remove", nest: () { + builder.element("d:prop", nest: () { + for (final e in remove) { + builder.element(e); + } + }); + }); + } + }); + return await _api.request( + "PROPPATCH", + path, + header: { + "Content-Type": "application/xml", + }, + body: builder.buildDocument().toXmlString(), + ); + } catch (e) { + _log.severe("[proppatch] Failed while proppatch", e); + rethrow; + } + } + + /// A folder can be created by sending a MKCOL request to the folder + Future mkcol({ + required String path, + }) async { + try { + return await _api.request("MKCOL", path); + } catch (e) { + _log.severe("[mkcol] Failed while get", e); + rethrow; + } + } + + /// A file or folder can be copied by sending a COPY request to the file or + /// folder and specifying the [destinationUrl] as full url + Future copy({ + required String path, + required String destinationUrl, + bool? overwrite, + }) async { + try { + return await _api.request("COPY", path, header: { + "Destination": Uri.parse(destinationUrl).toString(), + if (overwrite != null) "Overwrite": overwrite ? "T" : "F", + }); + } catch (e) { + _log.severe("[copy] Failed while delete", e); + rethrow; + } + } + + /// A file or folder can be moved by sending a MOVE request to the file or + /// folder and specifying the [destinationUrl] as full url + Future move({ + required String path, + required String destinationUrl, + bool? overwrite, + }) async { + try { + return await _api.request("MOVE", path, header: { + "Destination": Uri.parse(destinationUrl).toString(), + if (overwrite != null) "Overwrite": overwrite ? "T" : "F", + }); + } catch (e) { + _log.severe("[move] Failed while delete", e); + rethrow; + } + } + + Future report({ + required String path, + bool? favorite, + List? systemtag, + }) async { + try { + final namespaces = { + "DAV:": "d", + "http://owncloud.org/ns": "oc", + }; + final builder = XmlBuilder(); + builder + ..processing("xml", "version=\"1.0\"") + ..element("oc:filter-files", namespaces: namespaces, nest: () { + builder.element("oc:filter-rules", nest: () { + if (favorite != null) { + builder.element("oc:favorite", nest: () { + builder.text(favorite ? "1" : "0"); + }); + } + for (final t in systemtag ?? []) { + builder.element("oc:systemtag", nest: () { + builder.text(t); + }); + } + }); + builder.element("d:prop", nest: () { + builder.element("oc:fileid"); + }); + }); + return await _api.request( + "REPORT", + path, + header: { + "Content-Type": "application/xml", + }, + body: builder.buildDocument().toXmlString(), + ); + } catch (e) { + _log.severe("[report] Failed while report", e); + rethrow; + } + } +} diff --git a/app/lib/api/files_sharing_api.dart b/app/lib/api/files_sharing_api.dart new file mode 100644 index 00000000..37efce78 --- /dev/null +++ b/app/lib/api/files_sharing_api.dart @@ -0,0 +1,158 @@ +part of 'api.dart'; + +class ApiOcsFilesSharing { + ApiOcsFilesSharing(this._ocs); + + ApiOcsFilesSharingShares shares() => ApiOcsFilesSharingShares(this); + + ApiOcsFilesSharingShare share(String shareId) => + ApiOcsFilesSharingShare(this, shareId); + + ApiOcsFilesSharingSharees sharees() => ApiOcsFilesSharingSharees(this); + + final ApiOcs _ocs; +} + +@npLog +class ApiOcsFilesSharingShares { + ApiOcsFilesSharingShares(this._filesSharing); + + /// Get Shares from a specific file or folder + /// + /// If [sharedWithMe] is not null, [subfiles] and [path] are ignored. This is + /// a limitation of the server API. + /// + /// See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#get-shares-from-a-specific-file-or-folder + /// See: https://doc.owncloud.com/server/latest/developer_manual/core/apis/ocs-share-api.html#get-all-shares + Future get({ + String? path, + bool? reshares, + bool? subfiles, + bool? sharedWithMe, + }) async { + try { + return await _filesSharing._ocs._api.request( + "GET", + "ocs/v2.php/apps/files_sharing/api/v1/shares", + header: { + "OCS-APIRequest": "true", + }, + queryParameters: { + "format": "json", + if (path != null) "path": path, + if (reshares != null) "reshares": reshares.toString(), + if (subfiles != null) "subfiles": subfiles.toString(), + if (sharedWithMe != null) "shared_with_me": sharedWithMe.toString(), + }, + ); + } catch (e) { + _log.severe("[get] Failed while get", e); + rethrow; + } + } + + /// Create a new Share + /// + /// See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#create-a-new-share + Future post({ + required String path, + required int shareType, + String? shareWith, + String? publicUpload, + String? password, + int? permissions, + String? expireDate, + }) async { + try { + return await _filesSharing._ocs._api.request( + "POST", + "ocs/v2.php/apps/files_sharing/api/v1/shares", + header: { + "OCS-APIRequest": "true", + "Content-Type": "application/x-www-form-urlencoded", + }, + queryParameters: { + "format": "json", + "path": path, + "shareType": shareType.toString(), + if (shareWith != null) "shareWith": shareWith, + if (publicUpload != null) "publicUpload": publicUpload, + if (password != null) "password": password, + if (password != null) "password": password, + if (expireDate != null) "expireDate": expireDate.toString(), + }, + ); + } catch (e) { + _log.severe("[post] Failed while post", e); + rethrow; + } + } + + final ApiOcsFilesSharing _filesSharing; +} + +@npLog +class ApiOcsFilesSharingShare { + ApiOcsFilesSharingShare(this._filesSharing, this._shareId); + + /// Remove the given share + /// + /// See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#delete-share + /// * The type of share ID is listed as int in the document, however, the + /// share ID returned in [ApiOcsFilesSharingShares.get] is actually a string. To + /// keep it consistent, we'll use string instead + Future delete() async { + try { + return await _filesSharing._ocs._api.request( + "DELETE", + "ocs/v2.php/apps/files_sharing/api/v1/shares/$_shareId", + header: { + "OCS-APIRequest": "true", + }, + ); + } catch (e) { + _log.severe("[delete] Failed while delete", e); + rethrow; + } + } + + final ApiOcsFilesSharing _filesSharing; + final String _shareId; +} + +@npLog +class ApiOcsFilesSharingSharees { + ApiOcsFilesSharingSharees(this._filesSharing); + + /// Get all sharees matching a search term + /// + /// See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-sharee-api.html#search-sharees + Future get({ + String? search, + bool? lookup, + int? perPage, + String? itemType, + }) async { + try { + return await _filesSharing._ocs._api.request( + "GET", + "ocs/v1.php/apps/files_sharing/api/v1/sharees", + header: { + "OCS-APIRequest": "true", + }, + queryParameters: { + "format": "json", + if (search != null) "search": search, + if (lookup != null) "lookup": lookup.toString(), + if (perPage != null) "perPage": perPage.toString(), + if (itemType != null) "itemType": itemType, + }, + ); + } catch (e) { + _log.severe("[get] Failed while get", e); + rethrow; + } + } + + final ApiOcsFilesSharing _filesSharing; +} diff --git a/app/lib/api/systemtag_api.dart b/app/lib/api/systemtag_api.dart new file mode 100644 index 00000000..a0ff140c --- /dev/null +++ b/app/lib/api/systemtag_api.dart @@ -0,0 +1,142 @@ +part of 'api.dart'; + +@npLog +class ApiSystemtags { + const ApiSystemtags(this.api); + + /// Retrieve a list of all tags + /// + /// See: https://doc.owncloud.com/server/10.10/developer_manual/webdav_api/tags.html#list-tags + Future propfind({ + id, + displayName, + userVisible, + userAssignable, + }) async { + const endpoint = "remote.php/dav/systemtags"; + try { + if (id == null && + displayName == null && + userVisible == null && + userAssignable == null) { + // no body + return await api.request("PROPFIND", endpoint); + } + + final namespaces = { + "DAV:": "d", + "http://owncloud.org/ns": "oc", + }; + final builder = XmlBuilder(); + builder + ..processing("xml", "version=\"1.0\"") + ..element("d:propfind", namespaces: namespaces, nest: () { + builder.element("d:prop", nest: () { + if (id != null) { + builder.element("oc:id"); + } + if (displayName != null) { + builder.element("oc:display-name"); + } + if (userVisible != null) { + builder.element("oc:user-visible"); + } + if (userAssignable != null) { + builder.element("oc:user-assignable"); + } + }); + }); + return await api.request( + "PROPFIND", + endpoint, + header: { + "Content-Type": "application/xml", + }, + body: builder.buildDocument().toXmlString(), + ); + } catch (e) { + _log.severe("[propfind] Failed while propfind", e); + rethrow; + } + } + + final Api api; +} + +class ApiSystemtagsRelations { + const ApiSystemtagsRelations(this.api); + + ApiSystemtagsRelationsFiles files(int fileId) => + ApiSystemtagsRelationsFiles(this, fileId); + + final Api api; +} + +@npLog +class ApiSystemtagsRelationsFiles { + const ApiSystemtagsRelationsFiles(this.systemtagsRelations, this.fileId); + + /// Retrieve the tag ids and metadata of a given file + /// + /// See: https://doc.owncloud.com/server/10.10/developer_manual/webdav_api/tags.html#retrieve-the-tag-ids-and-metadata-of-a-given-file + Future propfind({ + id, + displayName, + userVisible, + userAssignable, + canAssign, + }) async { + final endpoint = "remote.php/dav/systemtags-relations/files/$fileId"; + try { + if (id == null && + displayName == null && + userVisible == null && + userAssignable == null && + canAssign == null) { + // no body + return await systemtagsRelations.api.request("PROPFIND", endpoint); + } + + final namespaces = { + "DAV:": "d", + "http://owncloud.org/ns": "oc", + }; + final builder = XmlBuilder(); + builder + ..processing("xml", "version=\"1.0\"") + ..element("d:propfind", namespaces: namespaces, nest: () { + builder.element("d:prop", nest: () { + if (id != null) { + builder.element("oc:id"); + } + if (displayName != null) { + builder.element("oc:display-name"); + } + if (userVisible != null) { + builder.element("oc:user-visible"); + } + if (userAssignable != null) { + builder.element("oc:user-assignable"); + } + if (canAssign != null) { + builder.element("oc:can-assign"); + } + }); + }); + return await systemtagsRelations.api.request( + "PROPFIND", + endpoint, + header: { + "Content-Type": "application/xml", + }, + body: builder.buildDocument().toXmlString(), + ); + } catch (e) { + _log.severe("[propfind] Failed while propfind", e); + rethrow; + } + } + + final ApiSystemtagsRelations systemtagsRelations; + final int fileId; +}