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. viewController: UIViewController?,
  375. completion: @escaping (_ error: NKError) -> Void) {
  376. let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata)
  377. let fileNameNew = fileNameNew.trimmingCharacters(in: .whitespacesAndNewlines)
  378. let fileNameNewLive = (fileNameNew as NSString).deletingPathExtension + ".mov"
  379. if metadata.isDirectoryE2EE {
  380. #if !EXTENSION
  381. Task {
  382. if let metadataLive = metadataLive {
  383. let error = await NCNetworkingE2EERename().rename(metadata: metadataLive, fileNameNew: fileNameNew, indexPath: indexPath)
  384. if error == .success {
  385. let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew, indexPath: indexPath)
  386. DispatchQueue.main.async { completion(error) }
  387. } else {
  388. DispatchQueue.main.async { completion(error) }
  389. }
  390. } else {
  391. let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew, indexPath: indexPath)
  392. DispatchQueue.main.async { completion(error) }
  393. }
  394. }
  395. #endif
  396. } else {
  397. if let metadataLive, metadata.isNotFlaggedAsLivePhotoByServer {
  398. renameMetadataPlain(metadataLive, fileNameNew: fileNameNewLive, indexPath: indexPath) { error in
  399. if error == .success {
  400. self.renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
  401. } else {
  402. completion(error)
  403. }
  404. }
  405. } else {
  406. renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
  407. }
  408. }
  409. }
  410. private func renameMetadataPlain(_ metadata: tableMetadata,
  411. fileNameNew: String,
  412. indexPath: IndexPath,
  413. completion: @escaping (_ error: NKError) -> Void) {
  414. let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCPermissions().permissionCanRename)
  415. if !metadata.permissions.isEmpty && !permission {
  416. return completion(NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_"))
  417. }
  418. let fileName = utility.removeForbiddenCharacters(fileNameNew)
  419. if fileName != fileNameNew {
  420. let errorDescription = String(format: NSLocalizedString("_forbidden_characters_", comment: ""), NCGlobal.shared.forbiddenCharacters.joined(separator: " "))
  421. let error = NKError(errorCode: NCGlobal.shared.errorConflict, errorDescription: errorDescription)
  422. return completion(error)
  423. }
  424. let fileNameNew = fileName
  425. if fileNameNew.isEmpty || fileNameNew == metadata.fileNameView {
  426. return completion(NKError())
  427. }
  428. let fileNamePath = metadata.serverUrl + "/" + metadata.fileName
  429. let fileNameToPath = metadata.serverUrl + "/" + fileNameNew
  430. NextcloudKit.shared.moveFileOrFolder(serverUrlFileNameSource: fileNamePath, serverUrlFileNameDestination: fileNameToPath, overwrite: false, account: metadata.account) { _, error in
  431. if error == .success {
  432. NCManageDatabase.shared.renameMetadata(fileNameTo: fileNameNew, ocId: metadata.ocId)
  433. if metadata.directory {
  434. let serverUrl = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName)
  435. let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: fileNameNew)
  436. if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) {
  437. NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, serverUrlTo: serverUrlTo, etag: "", encrypted: directory.e2eEncrypted, account: metadata.account)
  438. }
  439. } else {
  440. if (metadata.fileName as NSString).pathExtension != (fileNameNew as NSString).pathExtension {
  441. let path = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)
  442. self.utilityFileSystem.removeFile(atPath: path)
  443. } else {
  444. NCManageDatabase.shared.setLocalFile(ocId: metadata.ocId, fileName: fileNameNew)
  445. let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + metadata.fileName
  446. let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + fileNameNew
  447. self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath)
  448. }
  449. }
  450. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRenameFile, userInfo: ["ocId": metadata.ocId, "account": metadata.account, "indexPath": indexPath])
  451. }
  452. completion(error)
  453. }
  454. }
  455. // MARK: - Move
  456. func moveMetadata(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
  457. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
  458. let error = await moveMetadataPlain(metadataLive, serverUrlTo: serverUrlTo, overwrite: overwrite)
  459. if error == .success {
  460. return await moveMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
  461. } else {
  462. return error
  463. }
  464. }
  465. return await moveMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
  466. }
  467. private func moveMetadataPlain(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
  468. let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCPermissions().permissionCanRename)
  469. if !metadata.permissions.isEmpty && !permission {
  470. return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")
  471. }
  472. let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName
  473. let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName
  474. let result = await NCNetworking.shared.moveFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: overwrite, account: metadata.account)
  475. if result.error == .success {
  476. if metadata.directory {
  477. NCManageDatabase.shared.deleteDirectoryAndSubDirectory(serverUrl: utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName), account: result.account)
  478. } else {
  479. do {
  480. try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
  481. } catch { }
  482. NCManageDatabase.shared.deleteVideo(metadata: metadata)
  483. NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
  484. NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
  485. // LIVE PHOTO SERVER
  486. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadataLive.isFlaggedAsLivePhotoByServer {
  487. do {
  488. try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
  489. } catch { }
  490. NCManageDatabase.shared.deleteVideo(metadata: metadataLive)
  491. NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
  492. NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
  493. }
  494. }
  495. }
  496. return result.error
  497. }
  498. // MARK: - Copy
  499. func copyMetadata(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
  500. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
  501. let error = await copyMetadataPlain(metadataLive, serverUrlTo: serverUrlTo, overwrite: overwrite)
  502. if error == .success {
  503. return await copyMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
  504. } else {
  505. return error
  506. }
  507. }
  508. return await copyMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
  509. }
  510. private func copyMetadataPlain(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
  511. let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCPermissions().permissionCanRename)
  512. if !metadata.permissions.isEmpty && !permission {
  513. return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")
  514. }
  515. let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName
  516. let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName
  517. let result = await NCNetworking.shared.copyFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: overwrite, account: metadata.account)
  518. return result.error
  519. }
  520. // MARK: - Favorite
  521. func favoriteMetadata(_ metadata: tableMetadata,
  522. completion: @escaping (_ error: NKError) -> Void) {
  523. if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
  524. favoriteMetadataPlain(metadataLive) { error in
  525. if error == .success {
  526. self.favoriteMetadataPlain(metadata, completion: completion)
  527. } else {
  528. completion(error)
  529. }
  530. }
  531. } else {
  532. favoriteMetadataPlain(metadata, completion: completion)
  533. }
  534. }
  535. private func favoriteMetadataPlain(_ metadata: tableMetadata,
  536. completion: @escaping (_ error: NKError) -> Void) {
  537. let fileName = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId)
  538. let favorite = !metadata.favorite
  539. let ocId = metadata.ocId
  540. NextcloudKit.shared.setFavorite(fileName: fileName, favorite: favorite, account: metadata.account) { account, error in
  541. if error == .success && metadata.account == account {
  542. metadata.favorite = favorite
  543. NCManageDatabase.shared.addMetadata(metadata)
  544. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterFavoriteFile, userInfo: ["ocId": ocId, "serverUrl": metadata.serverUrl])
  545. }
  546. completion(error)
  547. }
  548. }
  549. // MARK: - Lock Files
  550. func lockUnlockFile(_ metadata: tableMetadata, shoulLock: Bool) {
  551. NextcloudKit.shared.lockUnlockFile(serverUrlFileName: metadata.serverUrl + "/" + metadata.fileName, shouldLock: shoulLock, account: metadata.account) { _, error in
  552. // 0: lock was successful; 412: lock did not change, no error, refresh
  553. guard error == .success || error.errorCode == NCGlobal.shared.errorPreconditionFailed else {
  554. let error = NKError(errorCode: error.errorCode, errorDescription: "_files_lock_error_")
  555. NCContentPresenter().messageNotification(metadata.fileName, error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
  556. return
  557. }
  558. NCNetworking.shared.readFile(serverUrlFileName: metadata.serverUrl + "/" + metadata.fileName, account: metadata.account) { _, metadata, error in
  559. guard error == .success, let metadata = metadata else { return }
  560. NCManageDatabase.shared.addMetadata(metadata)
  561. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
  562. }
  563. }
  564. }
  565. // MARK: - Direct Download
  566. func getVideoUrl(metadata: tableMetadata,
  567. completition: @escaping (_ url: URL?, _ autoplay: Bool, _ error: NKError) -> Void) {
  568. if !metadata.url.isEmpty {
  569. if metadata.url.hasPrefix("/") {
  570. completition(URL(fileURLWithPath: metadata.url), true, .success)
  571. } else {
  572. completition(URL(string: metadata.url), true, .success)
  573. }
  574. } else if utilityFileSystem.fileProviderStorageExists(metadata) {
  575. completition(URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)), false, .success)
  576. } else {
  577. NextcloudKit.shared.getDirectDownload(fileId: metadata.fileId, account: metadata.account) { _, url, _, error in
  578. if error == .success && url != nil {
  579. if let url = URL(string: url!) {
  580. completition(url, false, error)
  581. } else {
  582. completition(nil, false, error)
  583. }
  584. } else {
  585. completition(nil, false, error)
  586. }
  587. }
  588. }
  589. }
  590. // MARK: - Search
  591. /// WebDAV search
  592. func searchFiles(urlBase: NCUserBaseUrl,
  593. literal: String,
  594. account: String,
  595. taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
  596. completion: @escaping (_ metadatas: [tableMetadata]?, _ error: NKError) -> Void) {
  597. NextcloudKit.shared.searchLiteral(serverUrl: urlBase.urlBase,
  598. depth: "infinity",
  599. literal: literal,
  600. showHiddenFiles: NCKeychain().showHiddenFiles,
  601. account: account,
  602. options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { task in
  603. taskHandler(task)
  604. } completion: { _, files, _, error in
  605. guard error == .success else { return completion(nil, error) }
  606. NCManageDatabase.shared.convertFilesToMetadatas(files, useFirstAsMetadataFolder: false) { _, metadatas in
  607. NCManageDatabase.shared.addMetadatas(metadatas)
  608. completion(metadatas, error)
  609. }
  610. }
  611. }
  612. /// Unified Search (NC>=20)
  613. ///
  614. func unifiedSearchFiles(userBaseUrl: NCUserBaseUrl,
  615. literal: String,
  616. taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
  617. providers: @escaping (_ accout: String, _ searchProviders: [NKSearchProvider]?) -> Void,
  618. update: @escaping (_ account: String, _ id: String, NKSearchResult?, [tableMetadata]?) -> Void,
  619. completion: @escaping (_ account: String, _ error: NKError) -> Void) {
  620. let dispatchGroup = DispatchGroup()
  621. dispatchGroup.enter()
  622. dispatchGroup.notify(queue: .main) {
  623. completion(userBaseUrl.account, NKError())
  624. }
  625. NextcloudKit.shared.unifiedSearch(term: literal, timeout: 30, timeoutProvider: 90, account: userBaseUrl.account) { _ in
  626. // example filter
  627. // ["calendar", "files", "fulltextsearch"].contains(provider.id)
  628. return true
  629. } request: { request in
  630. if let request = request {
  631. self.requestsUnifiedSearch.append(request)
  632. }
  633. } taskHandler: { task in
  634. taskHandler(task)
  635. } providers: { account, searchProviders in
  636. providers(account, searchProviders)
  637. } update: { account, partialResult, provider, _ in
  638. guard let partialResult = partialResult else { return }
  639. var metadatas: [tableMetadata] = []
  640. switch provider.id {
  641. case "files":
  642. partialResult.entries.forEach({ entry in
  643. if let fileId = entry.fileId,
  644. let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && fileId == %@", userBaseUrl.userAccount, String(fileId))) {
  645. metadatas.append(metadata)
  646. } else if let filePath = entry.filePath {
  647. let semaphore = DispatchSemaphore(value: 0)
  648. self.loadMetadata(userBaseUrl: userBaseUrl, filePath: filePath, dispatchGroup: dispatchGroup) { _, metadata, _ in
  649. metadatas.append(metadata)
  650. semaphore.signal()
  651. }
  652. semaphore.wait()
  653. } else { print(#function, "[ERROR]: File search entry has no path: \(entry)") }
  654. })
  655. case "fulltextsearch":
  656. // NOTE: FTS could also return attributes like files
  657. // https://github.com/nextcloud/files_fulltextsearch/issues/143
  658. partialResult.entries.forEach({ entry in
  659. let url = URLComponents(string: entry.resourceURL)
  660. guard let dir = url?.queryItems?["dir"]?.value, let filename = url?.queryItems?["scrollto"]?.value else { return }
  661. if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(
  662. format: "account == %@ && path == %@ && fileName == %@",
  663. userBaseUrl.userAccount,
  664. "/remote.php/dav/files/" + userBaseUrl.user + dir,
  665. filename)) {
  666. metadatas.append(metadata)
  667. } else {
  668. let semaphore = DispatchSemaphore(value: 0)
  669. self.loadMetadata(userBaseUrl: userBaseUrl, filePath: dir + filename, dispatchGroup: dispatchGroup) { _, metadata, _ in
  670. metadatas.append(metadata)
  671. semaphore.signal()
  672. }
  673. semaphore.wait()
  674. }
  675. })
  676. default:
  677. partialResult.entries.forEach({ entry in
  678. 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)
  679. metadatas.append(metadata)
  680. })
  681. }
  682. update(account, provider.id, partialResult, metadatas)
  683. } completion: { _, _, _ in
  684. self.requestsUnifiedSearch.removeAll()
  685. dispatchGroup.leave()
  686. }
  687. }
  688. func unifiedSearchFilesProvider(userBaseUrl: NCUserBaseUrl,
  689. id: String, term: String,
  690. limit: Int, cursor: Int,
  691. taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
  692. completion: @escaping (_ account: String, _ searchResult: NKSearchResult?, _ metadatas: [tableMetadata]?, _ error: NKError) -> Void) {
  693. var metadatas: [tableMetadata] = []
  694. let request = NextcloudKit.shared.searchProvider(id, term: term, limit: limit, cursor: cursor, timeout: 60, account: userBaseUrl.account) { task in
  695. taskHandler(task)
  696. } completion: { account, searchResult, _, error in
  697. guard let searchResult = searchResult else {
  698. completion(account, nil, metadatas, error)
  699. return
  700. }
  701. switch id {
  702. case "files":
  703. searchResult.entries.forEach({ entry in
  704. if let fileId = entry.fileId, let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && fileId == %@", userBaseUrl.userAccount, String(fileId))) {
  705. metadatas.append(metadata)
  706. } else if let filePath = entry.filePath {
  707. let semaphore = DispatchSemaphore(value: 0)
  708. self.loadMetadata(userBaseUrl: userBaseUrl, filePath: filePath, dispatchGroup: nil) { _, metadata, _ in
  709. metadatas.append(metadata)
  710. semaphore.signal()
  711. }
  712. semaphore.wait()
  713. } else { print(#function, "[ERROR]: File search entry has no path: \(entry)") }
  714. })
  715. case "fulltextsearch":
  716. // NOTE: FTS could also return attributes like files
  717. // https://github.com/nextcloud/files_fulltextsearch/issues/143
  718. searchResult.entries.forEach({ entry in
  719. let url = URLComponents(string: entry.resourceURL)
  720. guard let dir = url?.queryItems?["dir"]?.value, let filename = url?.queryItems?["scrollto"]?.value else { return }
  721. if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && path == %@ && fileName == %@", userBaseUrl.userAccount, "/remote.php/dav/files/" + userBaseUrl.user + dir, filename)) {
  722. metadatas.append(metadata)
  723. } else {
  724. let semaphore = DispatchSemaphore(value: 0)
  725. self.loadMetadata(userBaseUrl: userBaseUrl, filePath: dir + filename, dispatchGroup: nil) { _, metadata, _ in
  726. metadatas.append(metadata)
  727. semaphore.signal()
  728. }
  729. semaphore.wait()
  730. }
  731. })
  732. default:
  733. searchResult.entries.forEach({ entry in
  734. 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)
  735. metadatas.append(newMetadata)
  736. })
  737. }
  738. completion(account, searchResult, metadatas, error)
  739. }
  740. if let request = request {
  741. requestsUnifiedSearch.append(request)
  742. }
  743. }
  744. func cancelUnifiedSearchFiles() {
  745. for request in requestsUnifiedSearch {
  746. request.cancel()
  747. }
  748. requestsUnifiedSearch.removeAll()
  749. }
  750. private func loadMetadata(userBaseUrl: NCUserBaseUrl,
  751. filePath: String,
  752. dispatchGroup: DispatchGroup? = nil,
  753. completion: @escaping (String, tableMetadata, NKError) -> Void) {
  754. let urlPath = userBaseUrl.urlBase + "/remote.php/dav/files/" + userBaseUrl.user + filePath
  755. dispatchGroup?.enter()
  756. self.readFile(serverUrlFileName: urlPath, account: userBaseUrl.account) { account, metadata, error in
  757. defer { dispatchGroup?.leave() }
  758. guard let metadata = metadata else { return }
  759. let returnMetadata = tableMetadata.init(value: metadata)
  760. NCManageDatabase.shared.addMetadata(metadata)
  761. completion(account, returnMetadata, error)
  762. }
  763. }
  764. func cancelDataTask() {
  765. let sessionManager = NextcloudKit.shared.sessionManager
  766. sessionManager.session.getTasksWithCompletionHandler { dataTasks, _, _ in
  767. dataTasks.forEach {
  768. $0.cancel()
  769. }
  770. }
  771. }
  772. }
  773. class NCOperationDownloadAvatar: ConcurrentOperation {
  774. var user: String
  775. var fileName: String
  776. var etag: String?
  777. var fileNameLocalPath: String
  778. var cell: NCCellProtocol!
  779. var view: UIView?
  780. var account: String
  781. init(user: String, fileName: String, fileNameLocalPath: String, account: String, cell: NCCellProtocol, view: UIView?) {
  782. self.user = user
  783. self.fileName = fileName
  784. self.fileNameLocalPath = fileNameLocalPath
  785. self.account = account
  786. self.cell = cell
  787. self.view = view
  788. self.etag = NCManageDatabase.shared.getTableAvatar(fileName: fileName)?.etag
  789. }
  790. override func start() {
  791. guard !isCancelled else { return self.finish() }
  792. NextcloudKit.shared.downloadAvatar(user: user,
  793. fileNameLocalPath: fileNameLocalPath,
  794. sizeImage: NCGlobal.shared.avatarSize,
  795. avatarSizeRounded: NCGlobal.shared.avatarSizeRounded,
  796. etag: self.etag,
  797. account: account,
  798. options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imageAvatar, _, etag, error in
  799. if error == .success, let imageAvatar {
  800. NCManageDatabase.shared.addAvatar(fileName: self.fileName, etag: etag ?? "")
  801. DispatchQueue.main.async {
  802. if self.user == self.cell.fileUser, let cellFileAvatarImageView = self.cell.fileAvatarImageView {
  803. cellFileAvatarImageView.contentMode = .scaleAspectFill
  804. UIView.transition(with: cellFileAvatarImageView,
  805. duration: 0.75,
  806. options: .transitionCrossDissolve,
  807. animations: { cellFileAvatarImageView.image = imageAvatar },
  808. completion: nil)
  809. } else {
  810. if self.view is UICollectionView {
  811. (self.view as? UICollectionView)?.reloadData()
  812. } else if self.view is UITableView {
  813. (self.view as? UITableView)?.reloadData()
  814. }
  815. }
  816. }
  817. } else if error.errorCode == NCGlobal.shared.errorNotModified {
  818. NCManageDatabase.shared.setAvatarLoaded(fileName: self.fileName)
  819. }
  820. self.finish()
  821. }
  822. }
  823. }