diff --git a/Sources/NextcloudKit/Models/NKDownloadLimit.swift b/Sources/NextcloudKit/Models/NKDownloadLimit.swift
new file mode 100644
index 00000000..b3722a4c
--- /dev/null
+++ b/Sources/NextcloudKit/Models/NKDownloadLimit.swift
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: Nextcloud GmbH
+// SPDX-FileCopyrightText: 2024 Iva Horn
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import Foundation
+
+///
+/// Data model for a download limit as returned in the WebDAV response for file properties.
+///
+/// Each relates to a share of a file and is optionally provided by the [Files Download Limit](https://github.com/nextcloud/files_downloadlimit) app for Nextcloud server.
+///
+public class NKDownloadLimit: NSObject {
+ ///
+ /// The number of downloads which already happened.
+ ///
+ public let count: Int
+
+ ///
+ /// Total number of allowed downloas.
+ ///
+ public let limit: Int
+
+ ///
+ /// The token identifying the related share.
+ ///
+ public let token: String
+
+ init(count: Int, limit: Int, token: String) {
+ self.count = count
+ self.limit = limit
+ self.token = token
+ }
+}
diff --git a/Sources/NextcloudKit/Models/NKFile.swift b/Sources/NextcloudKit/Models/NKFile.swift
index 1da7b9d5..dde57ee8 100644
--- a/Sources/NextcloudKit/Models/NKFile.swift
+++ b/Sources/NextcloudKit/Models/NKFile.swift
@@ -16,6 +16,12 @@ public class NKFile: NSObject {
public var date = Date()
public var directory: Bool = false
public var downloadURL = ""
+
+ ///
+ /// Download limits for shares of this file.
+ ///
+ public var downloadLimits = [NKDownloadLimit]()
+
public var e2eEncrypted: Bool = false
public var etag = ""
public var favorite: Bool = false
diff --git a/Sources/NextcloudKit/Models/NKProperties.swift b/Sources/NextcloudKit/Models/NKProperties.swift
index 929d4473..f5482f58 100644
--- a/Sources/NextcloudKit/Models/NKProperties.swift
+++ b/Sources/NextcloudKit/Models/NKProperties.swift
@@ -3,9 +3,18 @@
// SPDX-FileCopyrightText: 2023 Claudio Cambra
// SPDX-License-Identifier: GPL-3.0-or-later
+///
+/// Definition of properties used for decoding in ``NKDataFileXML``.
+///
public enum NKProperties: String, CaseIterable {
/// DAV
case displayname = ""
+
+ ///
+ /// Download limits for shares of a file as optionally provided by the [Files Download Limit](https://github.com/nextcloud/files_downloadlimit) app for Nextcloud server.
+ ///
+ case downloadLimit = ""
+
case getlastmodified = ""
case getetag = ""
case getcontenttype = ""
diff --git a/Sources/NextcloudKit/Models/NKRecommendedFiles.swift b/Sources/NextcloudKit/Models/NKRecommendedFiles.swift
new file mode 100644
index 00000000..186b1d50
--- /dev/null
+++ b/Sources/NextcloudKit/Models/NKRecommendedFiles.swift
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: Nextcloud GmbH
+// SPDX-FileCopyrightText: 2024 Marino Faggiana
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import Foundation
+import SwiftyXMLParser
+
+public class NKRecommendation: NSObject {
+ public var id: String
+ public var timestamp: Date?
+ public var name: String
+ public var directory: String
+ public var extensionType: String
+ public var mimeType: String
+ public var hasPreview: Bool
+ public var reason: String
+
+ public init(id: String, timestamp: Date?, name: String, directory: String, extensionType: String, mimeType: String, hasPreview: Bool, reason: String) {
+ self.id = id
+ self.timestamp = timestamp
+ self.name = name
+ self.directory = directory
+ self.extensionType = extensionType
+ self.mimeType = mimeType
+ self.hasPreview = hasPreview
+ self.reason = reason
+ }
+}
+
+class XMLToRecommendationParser {
+ func parse(xml: String) -> [NKRecommendation]? {
+ guard let data = xml.data(using: .utf8) else { return nil }
+ let xml = XML.parse(data)
+
+ // Parsing "enabled"
+ guard let enabledString = xml["ocs", "data", "enabled"].text,
+ Bool(enabledString == "1")
+ else {
+ return nil
+ }
+
+ // Parsing "recommendations"
+ var recommendations: [NKRecommendation] = []
+ let elements = xml["ocs", "data", "recommendations", "element"]
+
+ for element in elements {
+ let id = element["id"].text ?? ""
+ var timestamp: Date?
+ if let timestampDouble = element["timestamp"].double, timestampDouble > 0 {
+ timestamp = Date(timeIntervalSince1970: timestampDouble)
+ }
+ let name = element["name"].text ?? ""
+ let directory = element["directory"].text ?? ""
+ let extensionType = element["extension"].text ?? ""
+ let mimeType = element["mimeType"].text ?? ""
+ let hasPreview = element["hasPreview"].text == "1"
+ let reason = element["reason"].text ?? ""
+
+ let recommendation = NKRecommendation(
+ id: id,
+ timestamp: timestamp,
+ name: name,
+ directory: directory,
+ extensionType: extensionType,
+ mimeType: mimeType,
+ hasPreview: hasPreview,
+ reason: reason
+ )
+ recommendations.append(recommendation)
+ }
+
+ return recommendations
+ }
+}
diff --git a/Sources/NextcloudKit/NKDataFileXML.swift b/Sources/NextcloudKit/NKDataFileXML.swift
index c01d18e8..eef09d65 100644
--- a/Sources/NextcloudKit/NKDataFileXML.swift
+++ b/Sources/NextcloudKit/NKDataFileXML.swift
@@ -560,7 +560,23 @@ class NKDataFileXML: NSObject {
}
file.placePhotos = propstat["d:prop", "nc:metadata-photos-place"].text
-
+
+ for downloadLimit in propstat["d:prop", "nc:share-download-limits", "nc:share-download-limit"] {
+ guard let token = downloadLimit["nc:token"].text else {
+ continue
+ }
+
+ guard let limit = downloadLimit["nc:limit"].int else {
+ continue
+ }
+
+ guard let count = downloadLimit["nc:count"].int else {
+ continue
+ }
+
+ file.downloadLimits.append(NKDownloadLimit(count: count, limit: limit, token: token))
+ }
+
let results = self.nkCommonInstance.getInternalType(fileName: file.fileName, mimeType: file.contentType, directory: file.directory, account: nkSession.account)
file.contentType = results.mimeType
diff --git a/Sources/NextcloudKit/NextcloudKit+RecommendedFiles.swift b/Sources/NextcloudKit/NextcloudKit+RecommendedFiles.swift
new file mode 100644
index 00000000..172fb58c
--- /dev/null
+++ b/Sources/NextcloudKit/NextcloudKit+RecommendedFiles.swift
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: Nextcloud GmbH
+// SPDX-FileCopyrightText: 2024 Marino Faggiana
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import Foundation
+import Alamofire
+import SwiftyJSON
+
+public extension NextcloudKit {
+ func getRecommendedFiles(account: String,
+ options: NKRequestOptions = NKRequestOptions(),
+ request: @escaping (DataRequest?) -> Void = { _ in },
+ taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
+ completion: @escaping (_ account: String, _ recommendations: [NKRecommendation]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) {
+ let endpoint = "ocs/v2.php/apps/recommendations/api/v1/recommendations"
+ ///
+ options.contentType = "application/xml"
+ ///
+ guard let nkSession = nkCommonInstance.getSession(account: account),
+ let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options),
+ let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else {
+ return options.queue.async { completion(account, nil, nil, .urlError) }
+ }
+
+ let tosRequest = nkSession.sessionData.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: nil).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in
+ task.taskDescription = options.taskDescription
+ taskHandler(task)
+ }.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in
+ if self.nkCommonInstance.levelLog > 0 {
+ debugPrint(response)
+ }
+ switch response.result {
+ case .success(let data):
+ if let xmlString = String(data: data, encoding: .utf8) {
+ let parser = XMLToRecommendationParser()
+ if let recommendations = parser.parse(xml: xmlString) {
+ options.queue.async { completion(account, recommendations, response, .success) }
+ } else {
+ options.queue.async { completion(account, nil, response, .xmlError) }
+ }
+ } else {
+ options.queue.async { completion(account, nil, response, .xmlError) }
+ }
+ case .failure(let error):
+ let error = NKError(error: error, afResponse: response, responseData: response.data)
+ options.queue.async {
+ completion(account, nil, response, error)
+ }
+ }
+ }
+ options.queue.async { request(tosRequest) }
+ }
+}
diff --git a/Sources/NextcloudKit/NextcloudKit+ShareDownloadLimit.swift b/Sources/NextcloudKit/NextcloudKit+ShareDownloadLimit.swift
new file mode 100644
index 00000000..448e86ec
--- /dev/null
+++ b/Sources/NextcloudKit/NextcloudKit+ShareDownloadLimit.swift
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: Nextcloud GmbH
+// SPDX-FileCopyrightText: 2024 Iva Horn
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import Alamofire
+import Foundation
+
+public extension NextcloudKit {
+ private func makeEndpoint(with token: String) -> String {
+ "ocs/v2.php/apps/files_downloadlimit/api/v1/\(token)/limit"
+ }
+
+ func removeShareDownloadLimit(account: String, token: String, completion: @escaping (_ error: NKError) -> Void) {
+ let endpoint = makeEndpoint(with: token)
+ let options = NKRequestOptions()
+
+ guard let nkSession = nkCommonInstance.getSession(account: account),
+ let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options),
+ let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else {
+ return options.queue.async {
+ completion(.urlError)
+ }
+ }
+
+ nkSession
+ .sessionData
+ .request(url, method: .delete, parameters: nil, encoding: URLEncoding.default, headers: headers, interceptor: nil)
+ .validate(statusCode: 200..<300)
+ .response(queue: self.nkCommonInstance.backgroundQueue) { response in
+ if self.nkCommonInstance.levelLog > 0 {
+ debugPrint(response)
+ }
+
+ switch response.result {
+ case .failure(let error):
+ let error = NKError(error: error, afResponse: response, responseData: response.data)
+
+ options.queue.async {
+ completion(error)
+ }
+ case .success:
+ options.queue.async {
+ completion(.success)
+ }
+ }
+ }
+ }
+
+ func setShareDownloadLimit(account: String, token: String, limit: Int, completion: @escaping (_ error: NKError) -> Void) {
+ let endpoint = makeEndpoint(with: token)
+ let options = NKRequestOptions()
+ options.contentType = "application/json"
+
+ guard let nkSession = nkCommonInstance.getSession(account: account),
+ let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options),
+ let headers = nkCommonInstance.getStandardHeaders(account: account, options: options),
+ var urlRequest = try? URLRequest(url: url, method: .put, headers: headers) else {
+ return options.queue.async {
+ completion(.urlError)
+ }
+ }
+
+ urlRequest.httpBody = try? JSONEncoder().encode([
+ "limit": limit
+ ])
+
+ nkSession
+ .sessionData
+ .request(urlRequest)
+ .validate(statusCode: 200..<300)
+ .response(queue: self.nkCommonInstance.backgroundQueue) { response in
+ if self.nkCommonInstance.levelLog > 0 {
+ debugPrint(response)
+ }
+
+ switch response.result {
+ case .failure(let error):
+ let error = NKError(error: error, afResponse: response, responseData: response.data)
+
+ options.queue.async {
+ completion(error)
+ }
+ case .success:
+ options.queue.async {
+ completion(.success)
+ }
+ }
+ }
+ }
+}
diff --git a/Sources/NextcloudKit/NextcloudKit+Upload.swift b/Sources/NextcloudKit/NextcloudKit+Upload.swift
index a8d3ffa7..6438f082 100644
--- a/Sources/NextcloudKit/NextcloudKit+Upload.swift
+++ b/Sources/NextcloudKit/NextcloudKit+Upload.swift
@@ -96,7 +96,9 @@ public extension NextcloudKit {
/// - chunkSizeInMB: Size in MB of chunk
func uploadChunk(directory: String,
+ fileChunksOutputDirectory: String? = nil,
fileName: String,
+ destinationFileName: String? = nil,
date: Date?,
creationDate: Date?,
serverUrl: String,
@@ -119,7 +121,7 @@ public extension NextcloudKit {
}
let fileNameLocalSize = self.nkCommonInstance.getFileSize(filePath: directory + "/" + fileName)
let serverUrlChunkFolder = nkSession.urlBase + "/" + nkSession.dav + "/uploads/" + nkSession.userId + "/" + chunkFolder
- let serverUrlFileName = nkSession.urlBase + "/" + nkSession.dav + "/files/" + nkSession.userId + self.nkCommonInstance.returnPathfromServerUrl(serverUrl, urlBase: nkSession.urlBase, userId: nkSession.userId) + "/" + fileName
+ let serverUrlFileName = nkSession.urlBase + "/" + nkSession.dav + "/files/" + nkSession.userId + self.nkCommonInstance.returnPathfromServerUrl(serverUrl, urlBase: nkSession.urlBase, userId: nkSession.userId) + "/" + (destinationFileName ?? fileName)
if options.customHeader == nil {
options.customHeader = [:]
}
@@ -175,7 +177,8 @@ public extension NextcloudKit {
var uploadNKError = NKError()
var uploadAFError: AFError?
- self.nkCommonInstance.chunkedFile(inputDirectory: directory, outputDirectory: directory, fileName: fileName, chunkSize: chunkSize, filesChunk: filesChunk) { num in
+ let outputDirectory = fileChunksOutputDirectory ?? directory
+ self.nkCommonInstance.chunkedFile(inputDirectory: directory, outputDirectory: outputDirectory, fileName: fileName, chunkSize: chunkSize, filesChunk: filesChunk) { num in
numChunks(num)
} counterChunk: { counter in
counterChunk(counter)
@@ -190,7 +193,7 @@ public extension NextcloudKit {
for fileChunk in filesChunk {
let serverUrlFileName = serverUrlChunkFolder + "/" + fileChunk.fileName
- let fileNameLocalPath = directory + "/" + fileChunk.fileName
+ let fileNameLocalPath = outputDirectory + "/" + fileChunk.fileName
let fileSize = self.nkCommonInstance.getFileSize(filePath: fileNameLocalPath)
if fileSize == 0 {
// The file could not be sent