diff --git a/RemoteCloud/RemoteCloud.xcodeproj/project.pbxproj b/RemoteCloud/RemoteCloud.xcodeproj/project.pbxproj index 9d7cfc2..5bc400f 100644 --- a/RemoteCloud/RemoteCloud.xcodeproj/project.pbxproj +++ b/RemoteCloud/RemoteCloud.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 3FA46CE223849615007ACC82 /* FileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA46CE123849615007ACC82 /* FileCache.swift */; }; 3FA46CE5238496B7007ACC82 /* cache.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 3FA46CE3238496B7007ACC82 /* cache.xcdatamodeld */; }; 3FAF14D32387131F0052AFAB /* WebDAVStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAF14D22387131F0052AFAB /* WebDAVStorage.swift */; }; + 3FE215882394F39300CD2A19 /* FilesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE215872394F39300CD2A19 /* FilesStorage.swift */; }; 3FE23B1F2340B96200916DA6 /* UploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE23B1E2340B96200916DA6 /* UploadManager.swift */; }; /* End PBXBuildFile section */ @@ -72,6 +73,7 @@ 3FA46CE123849615007ACC82 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = ""; }; 3FA46CE4238496B7007ACC82 /* cache.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = cache.xcdatamodel; sourceTree = ""; }; 3FAF14D22387131F0052AFAB /* WebDAVStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebDAVStorage.swift; sourceTree = ""; }; + 3FE215872394F39300CD2A19 /* FilesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesStorage.swift; sourceTree = ""; }; 3FE23B1E2340B96200916DA6 /* UploadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -164,6 +166,7 @@ 3C400921224190920028A45A /* OneDriveStorage.swift */, 3F3ABABC2273119D002A8D16 /* pCloudStorage.swift */, 3FAF14D22387131F0052AFAB /* WebDAVStorage.swift */, + 3FE215872394F39300CD2A19 /* FilesStorage.swift */, ); path = Storages; sourceTree = ""; @@ -288,6 +291,7 @@ 3FA46CE223849615007ACC82 /* FileCache.swift in Sources */, 3CA2C951223495E700481DB5 /* RemoteStorage.swift in Sources */, 3C14C452225CE19E0044E4A7 /* CueSheet.swift in Sources */, + 3FE215882394F39300CD2A19 /* FilesStorage.swift in Sources */, 3C0654A122384BED003BD932 /* CryptCarotDAV.swift in Sources */, 3C40091E22413C340028A45A /* DropBoxStorage.swift in Sources */, 3F3ABABD2273119D002A8D16 /* pCloudStorage.swift in Sources */, diff --git a/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/Contents.json b/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/Contents.json new file mode 100644 index 0000000..52961a8 --- /dev/null +++ b/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "files3.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "files2.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "files1.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/files1.png b/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/files1.png new file mode 100644 index 0000000..ea3a826 Binary files /dev/null and b/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/files1.png differ diff --git a/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/files2.png b/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/files2.png new file mode 100644 index 0000000..42f8846 Binary files /dev/null and b/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/files2.png differ diff --git a/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/files3.png b/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/files3.png new file mode 100644 index 0000000..1ac0e76 Binary files /dev/null and b/RemoteCloud/RemoteCloud/Assets.xcassets/files.imageset/files3.png differ diff --git a/RemoteCloud/RemoteCloud/RemoteStorage.swift b/RemoteCloud/RemoteCloud/RemoteStorage.swift index ea701cb..141e5a9 100644 --- a/RemoteCloud/RemoteCloud/RemoteStorage.swift +++ b/RemoteCloud/RemoteCloud/RemoteStorage.swift @@ -12,6 +12,7 @@ import CoreData public enum CloudStorages: CaseIterable { case Local + case Files case DropBox case GoogleDrive case OneDrive @@ -258,6 +259,8 @@ public class CloudFactory { return UIImage(named: "cryptomator", in: Bundle(for: type(of: self)), compatibleWith: nil) case .Local: return UIImage(named: "local", in: Bundle(for: type(of: self)), compatibleWith: nil) + case .Files: + return UIImage(named: "files", in: Bundle(for: type(of: self)), compatibleWith: nil) } } @@ -281,6 +284,8 @@ public class CloudFactory { return "Cryptomator" case .Local: return "Local" + case .Files: + return "Files" } } @@ -306,6 +311,8 @@ public class CloudFactory { return Cryptomator(name: tagname) case .Local: return LocalStorage(name: tagname) + case .Files: + return FilesStorage(name: tagname) } } diff --git a/RemoteCloud/RemoteCloud/Storages/FilesStorage.swift b/RemoteCloud/RemoteCloud/Storages/FilesStorage.swift new file mode 100644 index 0000000..881712a --- /dev/null +++ b/RemoteCloud/RemoteCloud/Storages/FilesStorage.swift @@ -0,0 +1,907 @@ +// +// FilesStorage.swift +// RemoteCloud +// +// Created by rei8 on 2019/12/02. +// Copyright © 2019 lithium03. All rights reserved. +// + +import Foundation +import CoreData +import os.log +import UIKit +import CoreServices + +public class FilesStorage: RemoteStorageBase, UIDocumentPickerDelegate { + + public override func getStorageType() -> CloudStorages { + return .Files + } + + public convenience init(name: String) { + self.init() + service = CloudFactory.getServiceName(service: .Files) + storageName = name + } + + var cache_bookmarkData = Data() + var bookmarkData: Data { + if let name = storageName { + if let base64 = getKeyChain(key: "\(name)_bookmarkData"), let bookmark = Data(base64Encoded: base64) { + cache_bookmarkData = bookmark + } + return cache_bookmarkData + } + else { + return Data() + } + } + + var authCallback: ((Bool)->Void)? + + public override func auth(onFinish: ((Bool) -> Void)?) -> Void { + os_log("%{public}@", log: log, type: .debug, "auth(files:\(storageName ?? ""))") + + if authCallback != nil { + onFinish?(false) + } + + DispatchQueue.main.async { + if #available(iOS 13.0, *) { + if let controller = UIApplication.topViewController() { + let documentPicker = + UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String], + in: .open) + + documentPicker.delegate = self + self.authCallback = onFinish + controller.present(documentPicker, animated: true, completion: nil) + } + else { + onFinish?(false) + } + } + else { + if let controller = UIApplication.topViewController() { + let alart = UIAlertController(title: "iOS13 required", message: "folder selection feature needs >= iOS13", preferredStyle: .alert) + let cancel = UIAlertAction(title: "OK", style: .cancel) { action in + onFinish?(false) + } + alart.addAction(cancel) + + controller.present(alart, animated: true, completion: nil) + } + else { + onFinish?(false) + } + } + } + } + + public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let onFinish = authCallback else { + return + } + guard let url = urls.first else { + onFinish(false) + return + } + print(url) + do { + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish(false) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) + + let _ = self.setKeyChain(key: "\(self.storageName ?? "")_bookmarkData", value: bookmarkData.base64EncodedString()) + + DispatchQueue.global().async { + onFinish(true) + } + } + catch let error { + // Handle the error here. + print(error) + onFinish(false) + } + } + + public func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + if let onFinish = authCallback { + onFinish(false) + } + } + + public override func logout() { + if let name = storageName { + let _ = delKeyChain(key: "\(name)_bookmarkData") + } + super.logout() + } + + override func ListChildren(fileId: String = "", path: String = "", onFinish: (() -> Void)?) { + do { + var isStale = false + let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) + + if isStale { + print("url is Stale", url) + // Handle stale data here. + let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) + + let _ = self.setKeyChain(key: "\(self.storageName ?? "")_bookmarkData", value: bookmarkData.base64EncodedString()) + } + + // Use the URL here. + + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?() + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + // Use file coordination for reading and writing any of the URL’s content. + var error: NSError? = nil + NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in + + var targetURL = url + if fileId != "" { + targetURL.appendPathComponent(fileId) + } + + guard let fileURLs = try? FileManager.default.contentsOfDirectory(at: targetURL, includingPropertiesForKeys: nil) else { + onFinish?() + return + } + + let backgroudContext = CloudFactory.shared.data.persistentContainer.newBackgroundContext() + backgroudContext.perform { + let fetchRequest = NSFetchRequest(entityName: "RemoteData") + fetchRequest.predicate = NSPredicate(format: "parent == %@ && storage == %@", fileId, self.storageName ?? "") + if let result = try? backgroudContext.fetch(fetchRequest) { + for object in result { + backgroudContext.delete(object as! NSManagedObject) + } + } + } + + for fileURL in fileURLs { + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?() + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + storeItem(item: fileURL, parentFileId: fileId, parentPath: path, context: backgroudContext) + } + backgroudContext.perform { + try? backgroudContext.save() + DispatchQueue.global().async { + onFinish?() + } + } + } + } + catch let error { + // Handle the error here. + print(error) + onFinish?() + } + } + + func getIdFromURL(url: URL) -> String? { + var isStale = false + guard let baseUrl = try? URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) else { + return nil + } + guard !isStale else { + return nil + } + + let base = baseUrl.pathComponents + let target = url.pathComponents + guard base.count <= target.count else { + return nil + } + for i in 0..(entityName: "RemoteData") + fetchRequest.predicate = NSPredicate(format: "id == %@ && storage == %@", id, self.storageName ?? "") + if let result = try? context.fetch(fetchRequest) { + for object in result { + if let item = object as? RemoteData { + prevPath = item.path + let component = parentPath?.components(separatedBy: "/") + prevPath = component?.dropLast().joined(separator: "/") + prevParent = item.parent + } + context.delete(object as! NSManagedObject) + } + } + + guard let t = attr[.type] as? FileAttributeType else { + return + } + guard t == .typeRegular || t == .typeDirectory else { + return + } + let newitem = RemoteData(context: context) + newitem.storage = self.storageName + newitem.id = id + newitem.name = name + let comp = name.components(separatedBy: ".") + if comp.count >= 1 { + newitem.ext = comp.last!.lowercased() + } + newitem.cdate = attr[.creationDate] as? Date + newitem.mdate = attr[.modificationDate] as? Date + newitem.folder = (attr[.type] as? FileAttributeType) == .typeDirectory + newitem.size = attr[.size] as? NSNumber as? Int64 ?? 0 + newitem.hashstr = "" + newitem.parent = (parentFileId == nil) ? prevParent : parentFileId + if parentFileId == "" { + newitem.path = "\(self.storageName ?? ""):/\(name)" + } + else { + if let path = (parentPath == nil) ? prevPath : parentPath { + newitem.path = "\(path)/\(name)" + } + } + } + } + + override func readFile(fileId: String, start: Int64? = nil, length: Int64? = nil, callCount: Int = 0, onFinish: ((Data?) -> Void)?) { + + if let cache = CloudFactory.shared.cache.getCache(storage: storageName!, id: fileId, offset: start ?? 0, size: length ?? -1) { + if let data = try? Data(contentsOf: cache) { + os_log("%{public}@", log: log, type: .debug, "hit cache(File:\(storageName ?? "") \(fileId) \(start ?? -1) \(length ?? -1) \((start ?? 0) + (length ?? 0))") + onFinish?(data) + return + } + } + + os_log("%{public}@", log: log, type: .debug, "readFile(File:\(storageName ?? "") \(fileId) \(start ?? -1) \(length ?? -1) \((start ?? 0) + (length ?? 0))") + + do { + var isStale = false + let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) + + if isStale { + print("url is Stale", url) + // Handle stale data here. + let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) + + let _ = self.setKeyChain(key: "\(self.storageName ?? "")_bookmarkData", value: bookmarkData.base64EncodedString()) + } + + // Use the URL here. + + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + // Use file coordination for reading and writing any of the URL’s content. + var error: NSError? = nil + NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in + + var targetURL = url + if fileId != "" { + targetURL.appendPathComponent(fileId) + } + + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + var ret: Data? + let reqOffset = Int(start ?? 0) + do { + let hFile = try FileHandle(forReadingFrom: targetURL) + defer { + do { + if #available(iOS 13.0, *) { + try hFile.close() + } else { + hFile.closeFile() + } + } + catch { + print(error) + } + } + if #available(iOS 13.0, *) { + try hFile.seek(toOffset: UInt64(reqOffset)) + } else { + hFile.seek(toFileOffset: UInt64(reqOffset)) + } + if let size = length { + ret = hFile.readData(ofLength: Int(size)) + } + else { + ret = hFile.readDataToEndOfFile() + } + } + catch { + print(error) + } + if let d = ret { + CloudFactory.shared.cache.saveCache(storage: self.storageName!, id: fileId, offset: start ?? 0, data: d) + } + DispatchQueue.global().async { + onFinish?(ret) + } + } + } + catch let error { + // Handle the error here. + print(error) + onFinish?(nil) + } + } + + public override func getRaw(fileId: String) -> RemoteItem? { + return NetworkRemoteItem(storage: storageName ?? "", id: fileId) + } + + public override func getRaw(path: String) -> RemoteItem? { + return NetworkRemoteItem(path: path) + } + + public override func makeFolder(parentId: String, parentPath: String, newname: String, callCount: Int = 0, onFinish: ((String?) -> Void)?) { + + os_log("%{public}@", log: log, type: .debug, "makeFolder(File:\(storageName ?? "") \(parentId) \(newname)") + + do { + var isStale = false + let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) + + if isStale { + print("url is Stale", url) + // Handle stale data here. + let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) + + let _ = self.setKeyChain(key: "\(self.storageName ?? "")_bookmarkData", value: bookmarkData.base64EncodedString()) + } + + // Use the URL here. + + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + // Use file coordination for reading and writing any of the URL’s content. + var error: NSError? = nil + NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in + + var targetURL = url + if parentId != "" { + targetURL = targetURL.appendingPathComponent(parentId, isDirectory: true) + } + targetURL = targetURL.appendingPathComponent(newname, isDirectory: true) + + do { + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + try FileManager.default.createDirectory(at: targetURL, withIntermediateDirectories: false) + let backgroundContext = CloudFactory.shared.data.persistentContainer.newBackgroundContext() + storeItem(item: targetURL, parentFileId: parentId, parentPath: parentPath, context: backgroundContext) + let id = getIdFromURL(url: targetURL) + backgroundContext.perform { + try? backgroundContext.save() + DispatchQueue.global().async { + onFinish?(id) + } + } + } + catch { + onFinish?(nil) + } + } + } + catch let error { + // Handle the error here. + print(error) + onFinish?(nil) + } + } + + + override func moveItem(fileId: String, fromParentId: String, toParentId: String, callCount: Int = 0, onFinish: ((String?) -> Void)?) { + + do { + if fromParentId == toParentId { + onFinish?(nil) + return + } + + var isStale = false + let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) + + if isStale { + print("url is Stale", url) + // Handle stale data here. + let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) + + let _ = self.setKeyChain(key: "\(self.storageName ?? "")_bookmarkData", value: bookmarkData.base64EncodedString()) + } + + // Use the URL here. + + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + // Use file coordination for reading and writing any of the URL’s content. + var error: NSError? = nil + NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in + + let fromURL = url.appendingPathComponent(fileId) + let name = fromURL.lastPathComponent + var targetURL = url + var parentPath = "" + if toParentId != "" { + targetURL = url.appendingPathComponent(toParentId, isDirectory: true) + if Thread.isMainThread { + let viewContext = CloudFactory.shared.data.persistentContainer.viewContext + + let fetchRequest = NSFetchRequest(entityName: "RemoteData") + fetchRequest.predicate = NSPredicate(format: "id == %@ && storage == %@", toParentId, self.storageName ?? "") + if let result = try? viewContext.fetch(fetchRequest) { + if let items = result as? [RemoteData] { + parentPath = items.first?.path ?? "" + } + } + } + else { + DispatchQueue.main.sync { + let viewContext = CloudFactory.shared.data.persistentContainer.viewContext + + let fetchRequest = NSFetchRequest(entityName: "RemoteData") + fetchRequest.predicate = NSPredicate(format: "id == %@ && storage == %@", toParentId, self.storageName ?? "") + if let result = try? viewContext.fetch(fetchRequest) { + if let items = result as? [RemoteData] { + parentPath = items.first?.path ?? "" + } + } + } + } + } + let backgroundContext = CloudFactory.shared.data.persistentContainer.newBackgroundContext() + backgroundContext.perform { + let fetchRequest2 = NSFetchRequest(entityName: "RemoteData") + fetchRequest2.predicate = NSPredicate(format: "id == %@ && storage == %@", fileId, self.storageName ?? "") + if let result = try? backgroundContext.fetch(fetchRequest2) { + for object in result { + backgroundContext.delete(object as! NSManagedObject) + } + } + } + targetURL = targetURL.appendingPathComponent(name) + + os_log("%{public}@", log: self.log, type: .debug, "moveItem(File:\(self.storageName ?? "") \(fromParentId)->\(toParentId)") + + do { + try FileManager.default.moveItem(at: fromURL, to: targetURL) + self.storeItem(item: targetURL, parentFileId: toParentId, parentPath: parentPath, context: backgroundContext) + let id = self.getIdFromURL(url: targetURL) + backgroundContext.perform { + try? backgroundContext.save() + DispatchQueue.global().async { + onFinish?(id) + } + } + } + catch { + onFinish?(nil) + } + + } + } + catch let error { + // Handle the error here. + print(error) + onFinish?(nil) + } + } + + override func deleteItem(fileId: String, callCount: Int = 0, onFinish: ((Bool) -> Void)?) { + + os_log("%{public}@", log: log, type: .debug, "deleteItem(File:\(storageName ?? "") \(fileId)") + + do { + var isStale = false + let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) + + if isStale { + print("url is Stale", url) + // Handle stale data here. + let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) + + let _ = self.setKeyChain(key: "\(self.storageName ?? "")_bookmarkData", value: bookmarkData.base64EncodedString()) + } + + // Use the URL here. + + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(false) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + // Use file coordination for reading and writing any of the URL’s content. + var error: NSError? = nil + NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in + + var targetURL = url + targetURL.appendPathComponent(fileId) + + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(false) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + do { + try FileManager.default.removeItem(at: targetURL) + let backgroundContext = CloudFactory.shared.data.persistentContainer.newBackgroundContext() + backgroundContext.performAndWait { + let fetchRequest = NSFetchRequest(entityName: "RemoteData") + fetchRequest.predicate = NSPredicate(format: "id == %@ && storage == %@", fileId, self.storageName ?? "") + if let result = try? backgroundContext.fetch(fetchRequest) { + for object in result { + backgroundContext.delete(object as! NSManagedObject) + } + } + + self.deleteChildRecursive(parent: fileId, context: backgroundContext) + } + backgroundContext.perform { + try? backgroundContext.save() + DispatchQueue.global().async { + onFinish?(true) + } + } + } + catch { + onFinish?(false) + } + } + } + catch let error { + // Handle the error here. + print(error) + onFinish?(false) + } + } + + override func renameItem(fileId: String, newname: String, callCount: Int = 0, onFinish: ((String?) -> Void)?) { + + os_log("%{public}@", log: log, type: .debug, "renameItem(File:\(storageName ?? "") \(fileId)") + + do { + var isStale = false + let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) + + if isStale { + print("url is Stale", url) + // Handle stale data here. + let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) + + let _ = self.setKeyChain(key: "\(self.storageName ?? "")_bookmarkData", value: bookmarkData.base64EncodedString()) + } + + // Use the URL here. + + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + // Use file coordination for reading and writing any of the URL’s content. + var error: NSError? = nil + NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in + + let fromURL = url.appendingPathComponent(fileId) + let newURL = fromURL.deletingLastPathComponent().appendingPathComponent(newname) + + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + do { + try FileManager.default.moveItem(at: fromURL, to: newURL) + var parentPath: String? + var parentId: String? + let backgroundContext = CloudFactory.shared.data.persistentContainer.newBackgroundContext() + backgroundContext.perform { + let fetchRequest2 = NSFetchRequest(entityName: "RemoteData") + fetchRequest2.predicate = NSPredicate(format: "id == %@ && storage == %@", fileId, self.storageName ?? "") + if let result = try? backgroundContext.fetch(fetchRequest2) as? [RemoteData] { + for object in result { + parentPath = object.path + let component = parentPath?.components(separatedBy: "/") + parentPath = component?.dropLast().joined(separator: "/") + parentId = object.parent + backgroundContext.delete(object) + } + } + } + self.storeItem(item: newURL, parentFileId: parentId, parentPath: parentPath, context: backgroundContext) + backgroundContext.perform { + try? backgroundContext.save() + let id = self.getIdFromURL(url: newURL) + DispatchQueue.global().async { + onFinish?(id) + } + } + } + catch { + onFinish?(nil) + } + } + } + catch let error { + // Handle the error here. + print(error) + onFinish?(nil) + } + } + + override func changeTime(fileId: String, newdate: Date, callCount: Int = 0, onFinish: ((String?) -> Void)?) { + + os_log("%{public}@", log: log, type: .debug, "changeTime(File:\(storageName ?? "") \(fileId) \(newdate)") + + do { + var isStale = false + let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) + + if isStale { + print("url is Stale", url) + // Handle stale data here. + let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) + + let _ = self.setKeyChain(key: "\(self.storageName ?? "")_bookmarkData", value: bookmarkData.base64EncodedString()) + } + + // Use the URL here. + + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + // Use file coordination for reading and writing any of the URL’s content. + var error: NSError? = nil + NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in + + let targetURL = url.appendingPathComponent(fileId) + + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + do { + try FileManager.default.setAttributes([FileAttributeKey.modificationDate: newdate], ofItemAtPath: targetURL.path) + let backgroundContext = CloudFactory.shared.data.persistentContainer.newBackgroundContext() + self.storeItem(item: targetURL, context: backgroundContext) + let id = getIdFromURL(url: targetURL) + backgroundContext.perform { + try? backgroundContext.save() + DispatchQueue.global().async { + onFinish?(id) + } + } + } + catch { + print(error) + onFinish?(nil) + } + } + } + catch let error { + // Handle the error here. + print(error) + onFinish?(nil) + } + } + + override func uploadFile(parentId: String, sessionId: String, uploadname: String, target: URL, onFinish: ((String?)->Void)?) { + + var parentPath = "" + if parentId != "" { + if Thread.isMainThread { + let viewContext = CloudFactory.shared.data.persistentContainer.viewContext + + let fetchRequest = NSFetchRequest(entityName: "RemoteData") + fetchRequest.predicate = NSPredicate(format: "id == %@ && storage == %@", parentId, self.storageName ?? "") + if let result = try? viewContext.fetch(fetchRequest) { + if let items = result as? [RemoteData] { + parentPath = items.first?.path ?? "" + } + } + } + else { + DispatchQueue.main.sync { + let viewContext = CloudFactory.shared.data.persistentContainer.viewContext + + let fetchRequest = NSFetchRequest(entityName: "RemoteData") + fetchRequest.predicate = NSPredicate(format: "id == %@ && storage == %@", parentId, self.storageName ?? "") + if let result = try? viewContext.fetch(fetchRequest) { + if let items = result as? [RemoteData] { + parentPath = items.first?.path ?? "" + } + } + } + } + } + + os_log("%{public}@", log: log, type: .debug, "uploadFile(File:\(storageName ?? "") \(uploadname)->\(parentId) \(target)") + + do { + var isStale = false + let url = try URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) + + if isStale { + print("url is Stale", url) + // Handle stale data here. + let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) + + let _ = self.setKeyChain(key: "\(self.storageName ?? "")_bookmarkData", value: bookmarkData.base64EncodedString()) + } + + // Use the URL here. + + // Start accessing a security-scoped resource. + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + // Use file coordination for reading and writing any of the URL’s content. + var error: NSError? = nil + NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in + + var newURL = url + if parentId != "" { + newURL = url.appendingPathComponent(parentId) + } + newURL = newURL.appendingPathComponent(uploadname) + + guard url.startAccessingSecurityScopedResource() else { + // Handle the failure here. + onFinish?(nil) + return + } + + // Make sure you release the security-scoped resource when you are done. + defer { url.stopAccessingSecurityScopedResource() } + + do { + let attr = try FileManager.default.attributesOfItem(atPath: target.path) + let fileSize = attr[.size] as! UInt64 + + UploadManeger.shared.UploadFixSize(identifier: sessionId, size: Int(fileSize)) + + try FileManager.default.moveItem(at: target, to: newURL) + + UploadManeger.shared.UploadProgress(identifier: sessionId, possition: Int(fileSize)) + + let backgroundContext = CloudFactory.shared.data.persistentContainer.newBackgroundContext() + self.storeItem(item: newURL, parentFileId: parentId, parentPath: parentPath, context: backgroundContext) + let id = self.getIdFromURL(url: newURL) + backgroundContext.perform { + try? backgroundContext.save() + DispatchQueue.global().async { + onFinish?(id) + } + } + } + catch { + onFinish?(nil) + } + } + } + catch let error { + // Handle the error here. + print(error) + onFinish?(nil) + } + } + +} diff --git a/RemoteCloud/RemoteCloud/Storages/LocalStorage.swift b/RemoteCloud/RemoteCloud/Storages/LocalStorage.swift index 0de6256..4f2feab 100644 --- a/RemoteCloud/RemoteCloud/Storages/LocalStorage.swift +++ b/RemoteCloud/RemoteCloud/Storages/LocalStorage.swift @@ -122,47 +122,40 @@ public class LocalStorage: RemoteStorageBase { let targetURL = documentsURL.appendingPathComponent(fileId) print(targetURL) - guard let attr = try? FileManager.default.attributesOfItem(atPath: targetURL.path) else { - return - } - let maxlen = Int(truncating: attr[.size] as? NSNumber ?? 0) - - guard let stream = InputStream(url: targetURL) else { - onFinish?(nil) - return - } - stream.open() - defer { - stream.close() - } - + var ret: Data? let reqOffset = Int(start ?? 0) - var offset = 0 - while offset < reqOffset { - var buflen = reqOffset - offset - if buflen > 1024*1024 { - buflen = 1024*1024 + do { + let hFile = try FileHandle(forReadingFrom: targetURL) + defer { + do { + if #available(iOS 13.0, *) { + try hFile.close() + } else { + hFile.closeFile() + } + } + catch { + print(error) + } } - var buf:[UInt8] = [UInt8](repeating: 0, count: buflen) - let len = stream.read(&buf, maxLength: buf.count) - if len <= 0 { - print(stream.streamError!) - onFinish?(nil) - return + if #available(iOS 13.0, *) { + try hFile.seek(toOffset: UInt64(reqOffset)) + } else { + hFile.seek(toFileOffset: UInt64(reqOffset)) + } + if let size = length { + ret = hFile.readData(ofLength: Int(size)) + } + else { + ret = hFile.readDataToEndOfFile() } - offset += len } - - let len = Int(length ?? Int64(maxlen - reqOffset)) - - var buf:[UInt8] = [UInt8](repeating: 0, count: len) - let rlen = stream.read(&buf, maxLength: buf.count) - if rlen <= 0 { - print(stream.streamError!) - onFinish?(nil) - return + catch { + print(error) + } + DispatchQueue.global().async { + onFinish?(ret) } - onFinish?(Data(buf)) } func getIdFromURL(url: URL) -> String { @@ -259,9 +252,7 @@ public class LocalStorage: RemoteStorageBase { targetURL = targetURL.appendingPathComponent(name) do { try FileManager.default.moveItem(at: fromURL, to: targetURL) - DispatchQueue.main.async { - self.storeItem(item: targetURL, parentFileId: toParentId, parentPath: parentPath, context: backgroundContext) - } + self.storeItem(item: targetURL, parentFileId: toParentId, parentPath: parentPath, context: backgroundContext) let id = self.getIdFromURL(url: targetURL) backgroundContext.perform { try? backgroundContext.save() diff --git a/RemoteCloud/RemoteCloud/Storages/WebDAVStorage.swift b/RemoteCloud/RemoteCloud/Storages/WebDAVStorage.swift index 71d2765..24a80d6 100644 --- a/RemoteCloud/RemoteCloud/Storages/WebDAVStorage.swift +++ b/RemoteCloud/RemoteCloud/Storages/WebDAVStorage.swift @@ -498,7 +498,7 @@ public class WebDAVStorage: NetworkStorage, URLSessionTaskDelegate, URLSessionDa } override func authorize(onFinish: ((Bool) -> Void)?) { - os_log("%{public}@", log: log, type: .debug, "authorize(google:\(storageName ?? ""))") + os_log("%{public}@", log: log, type: .debug, "authorize(WebDAV:\(storageName ?? ""))") DispatchQueue.main.async { if let controller = UIApplication.topViewController() { diff --git a/ccViewer/CryptCloudViewer.xcodeproj/project.pbxproj b/ccViewer/CryptCloudViewer.xcodeproj/project.pbxproj index 468688e..dd3b98f 100644 --- a/ccViewer/CryptCloudViewer.xcodeproj/project.pbxproj +++ b/ccViewer/CryptCloudViewer.xcodeproj/project.pbxproj @@ -803,7 +803,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = ccViewer/ccViewer.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 60; + CURRENT_PROJECT_VERSION = 62; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 7A9X38B4YU; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; @@ -838,7 +838,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = ccViewer/ccViewer.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 60; + CURRENT_PROJECT_VERSION = 62; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 7A9X38B4YU; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; diff --git a/ccViewer/ccViewer/About.storyboard b/ccViewer/ccViewer/About.storyboard index 369034b..5f684b2 100644 --- a/ccViewer/ccViewer/About.storyboard +++ b/ccViewer/ccViewer/About.storyboard @@ -24,7 +24,7 @@ - + @@ -144,7 +144,7 @@ - FFmpeg version 4.2.1

GNU LESSER GENERAL PUBLIC LICENSE + FFmpeg version 4.2.1

GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA @@ -153,7 +153,7 @@ of this license document, but changing it is not allowed. 

Google Cast SDK (iOS Sender v4.4.6)

Google Cast SDK Additional Developer Terms of Service https://developers.google.com/cast/docs/terms Chrome Sender, Media Player Library and Receiver use the Closure library, which is licensed under the Apache 2.0 License, and the lit-html library, which is licensed under the BSD 3-Clause License. - + diff --git a/ccViewer/ccViewer/Assets.xcassets/app.imageset/Contents.json b/ccViewer/ccViewer/Assets.xcassets/app.imageset/Contents.json index 43eea83..8c18007 100644 --- a/ccViewer/ccViewer/Assets.xcassets/app.imageset/Contents.json +++ b/ccViewer/ccViewer/Assets.xcassets/app.imageset/Contents.json @@ -2,15 +2,17 @@ "images" : [ { "idiom" : "universal", + "filename" : "ccViewer340.png", "scale" : "1x" }, { "idiom" : "universal", + "filename" : "ccViewer680.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "ccViewer1024.png", + "filename" : "ccViewer1020.png", "scale" : "3x" } ], diff --git a/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer1020.png b/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer1020.png new file mode 100644 index 0000000..9eb096b Binary files /dev/null and b/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer1020.png differ diff --git a/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer1024.png b/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer1024.png deleted file mode 100644 index 6f915ea..0000000 Binary files a/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer1024.png and /dev/null differ diff --git a/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer340.png b/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer340.png new file mode 100644 index 0000000..dd16ef2 Binary files /dev/null and b/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer340.png differ diff --git a/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer680.png b/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer680.png new file mode 100644 index 0000000..979eee0 Binary files /dev/null and b/ccViewer/ccViewer/Assets.xcassets/app.imageset/ccViewer680.png differ diff --git a/ccViewer/ccViewer/TableViewControllerRoot.swift b/ccViewer/ccViewer/TableViewControllerRoot.swift index 30c24af..67ba270 100644 --- a/ccViewer/ccViewer/TableViewControllerRoot.swift +++ b/ccViewer/ccViewer/TableViewControllerRoot.swift @@ -154,7 +154,7 @@ class TableViewControllerRoot: UITableViewController { let alart0 = UIAlertController(title: "Remove Item", message: NSString(format: NSLocalizedString("Select 'Just hide on menu' / 'Log out'", comment: "") as NSString, name) as String, preferredStyle: .alert) - let hideAction = UIAlertAction(title: NSLocalizedString("Just hide on manu", comment: ""), style: .default) { action in + let hideAction = UIAlertAction(title: NSLocalizedString("Just hide on menu", comment: ""), style: .default) { action in let hideItem = self.storage[indexPath.row] if let hideIndex = self.storageShow.firstIndex(of: hideItem) { self.storageShow.remove(at: hideIndex) diff --git a/ccViewer/ccViewer/ja.lproj/Localizable.strings b/ccViewer/ccViewer/ja.lproj/Localizable.strings index f9caf3b..6e6bca1 100644 --- a/ccViewer/ccViewer/ja.lproj/Localizable.strings +++ b/ccViewer/ccViewer/ja.lproj/Localizable.strings @@ -67,7 +67,7 @@ "as Raw binay" = "生データとして開く"; "Select 'Just hide on menu' / 'Log out'" = "隠すのみ か ログアウト を選択"; -"Just hide on manu" = "隠すのみ"; +"Just hide on menu" = "隠すのみ"; "Partial Play" = "一部のみ再生"; "Start skip" = "最初を飛ばす";