You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
7.1 KiB
244 lines
7.1 KiB
part of '../../nextcloud.dart'; |
|
|
|
/// WebDavFile class |
|
class WebDavFile { |
|
/// Creates a new WebDavFile object with the given path |
|
WebDavFile({ |
|
required this.path, |
|
required final List<xml.XmlElement> props, |
|
required final Map<String, String> namespaces, |
|
}) : _props = props, |
|
_namespaces = namespaces; |
|
|
|
/// The path of file |
|
final String path; |
|
|
|
final List<xml.XmlElement> _props; |
|
final Map<String, String> _namespaces; |
|
|
|
/// Gets a prop by it's qualified name. It already does namespace matching |
|
xml.XmlElement? getProp(final String qualifiedName) { |
|
final name = xml.XmlName.fromString(qualifiedName); |
|
final namespaceUri = _namespaces.keys.singleWhere((final namespaceUri) => _namespaces[namespaceUri] == name.prefix); |
|
|
|
final matches = |
|
_props.where((final prop) => prop.name.local == name.local && prop.namespaceUri == namespaceUri).toList(); |
|
if (matches.isNotEmpty) { |
|
return matches[0]; |
|
} |
|
return null; |
|
} |
|
|
|
/// The fileid namespaced by the instance id, globally unique |
|
String? get id => getProp(WebDavProps.ocId.name)?.text; |
|
|
|
/// The unique id for the file within the instance |
|
String? get fileId => getProp(WebDavProps.ocFileId.name)?.text; |
|
|
|
/// Whether this is a collection resource type |
|
bool? get isCollection { |
|
final prop = getProp(WebDavProps.davResourceType.name); |
|
if (prop != null) { |
|
return prop.getElement('d:collection') != null; |
|
} |
|
return null; |
|
} |
|
|
|
/// Mime-type of the file |
|
String? get mimeType => getProp(WebDavProps.davContentType.name)?.text; |
|
|
|
/// ETag of the file |
|
String? get etag => getProp(WebDavProps.davETag.name)?.text; |
|
|
|
/// File content length or folder size |
|
int? get size { |
|
for (final prop in [ |
|
getProp(WebDavProps.ocSize.name), |
|
getProp(WebDavProps.davContentLength.name), |
|
]) { |
|
if (prop != null) { |
|
return int.parse(prop.text); |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
/// The user id of the owner of a shared file |
|
String? get ownerId => getProp(WebDavProps.ocOwnerId.name)?.text; |
|
|
|
/// The display name of the owner of a shared file |
|
String? get ownerDisplay => getProp(WebDavProps.ocOwnerDisplayName.name)?.text; |
|
|
|
/// Share note |
|
String? get note => getProp(WebDavProps.ncNote.name)?.text; |
|
|
|
/// Last modified date of the file |
|
DateTime? get lastModified { |
|
final prop = getProp(WebDavProps.davLastModified.name); |
|
if (prop != null) { |
|
return DateFormat('E, d MMM yyyy HH:mm:ss', 'en_US').parseUtc(prop.text); |
|
} |
|
return null; |
|
} |
|
|
|
/// Upload date of the file |
|
DateTime? get uploadedDate { |
|
final prop = getProp(WebDavProps.ncUploadTime.name); |
|
if (prop != null) { |
|
return DateTime.fromMillisecondsSinceEpoch(int.parse(prop.text) * 1000); |
|
} |
|
return null; |
|
} |
|
|
|
/// Creation date of the file as provided by uploader |
|
DateTime? get createdDate { |
|
final prop = getProp(WebDavProps.ncCreationTime.name); |
|
if (prop != null) { |
|
return DateTime.fromMillisecondsSinceEpoch(int.parse(prop.text) * 1000); |
|
} |
|
return null; |
|
} |
|
|
|
/// List of types of shares of the file |
|
List<int>? get shareTypes { |
|
final prop = getProp(WebDavProps.ocShareTypes.name); |
|
if (prop != null) { |
|
return prop.findElements('oc:share-type').map((final element) => int.parse(element.text)).toList(); |
|
} |
|
return null; |
|
} |
|
|
|
/// User IDs of sharees |
|
List<String>? get sharees { |
|
final prop = getProp(WebDavProps.ncShareees.name); |
|
if (prop != null) { |
|
return prop.findAllElements('nc:id').map((final e) => e.text).toList(); |
|
} |
|
return null; |
|
} |
|
|
|
/// Whether this file is marked as favorite |
|
bool? get favorite { |
|
final prop = getProp(WebDavProps.ocFavorite.name); |
|
if (prop != null) { |
|
return prop.text == '1'; |
|
} |
|
return null; |
|
} |
|
|
|
/// Whether this file has a preview image |
|
bool? get hasPreview { |
|
final prop = getProp(WebDavProps.ncHasPreview.name); |
|
if (prop != null) { |
|
return prop.text == 'true'; |
|
} |
|
return null; |
|
} |
|
|
|
/// Returns the decoded name of the file / folder without the whole path |
|
String get name { |
|
// normalised path (remove trailing slash) |
|
final end = path.endsWith('/') ? path.length - 1 : path.length; |
|
final segments = Uri.parse(path, 0, end).pathSegments; |
|
if (segments.isNotEmpty) { |
|
return segments.last; |
|
} |
|
return ''; |
|
} |
|
|
|
/// Returns if the file is a directory |
|
bool get isDirectory => path.endsWith('/') || (isCollection ?? false); |
|
|
|
// coverage:ignore-start |
|
@override |
|
String toString() => |
|
// ignore: lines_longer_than_80_chars |
|
'WebDavFile{name: $name, id: $id, isDirectory: $isDirectory, path: $path, mimeType: $mimeType, size: $size, modificationTime: $lastModified, shareTypes: $shareTypes}'; |
|
// coverage:ignore-end |
|
} |
|
|
|
/// Converts a single d:response to a [WebDavFile] |
|
WebDavFile _fromWebDavXml( |
|
final String basePath, |
|
final Map<String, String> namespaces, |
|
final xml.XmlElement response, |
|
) { |
|
final davItemName = response.findElements('d:href').single.text; |
|
final filePath = Uri.decodeFull(davItemName.substring(Uri.encodeFull(basePath).length, davItemName.length)); |
|
final allProps = <xml.XmlElement>[]; |
|
|
|
final propStatElements = response.findElements('d:propstat'); |
|
for (final propStat in propStatElements) { |
|
final status = propStat.getElement('d:status')!.text; |
|
final props = propStat.getElement('d:prop'); |
|
|
|
if (!status.contains('200')) { |
|
// Skip any props that are not returned correctly (e.g. not found) |
|
continue; |
|
} |
|
for (final prop in props!.nodes.whereType<xml.XmlElement>()) { |
|
if (prop.children.isNotEmpty && prop.text.isNotEmpty) { |
|
allProps.add(prop); |
|
} |
|
} |
|
} |
|
|
|
return WebDavFile( |
|
path: filePath, |
|
props: allProps, |
|
namespaces: namespaces, |
|
); |
|
} |
|
|
|
/// Extract a file from the webdav xml |
|
WebDavFile fileFromWebDavXml( |
|
final String basePath, |
|
final Map<String, String> namespaces, |
|
final String xmlStr, |
|
) { |
|
final xmlDocument = xml.XmlDocument.parse(xmlStr); |
|
return _fromWebDavXml( |
|
basePath, |
|
namespaces, |
|
xmlDocument.findAllElements('d:response').single, |
|
); |
|
} |
|
|
|
/// Extract the file tree from the webdav xml |
|
List<WebDavFile> treeFromWebDavXml( |
|
final String basePath, |
|
final Map<String, String> namespaces, |
|
final String xmlStr, |
|
) { |
|
// Initialize a list to store the FileInfo Objects |
|
final tree = []; |
|
|
|
// parse the xml using the xml.XmlDocument.parse method |
|
final xmlDocument = xml.XmlDocument.parse(xmlStr); |
|
|
|
// Iterate over the response to find all folders / files and parse the information |
|
for (final response in xmlDocument.findAllElements('d:response')) { |
|
tree.add( |
|
_fromWebDavXml( |
|
basePath, |
|
namespaces, |
|
response, |
|
), |
|
); |
|
} |
|
return tree.cast<WebDavFile>(); |
|
} |
|
|
|
/// Returns false if some updates have failed. |
|
bool checkUpdateFromWebDavXml(final String xmlStr) { |
|
final xmlDocument = xml.XmlDocument.parse(xmlStr); |
|
final response = xmlDocument.findAllElements('d:response').single; |
|
final propStatElements = response.findElements('d:propstat'); |
|
for (final propStat in propStatElements) { |
|
final status = propStat.getElement('d:status')!.text; |
|
if (!status.contains('200')) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
}
|
|
|