NCNetworking+WebDAV.swift 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. //
  2. // NCNetworking+WebDAV.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 07/02/24.
  6. // Copyright © 2024 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import UIKit
  24. import JGProgressHUD
  25. import NextcloudKit
  26. import Alamofire
  27. import Queuer
  28. import Photos
  29. extension NCNetworking {
  30. // MARK: - Read file, folder
  31. func readFolder(serverUrl: String,
  32. account: String,
  33. forceReplaceMetadatas: Bool = false,
  34. taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
  35. completion: @escaping (_ account: String, _ metadataFolder: tableMetadata?, _ metadatas: [tableMetadata]?, _ metadatasDifferentCount: Int, _ metadatasModified: Int, _ error: NKError) -> Void) {
  36. NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl,
  37. depth: "1",
  38. showHiddenFiles: NCKeychain().showHiddenFiles,
  39. includeHiddenFiles: NCGlobal.shared.includeHiddenFiles,
  40. account: account,
  41. options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { task in
  42. taskHandler(task)
  43. } completion: { account, files, _, error in
  44. guard error == .success else {
  45. return completion(account, nil, nil, 0, 0, error)
  46. }
  47. NCManageDatabase.shared.convertFilesToMetadatas(files, useFirstAsMetadataFolder: true) { metadataFolder, metadatas in
  48. NCManageDatabase.shared.addMetadata(metadataFolder)
  49. NCManageDatabase.shared.addDirectory(e2eEncrypted: metadataFolder.e2eEncrypted, favorite: metadataFolder.favorite, ocId: metadataFolder.ocId, fileId: metadataFolder.fileId, etag: metadataFolder.etag, permissions: metadataFolder.permissions, richWorkspace: metadataFolder.richWorkspace, serverUrl: serverUrl, account: metadataFolder.account)
  50. #if !EXTENSION
  51. // Convert Live Photo
  52. for metadata in metadatas {
  53. if NCGlobal.shared.isLivePhotoServerAvailable, metadata.isLivePhoto {
  54. NCNetworking.shared.convertLivePhoto(metadata: metadata)
  55. }
  56. }
  57. #endif
  58. let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal)
  59. if forceReplaceMetadatas {
  60. NCManageDatabase.shared.replaceMetadata(metadatas, predicate: predicate)
  61. completion(account, metadataFolder, metadatas, 1, 1, error)
  62. } else {
  63. let results = NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
  64. completion(account, metadataFolder, metadatas, results.metadatasDifferentCount, results.metadatasModified, error)
  65. }
  66. }
  67. }
  68. }
  69. func readFile(serverUrlFileName: String,
  70. showHiddenFiles: Bool = NCKeychain().showHiddenFiles,
  71. account: String,
  72. queue: DispatchQueue = NextcloudKit.shared.nkCommonInstance.backgroundQueue,
  73. taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
  74. completion: @escaping (_ account: String, _ metadata: tableMetadata?, _ error: NKError) -> Void) {
  75. let options = NKRequestOptions(queue: queue)
  76. NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName, depth: "0", showHiddenFiles: showHiddenFiles, account: account, options: options) { task in
  77. taskHandler(task)
  78. } completion: { account, files, _, error in
  79. guard error == .success, files.count == 1, let file = files.first else {
  80. completion(account, nil, error)
  81. return
  82. }
  83. let isDirectoryE2EE = self.utilityFileSystem.isDirectoryE2EE(file: file)
  84. let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
  85. completion(account, metadata, error)
  86. }
  87. }
  88. func fileExists(serverUrlFileName: String,
  89. account: String,
  90. completion: @escaping (_ account: String, _ exists: Bool?, _ file: NKFile?, _ error: NKError) -> Void) {
  91. let options = NKRequestOptions(timeout: 10, createProperties: [], queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
  92. NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName,
  93. depth: "0",
  94. account: account,
  95. options: options) { account, files, _, error in
  96. if error == .success, let file = files.first {
  97. completion(account, true, file, error)
  98. } else if error.errorCode == NCGlobal.shared.errorResourceNotFound {
  99. completion(account, false, nil, error)
  100. } else {
  101. completion(account, nil, nil, error)
  102. }
  103. }
  104. }
  105. func fileExists(serverUrlFileName: String, account: String) async -> (account: String, exists: Bool?, file: NKFile?, error: NKError) {
  106. await withUnsafeContinuation({ continuation in
  107. fileExists(serverUrlFileName: serverUrlFileName, account: account) { account, exists, file, error in
  108. continuation.resume(returning: (account, exists, file, error))
  109. }
  110. })
  111. }
  112. func createFileName(fileNameBase: String, account: String, serverUrl: String) async -> String {
  113. var exitLoop = false
  114. var resultFileName = fileNameBase
  115. func newFileName() {
  116. var name = NSString(string: resultFileName).deletingPathExtension
  117. let ext = NSString(string: resultFileName).pathExtension
  118. let characters = Array(name)
  119. if characters.count < 2 {
  120. if ext.isEmpty {
  121. resultFileName = name + " 1"
  122. } else {
  123. resultFileName = name + " 1" + "." + ext
  124. }
  125. } else {
  126. let space = characters[characters.count - 2]
  127. let numChar = characters[characters.count - 1]
  128. var num = Int(String(numChar))
  129. if space == " " && num != nil {
  130. name = String(name.dropLast())
  131. num = num! + 1
  132. if ext.isEmpty {
  133. resultFileName = name + "\(num!)"
  134. } else {
  135. resultFileName = name + "\(num!)" + "." + ext
  136. }
  137. } else {
  138. if ext.isEmpty {
  139. resultFileName = name + " 1"
  140. } else {
  141. resultFileName = name + " 1" + "." + ext
  142. }
  143. }
  144. }
  145. }
  146. while !exitLoop {
  147. if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "fileNameView == %@ AND serverUrl == %@ AND account == %@", resultFileName, serverUrl, account)) != nil {
  148. newFileName()
  149. continue
  150. }
  151. let results = await fileExists(serverUrlFileName: serverUrl + "/" + resultFileName, account: account)
  152. if let exists = results.exists, exists {
  153. newFileName()
  154. } else {
  155. exitLoop = true
  156. }
  157. }
  158. return resultFileName
  159. }
  160. // MARK: - Create Folder
  161. func createFolder(fileName: String,
  162. serverUrl: String,
  163. account: String,
  164. urlBase: String,
  165. userId: String,
  166. overwrite: Bool = false,
  167. withPush: Bool,
  168. sceneIdentifier: String?,
  169. completion: @escaping (_ error: NKError) -> Void) {
  170. let isDirectoryEncrypted = utilityFileSystem.isDirectoryE2EE(account: account, urlBase: urlBase, userId: userId, serverUrl: serverUrl)
  171. let fileName = fileName.trimmingCharacters(in: .whitespacesAndNewlines)
  172. if isDirectoryEncrypted {
  173. #if !EXTENSION
  174. Task {
  175. let error = await NCNetworkingE2EECreateFolder().createFolder(fileName: fileName, serverUrl: serverUrl, account: account, urlBase: urlBase, userId: userId, withPush: withPush, sceneIdentifier: sceneIdentifier)
  176. completion(error)
  177. }
  178. #endif
  179. } else {
  180. createFolderPlain(fileName: fileName, serverUrl: serverUrl, account: account, urlBase: urlBase, overwrite: overwrite, withPush: withPush, sceneIdentifier: sceneIdentifier, completion: completion)
  181. }
  182. }
  183. private func createFolderPlain(fileName: String,
  184. serverUrl: String,
  185. account: String,
  186. urlBase: String,
  187. overwrite: Bool,
  188. withPush: Bool,
  189. sceneIdentifier: String?,
  190. completion: @escaping (_ error: NKError) -> Void) {
  191. var fileNameFolder = utility.removeForbiddenCharacters(fileName)
  192. if fileName != fileNameFolder {
  193. let errorDescription = String(format: NSLocalizedString("_forbidden_characters_", comment: ""), NCGlobal.shared.forbiddenCharacters.joined(separator: " "))
  194. let error = NKError(errorCode: NCGlobal.shared.errorConflict, errorDescription: errorDescription)
  195. return completion(error)
  196. }
  197. if !overwrite {
  198. fileNameFolder = utilityFileSystem.createFileName(fileNameFolder, serverUrl: serverUrl, account: account)
  199. }
  200. if fileNameFolder.isEmpty {
  201. return completion(NKError())
  202. }
  203. let fileNameFolderUrl = serverUrl + "/" + fileNameFolder
  204. NextcloudKit.shared.createFolder(serverUrlFileName: fileNameFolderUrl, account: account) { account, _, _, error in
  205. guard error == .success else {
  206. if error.errorCode == NCGlobal.shared.errorMethodNotSupported && overwrite {
  207. completion(NKError())
  208. } else {
  209. completion(error)
  210. }
  211. return
  212. }
  213. self.readFile(serverUrlFileName: fileNameFolderUrl, account: account) { account, metadataFolder, error in
  214. if error == .success {
  215. if let metadata = metadataFolder {
  216. NCManageDatabase.shared.addMetadata(metadata)
  217. NCManageDatabase.shared.addDirectory(e2eEncrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, permissions: metadata.permissions, serverUrl: fileNameFolderUrl, account: account)
  218. }
  219. if let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadataFolder?.ocId) {
  220. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterCreateFolder, userInfo: ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "withPush": withPush, "sceneIdentifier": sceneIdentifier as Any])
  221. }
  222. }
  223. completion(error)
  224. }
  225. }
  226. }
  227. func createFolder(assets: [PHAsset]?,
  228. useSubFolder: Bool,
  229. account: String,
  230. urlBase: String,
  231. userId: String,
  232. withPush: Bool,
  233. sceneIdentifier: String? = nil) -> Bool {
  234. let autoUploadPath = NCManageDatabase.shared.getAccountAutoUploadPath(urlBase: urlBase, userId: userId, account: account)
  235. let serverUrlBase = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: urlBase, userId: userId, account: account)
  236. let fileNameBase = NCManageDatabase.shared.getAccountAutoUploadFileName()
  237. let autoUploadSubfolderGranularity = NCManageDatabase.shared.getAccountAutoUploadSubfolderGranularity()
  238. func createFolder(fileName: String, serverUrl: String) -> Bool {
  239. var result: Bool = false
  240. let semaphore = DispatchSemaphore(value: 0)
  241. NCNetworking.shared.createFolder(fileName: fileName, serverUrl: serverUrl, account: account, urlBase: urlBase, userId: userId, overwrite: true, withPush: withPush, sceneIdentifier: sceneIdentifier) { error in
  242. if error == .success { result = true }
  243. semaphore.signal()
  244. }
  245. semaphore.wait()
  246. return result
  247. }
  248. func createNameSubFolder() -> [String] {
  249. var datesSubFolder: [String] = []
  250. if let assets {
  251. for asset in assets {
  252. datesSubFolder.append(utilityFileSystem.createGranularityPath(asset: asset))
  253. }
  254. } else {
  255. datesSubFolder.append(utilityFileSystem.createGranularityPath())
  256. }
  257. return Array(Set(datesSubFolder))
  258. }
  259. var result = createFolder(fileName: fileNameBase, serverUrl: serverUrlBase)
  260. if useSubFolder && result {
  261. for dateSubFolder in createNameSubFolder() {
  262. let subfolderArray = dateSubFolder.split(separator: "/")
  263. let year = subfolderArray[0]
  264. let serverUrlYear = autoUploadPath
  265. result = createFolder(fileName: String(year), serverUrl: serverUrlYear) // Year always present independently of preference value
  266. if result && autoUploadSubfolderGranularity >= NCGlobal.shared.subfolderGranularityMonthly {
  267. let month = subfolderArray[1]
  268. let serverUrlMonth = autoUploadPath + "/" + year
  269. result = createFolder(fileName: String(month), serverUrl: serverUrlMonth)
  270. if result && autoUploadSubfolderGranularity == NCGlobal.shared.subfolderGranularityDaily {
  271. let day = subfolderArray[2]
  272. let serverUrlDay = autoUploadPath + "/" + year + "/" + month
  273. result = createFolder(fileName: String(day), serverUrl: serverUrlDay)
  274. }
  275. }
  276. if !result { break }
  277. }
  278. }
  279. return result
  280. }
  281. // MARK: - Delete
  282. func deleteMetadata(_ metadata: tableMetadata, onlyLocalCache: Bool) async -> (NKError) {
  283. if onlyLocalCache {
  284. #if !EXTENSION
  285. NCActivityIndicator.shared.start()
  286. #endif
  287. func delete(metadata: tableMetadata) {
  288. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
  289. NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "account == %@ AND ocId == %@", metadataLive.account, metadataLive.ocId))
  290. utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
  291. }
  292. NCManageDatabase.shared.deleteVideo(metadata: metadata)
  293. NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "account == %@ AND ocId == %@", metadata.account, metadata.ocId))
  294. utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
  295. }
  296. if metadata.directory {
  297. let serverUrl = metadata.serverUrl + "/" + metadata.fileName
  298. if let metadatas = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND directory == false", metadata.account, serverUrl)) {
  299. for metadata in metadatas {
  300. delete(metadata: metadata)
  301. }
  302. }
  303. } else {
  304. delete(metadata: metadata)
  305. }
  306. #if !EXTENSION
  307. NCActivityIndicator.shared.stop()
  308. #endif
  309. return NKError()
  310. }
  311. if metadata.isDirectoryE2EE {
  312. #if !EXTENSION
  313. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
  314. let error = await NCNetworkingE2EEDelete().delete(metadata: metadataLive)
  315. if error == .success {
  316. return await NCNetworkingE2EEDelete().delete(metadata: metadata)
  317. } else {
  318. return error
  319. }
  320. } else {
  321. return await NCNetworkingE2EEDelete().delete(metadata: metadata)
  322. }
  323. #else
  324. return NKError()
  325. #endif
  326. } else {
  327. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
  328. let error = await deleteMetadataPlain(metadataLive)
  329. if error == .success {
  330. return await deleteMetadataPlain(metadata)
  331. } else {
  332. return error
  333. }
  334. } else {
  335. return await deleteMetadataPlain(metadata)
  336. }
  337. }
  338. }
  339. func deleteMetadataPlain(_ metadata: tableMetadata, customHeader: [String: String]? = nil) async -> NKError {
  340. // verify permission
  341. let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCPermissions().permissionCanDelete)
  342. if !metadata.permissions.isEmpty && permission == false {
  343. return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_delete_file_")
  344. }
  345. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  346. let options = NKRequestOptions(customHeader: customHeader)
  347. let result = await NCNetworking.shared.deleteFileOrFolder(serverUrlFileName: serverUrlFileName, account: metadata.account ,options: options)
  348. if result.error == .success || result.error.errorCode == NCGlobal.shared.errorResourceNotFound {
  349. do {
  350. try FileManager.default.removeItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
  351. } catch { }
  352. NCManageDatabase.shared.deleteVideo(metadata: metadata)
  353. NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
  354. NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
  355. // LIVE PHOTO SERVER
  356. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadataLive.isFlaggedAsLivePhotoByServer {
  357. do {
  358. try FileManager.default.removeItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
  359. } catch { }
  360. NCManageDatabase.shared.deleteVideo(metadata: metadataLive)
  361. NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
  362. NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
  363. }
  364. if metadata.directory {
  365. NCManageDatabase.shared.deleteDirectoryAndSubDirectory(serverUrl: utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName), account: metadata.account)
  366. }
  367. }
  368. return result.error
  369. }
  370. // MARK: - Rename
  371. func renameMetadata(_ metadata: tableMetadata,
  372. fileNameNew: String,
  373. indexPath: IndexPath,
  374. completion: @escaping (_ error: NKError) -> Void) {
  375. let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata)
  376. let fileNameNew = fileNameNew.trimmingCharacters(in: .whitespacesAndNewlines)
  377. let fileNameNewLive = (fileNameNew as NSString).deletingPathExtension + ".mov"
  378. if metadata.isDirectoryE2EE {
  379. #if !EXTENSION
  380. Task {
  381. if let metadataLive = metadataLive {
  382. let error = await NCNetworkingE2EERename().rename(metadata: metadataLive, fileNameNew: fileNameNew, indexPath: indexPath)
  383. if error == .success {
  384. let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew, indexPath: indexPath)
  385. DispatchQueue.main.async { completion(error) }
  386. } else {
  387. DispatchQueue.main.async { completion(error) }
  388. }
  389. } else {
  390. let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew, indexPath: indexPath)
  391. DispatchQueue.main.async { completion(error) }
  392. }
  393. }
  394. #endif
  395. } else {
  396. if let metadataLive, metadata.isNotFlaggedAsLivePhotoByServer {
  397. renameMetadataPlain(metadataLive, fileNameNew: fileNameNewLive, indexPath: indexPath) { error in
  398. if error == .success {
  399. self.renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
  400. } else {
  401. completion(error)
  402. }
  403. }
  404. } else {
  405. renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
  406. }
  407. }
  408. }
  409. private func renameMetadataPlain(_ metadata: tableMetadata,
  410. fileNameNew: String,
  411. indexPath: IndexPath,
  412. completion: @escaping (_ error: NKError) -> Void) {
  413. let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCPermissions().permissionCanRename)
  414. if !metadata.permissions.isEmpty && !permission {
  415. return completion(NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_"))
  416. }
  417. let fileName = utility.removeForbiddenCharacters(fileNameNew)
  418. if fileName != fileNameNew {
  419. let errorDescription = String(format: NSLocalizedString("_forbidden_characters_", comment: ""), NCGlobal.shared.forbiddenCharacters.joined(separator: " "))
  420. let error = NKError(errorCode: NCGlobal.shared.errorConflict, errorDescription: errorDescription)
  421. return completion(error)
  422. }
  423. let fileNameNew = fileName
  424. if fileNameNew.isEmpty || fileNameNew == metadata.fileNameView {
  425. return completion(NKError())
  426. }
  427. let fileNamePath = metadata.serverUrl + "/" + metadata.fileName
  428. let fileNameToPath = metadata.serverUrl + "/" + fileNameNew
  429. NextcloudKit.shared.moveFileOrFolder(serverUrlFileNameSource: fileNamePath, serverUrlFileNameDestination: fileNameToPath, overwrite: false, account: metadata.account) { _, error in
  430. if error == .success {
  431. NCManageDatabase.shared.renameMetadata(fileNameTo: fileNameNew, ocId: metadata.ocId)
  432. if metadata.directory {
  433. let serverUrl = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName)
  434. let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: fileNameNew)
  435. if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) {
  436. NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, serverUrlTo: serverUrlTo, etag: "", encrypted: directory.e2eEncrypted, account: metadata.account)
  437. }
  438. } else {
  439. if (metadata.fileName as NSString).pathExtension != (fileNameNew as NSString).pathExtension {
  440. let path = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)
  441. self.utilityFileSystem.removeFile(atPath: path)
  442. } else {
  443. NCManageDatabase.shared.setLocalFile(ocId: metadata.ocId, fileName: fileNameNew)
  444. let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + metadata.fileName
  445. let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + fileNameNew
  446. self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath)
  447. }
  448. }
  449. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRenameFile, userInfo: ["ocId": metadata.ocId, "account": metadata.account, "indexPath": indexPath])
  450. }
  451. completion(error)
  452. }
  453. }
  454. // MARK: - Move
  455. func moveMetadata(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
  456. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
  457. let error = await moveMetadataPlain(metadataLive, serverUrlTo: serverUrlTo, overwrite: overwrite)
  458. if error == .success {
  459. return await moveMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
  460. } else {
  461. return error
  462. }
  463. }
  464. return await moveMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
  465. }
  466. private func moveMetadataPlain(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
  467. let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCPermissions().permissionCanRename)
  468. if !metadata.permissions.isEmpty && !permission {
  469. return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")
  470. }
  471. let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName
  472. let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName
  473. let result = await NCNetworking.shared.moveFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: overwrite, account: metadata.account)
  474. if result.error == .success {
  475. if metadata.directory {
  476. NCManageDatabase.shared.deleteDirectoryAndSubDirectory(serverUrl: utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName), account: result.account)
  477. } else {
  478. do {
  479. try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
  480. } catch { }
  481. NCManageDatabase.shared.deleteVideo(metadata: metadata)
  482. NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
  483. NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
  484. // LIVE PHOTO SERVER
  485. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadataLive.isFlaggedAsLivePhotoByServer {
  486. do {
  487. try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
  488. } catch { }
  489. NCManageDatabase.shared.deleteVideo(metadata: metadataLive)
  490. NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
  491. NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
  492. }
  493. }
  494. }
  495. return result.error
  496. }
  497. // MARK: - Copy
  498. func copyMetadata(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
  499. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
  500. let error = await copyMetadataPlain(metadataLive, serverUrlTo: serverUrlTo, overwrite: overwrite)
  501. if error == .success {
  502. return await copyMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
  503. } else {
  504. return error
  505. }
  506. }
  507. return await copyMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
  508. }
  509. private func copyMetadataPlain(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
  510. let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCPermissions().permissionCanRename)
  511. if !metadata.permissions.isEmpty && !permission {
  512. return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")
  513. }
  514. let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName
  515. let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName
  516. let result = await NCNetworking.shared.copyFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: overwrite, account: metadata.account)
  517. return result.error
  518. }
  519. // MARK: - Favorite
  520. func favoriteMetadata(_ metadata: tableMetadata,
  521. completion: @escaping (_ error: NKError) -> Void) {
  522. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
  523. favoriteMetadataPlain(metadataLive) { error in
  524. if error == .success {
  525. self.favoriteMetadataPlain(metadata, completion: completion)
  526. } else {
  527. completion(error)
  528. }
  529. }
  530. } else {
  531. favoriteMetadataPlain(metadata, completion: completion)
  532. }
  533. }
  534. private func favoriteMetadataPlain(_ metadata: tableMetadata,
  535. completion: @escaping (_ error: NKError) -> Void) {
  536. let fileName = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId)
  537. let favorite = !metadata.favorite
  538. let ocId = metadata.ocId
  539. NextcloudKit.shared.setFavorite(fileName: fileName, favorite: favorite, account: metadata.account) { account, error in
  540. if error == .success && metadata.account == account {
  541. metadata.favorite = favorite
  542. NCManageDatabase.shared.addMetadata(metadata)
  543. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterFavoriteFile, userInfo: ["ocId": ocId, "serverUrl": metadata.serverUrl])
  544. }
  545. completion(error)
  546. }
  547. }
  548. // MARK: - Lock Files
  549. func lockUnlockFile(_ metadata: tableMetadata, shoulLock: Bool) {
  550. NextcloudKit.shared.lockUnlockFile(serverUrlFileName: metadata.serverUrl + "/" + metadata.fileName, shouldLock: shoulLock, account: metadata.account) { _, error in
  551. // 0: lock was successful; 412: lock did not change, no error, refresh
  552. guard error == .success || error.errorCode == NCGlobal.shared.errorPreconditionFailed else {
  553. let error = NKError(errorCode: error.errorCode, errorDescription: "_files_lock_error_")
  554. NCContentPresenter().messageNotification(metadata.fileName, error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
  555. return
  556. }
  557. NCNetworking.shared.readFile(serverUrlFileName: metadata.serverUrl + "/" + metadata.fileName, account: metadata.account) { _, metadata, error in
  558. guard error == .success, let metadata = metadata else { return }
  559. NCManageDatabase.shared.addMetadata(metadata)
  560. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
  561. }
  562. }
  563. }
  564. // MARK: - Direct Download
  565. func getVideoUrl(metadata: tableMetadata,
  566. completition: @escaping (_ url: URL?, _ autoplay: Bool, _ error: NKError) -> Void) {
  567. if !metadata.url.isEmpty {
  568. if metadata.url.hasPrefix("/") {
  569. completition(URL(fileURLWithPath: metadata.url), true, .success)
  570. } else {
  571. completition(URL(string: metadata.url), true, .success)
  572. }
  573. } else if utilityFileSystem.fileProviderStorageExists(metadata) {
  574. completition(URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)), false, .success)
  575. } else {
  576. NextcloudKit.shared.getDirectDownload(fileId: metadata.fileId, account: metadata.account) { _, url, _, error in
  577. if error == .success && url != nil {
  578. if let url = URL(string: url!) {
  579. completition(url, false, error)
  580. } else {
  581. completition(nil, false, error)
  582. }
  583. } else {
  584. completition(nil, false, error)
  585. }
  586. }
  587. }
  588. }
  589. // MARK: - Search
  590. /// WebDAV search
  591. func searchFiles(urlBase: NCUserBaseUrl,
  592. literal: String,
  593. account: String,
  594. taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
  595. completion: @escaping (_ metadatas: [tableMetadata]?, _ error: NKError) -> Void) {
  596. NextcloudKit.shared.searchLiteral(serverUrl: urlBase.urlBase,
  597. depth: "infinity",
  598. literal: literal,
  599. showHiddenFiles: NCKeychain().showHiddenFiles,
  600. account: account,
  601. options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { task in
  602. taskHandler(task)
  603. } completion: { _, files, _, error in
  604. guard error == .success else { return completion(nil, error) }
  605. NCManageDatabase.shared.convertFilesToMetadatas(files, useFirstAsMetadataFolder: false) { _, metadatas in
  606. NCManageDatabase.shared.addMetadatas(metadatas)
  607. completion(metadatas, error)
  608. }
  609. }
  610. }
  611. /// Unified Search (NC>=20)
  612. ///
  613. func unifiedSearchFiles(userBaseUrl: NCUserBaseUrl,
  614. literal: String,
  615. taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
  616. providers: @escaping (_ accout: String, _ searchProviders: [NKSearchProvider]?) -> Void,
  617. update: @escaping (_ account: String, _ id: String, NKSearchResult?, [tableMetadata]?) -> Void,
  618. completion: @escaping (_ account: String, _ error: NKError) -> Void) {
  619. let dispatchGroup = DispatchGroup()
  620. dispatchGroup.enter()
  621. dispatchGroup.notify(queue: .main) {
  622. completion(userBaseUrl.account, NKError())
  623. }
  624. NextcloudKit.shared.unifiedSearch(term: literal, timeout: 30, timeoutProvider: 90, account: userBaseUrl.account) { _ in
  625. // example filter
  626. // ["calendar", "files", "fulltextsearch"].contains(provider.id)
  627. return true
  628. } request: { request in
  629. if let request = request {
  630. self.requestsUnifiedSearch.append(request)
  631. }
  632. } taskHandler: { task in
  633. taskHandler(task)
  634. } providers: { account, searchProviders in
  635. providers(account, searchProviders)
  636. } update: { account, partialResult, provider, _ in
  637. guard let partialResult = partialResult else { return }
  638. var metadatas: [tableMetadata] = []
  639. switch provider.id {
  640. case "files":
  641. partialResult.entries.forEach({ entry in
  642. if let fileId = entry.fileId,
  643. let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && fileId == %@", userBaseUrl.userAccount, String(fileId))) {
  644. metadatas.append(metadata)
  645. } else if let filePath = entry.filePath {
  646. let semaphore = DispatchSemaphore(value: 0)
  647. self.loadMetadata(userBaseUrl: userBaseUrl, filePath: filePath, dispatchGroup: dispatchGroup) { _, metadata, _ in
  648. metadatas.append(metadata)
  649. semaphore.signal()
  650. }
  651. semaphore.wait()
  652. } else { print(#function, "[ERROR]: File search entry has no path: \(entry)") }
  653. })
  654. case "fulltextsearch":
  655. // NOTE: FTS could also return attributes like files
  656. // https://github.com/nextcloud/files_fulltextsearch/issues/143
  657. partialResult.entries.forEach({ entry in
  658. let url = URLComponents(string: entry.resourceURL)
  659. guard let dir = url?.queryItems?["dir"]?.value, let filename = url?.queryItems?["scrollto"]?.value else { return }
  660. if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(
  661. format: "account == %@ && path == %@ && fileName == %@",
  662. userBaseUrl.userAccount,
  663. "/remote.php/dav/files/" + userBaseUrl.user + dir,
  664. filename)) {
  665. metadatas.append(metadata)
  666. } else {
  667. let semaphore = DispatchSemaphore(value: 0)
  668. self.loadMetadata(userBaseUrl: userBaseUrl, filePath: dir + filename, dispatchGroup: dispatchGroup) { _, metadata, _ in
  669. metadatas.append(metadata)
  670. semaphore.signal()
  671. }
  672. semaphore.wait()
  673. }
  674. })
  675. default:
  676. partialResult.entries.forEach({ entry in
  677. let metadata = NCManageDatabase.shared.createMetadata(account: userBaseUrl.account, user: userBaseUrl.user, userId: userBaseUrl.userId, fileName: entry.title, fileNameView: entry.title, ocId: NSUUID().uuidString, serverUrl: userBaseUrl.urlBase, urlBase: userBaseUrl.urlBase, url: entry.resourceURL, contentType: "", isUrl: true, name: partialResult.id, subline: entry.subline, iconName: entry.icon, iconUrl: entry.thumbnailURL)
  678. metadatas.append(metadata)
  679. })
  680. }
  681. update(account, provider.id, partialResult, metadatas)
  682. } completion: { _, _, _ in
  683. self.requestsUnifiedSearch.removeAll()
  684. dispatchGroup.leave()
  685. }
  686. }
  687. func unifiedSearchFilesProvider(userBaseUrl: NCUserBaseUrl,
  688. id: String, term: String,
  689. limit: Int, cursor: Int,
  690. taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
  691. completion: @escaping (_ account: String, _ searchResult: NKSearchResult?, _ metadatas: [tableMetadata]?, _ error: NKError) -> Void) {
  692. var metadatas: [tableMetadata] = []
  693. let request = NextcloudKit.shared.searchProvider(id, term: term, limit: limit, cursor: cursor, timeout: 60, account: userBaseUrl.account) { task in
  694. taskHandler(task)
  695. } completion: { account, searchResult, _, error in
  696. guard let searchResult = searchResult else {
  697. completion(account, nil, metadatas, error)
  698. return
  699. }
  700. switch id {
  701. case "files":
  702. searchResult.entries.forEach({ entry in
  703. if let fileId = entry.fileId, let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && fileId == %@", userBaseUrl.userAccount, String(fileId))) {
  704. metadatas.append(metadata)
  705. } else if let filePath = entry.filePath {
  706. let semaphore = DispatchSemaphore(value: 0)
  707. self.loadMetadata(userBaseUrl: userBaseUrl, filePath: filePath, dispatchGroup: nil) { _, metadata, _ in
  708. metadatas.append(metadata)
  709. semaphore.signal()
  710. }
  711. semaphore.wait()
  712. } else { print(#function, "[ERROR]: File search entry has no path: \(entry)") }
  713. })
  714. case "fulltextsearch":
  715. // NOTE: FTS could also return attributes like files
  716. // https://github.com/nextcloud/files_fulltextsearch/issues/143
  717. searchResult.entries.forEach({ entry in
  718. let url = URLComponents(string: entry.resourceURL)
  719. guard let dir = url?.queryItems?["dir"]?.value, let filename = url?.queryItems?["scrollto"]?.value else { return }
  720. if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && path == %@ && fileName == %@", userBaseUrl.userAccount, "/remote.php/dav/files/" + userBaseUrl.user + dir, filename)) {
  721. metadatas.append(metadata)
  722. } else {
  723. let semaphore = DispatchSemaphore(value: 0)
  724. self.loadMetadata(userBaseUrl: userBaseUrl, filePath: dir + filename, dispatchGroup: nil) { _, metadata, _ in
  725. metadatas.append(metadata)
  726. semaphore.signal()
  727. }
  728. semaphore.wait()
  729. }
  730. })
  731. default:
  732. searchResult.entries.forEach({ entry in
  733. let newMetadata = NCManageDatabase.shared.createMetadata(account: userBaseUrl.account, user: userBaseUrl.user, userId: userBaseUrl.userId, fileName: entry.title, fileNameView: entry.title, ocId: NSUUID().uuidString, serverUrl: userBaseUrl.urlBase, urlBase: userBaseUrl.urlBase, url: entry.resourceURL, contentType: "", isUrl: true, name: searchResult.name.lowercased(), subline: entry.subline, iconName: entry.icon, iconUrl: entry.thumbnailURL)
  734. metadatas.append(newMetadata)
  735. })
  736. }
  737. completion(account, searchResult, metadatas, error)
  738. }
  739. if let request = request {
  740. requestsUnifiedSearch.append(request)
  741. }
  742. }
  743. func cancelUnifiedSearchFiles() {
  744. for request in requestsUnifiedSearch {
  745. request.cancel()
  746. }
  747. requestsUnifiedSearch.removeAll()
  748. }
  749. private func loadMetadata(userBaseUrl: NCUserBaseUrl,
  750. filePath: String,
  751. dispatchGroup: DispatchGroup? = nil,
  752. completion: @escaping (String, tableMetadata, NKError) -> Void) {
  753. let urlPath = userBaseUrl.urlBase + "/remote.php/dav/files/" + userBaseUrl.user + filePath
  754. dispatchGroup?.enter()
  755. self.readFile(serverUrlFileName: urlPath, account: userBaseUrl.account) { account, metadata, error in
  756. defer { dispatchGroup?.leave() }
  757. guard let metadata = metadata else { return }
  758. let returnMetadata = tableMetadata.init(value: metadata)
  759. NCManageDatabase.shared.addMetadata(metadata)
  760. completion(account, returnMetadata, error)
  761. }
  762. }
  763. func cancelDataTask() {
  764. let sessionManager = NextcloudKit.shared.sessionManager
  765. sessionManager.session.getTasksWithCompletionHandler { dataTasks, _, _ in
  766. dataTasks.forEach {
  767. $0.cancel()
  768. }
  769. }
  770. }
  771. }
  772. class NCOperationDownloadAvatar: ConcurrentOperation {
  773. var user: String
  774. var fileName: String
  775. var etag: String?
  776. var fileNameLocalPath: String
  777. var cell: NCCellProtocol!
  778. var view: UIView?
  779. var account: String
  780. init(user: String, fileName: String, fileNameLocalPath: String, account: String, cell: NCCellProtocol, view: UIView?) {
  781. self.user = user
  782. self.fileName = fileName
  783. self.fileNameLocalPath = fileNameLocalPath
  784. self.account = account
  785. self.cell = cell
  786. self.view = view
  787. self.etag = NCManageDatabase.shared.getTableAvatar(fileName: fileName)?.etag
  788. }
  789. override func start() {
  790. guard !isCancelled else { return self.finish() }
  791. NextcloudKit.shared.downloadAvatar(user: user,
  792. fileNameLocalPath: fileNameLocalPath,
  793. sizeImage: NCGlobal.shared.avatarSize,
  794. avatarSizeRounded: NCGlobal.shared.avatarSizeRounded,
  795. etag: self.etag,
  796. account: account,
  797. options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imageAvatar, _, etag, error in
  798. if error == .success, let imageAvatar {
  799. NCManageDatabase.shared.addAvatar(fileName: self.fileName, etag: etag ?? "")
  800. DispatchQueue.main.async {
  801. if self.user == self.cell.fileUser, let cellFileAvatarImageView = self.cell.fileAvatarImageView {
  802. cellFileAvatarImageView.contentMode = .scaleAspectFill
  803. UIView.transition(with: cellFileAvatarImageView,
  804. duration: 0.75,
  805. options: .transitionCrossDissolve,
  806. animations: { cellFileAvatarImageView.image = imageAvatar },
  807. completion: nil)
  808. } else {
  809. if self.view is UICollectionView {
  810. (self.view as? UICollectionView)?.reloadData()
  811. } else if self.view is UITableView {
  812. (self.view as? UITableView)?.reloadData()
  813. }
  814. }
  815. }
  816. } else if error.errorCode == NCGlobal.shared.errorNotModified {
  817. NCManageDatabase.shared.setAvatarLoaded(fileName: self.fileName)
  818. }
  819. self.finish()
  820. }
  821. }
  822. }