Skip to content

Commit 8de1db1

Browse files
committed
v1.4.2(54)
Add network cache option. Fix ffplayer bug in position when MPEG-TS wrap around occured.
1 parent ad8be7d commit 8de1db1

File tree

12 files changed

+483
-26
lines changed

12 files changed

+483
-26
lines changed

RemoteCloud/RemoteCloud.xcodeproj/project.pbxproj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
3F3ABABD2273119D002A8D16 /* pCloudStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F3ABABC2273119D002A8D16 /* pCloudStorage.swift */; };
2929
3F92E7052336BFBA00D1FF9B /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3F92E7042336BFBA00D1FF9B /* Media.xcassets */; };
3030
3F987DEC2277608A0020A796 /* Cryptomator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F987DEB2277608A0020A796 /* Cryptomator.swift */; };
31+
3FA46CE223849615007ACC82 /* FileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA46CE123849615007ACC82 /* FileCache.swift */; };
32+
3FA46CE5238496B7007ACC82 /* cache.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 3FA46CE3238496B7007ACC82 /* cache.xcdatamodeld */; };
3133
3FE23B1F2340B96200916DA6 /* UploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE23B1E2340B96200916DA6 /* UploadManager.swift */; };
3234
/* End PBXBuildFile section */
3335

@@ -66,6 +68,8 @@
6668
3F3ABABC2273119D002A8D16 /* pCloudStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = pCloudStorage.swift; sourceTree = "<group>"; };
6769
3F92E7042336BFBA00D1FF9B /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; };
6870
3F987DEB2277608A0020A796 /* Cryptomator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cryptomator.swift; sourceTree = "<group>"; };
71+
3FA46CE123849615007ACC82 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = "<group>"; };
72+
3FA46CE4238496B7007ACC82 /* cache.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = cache.xcdatamodel; sourceTree = "<group>"; };
6973
3FE23B1E2340B96200916DA6 /* UploadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadManager.swift; sourceTree = "<group>"; };
7074
/* End PBXFileReference section */
7175

@@ -131,6 +135,8 @@
131135
3F3ABABA2272C707002A8D16 /* Secret.swift */,
132136
3F92E7042336BFBA00D1FF9B /* Media.xcassets */,
133137
3FE23B1E2340B96200916DA6 /* UploadManager.swift */,
138+
3FA46CE123849615007ACC82 /* FileCache.swift */,
139+
3FA46CE3238496B7007ACC82 /* cache.xcdatamodeld */,
134140
);
135141
path = RemoteCloud;
136142
sourceTree = "<group>";
@@ -275,6 +281,7 @@
275281
3CA2C95E2235569800481DB5 /* cloud.xcdatamodeld in Sources */,
276282
3F3ABABB2272C707002A8D16 /* Secret.swift in Sources */,
277283
3CF14614223B29360025081A /* CryptRclone.swift in Sources */,
284+
3FA46CE223849615007ACC82 /* FileCache.swift in Sources */,
278285
3CA2C951223495E700481DB5 /* RemoteStorage.swift in Sources */,
279286
3C14C452225CE19E0044E4A7 /* CueSheet.swift in Sources */,
280287
3C0654A122384BED003BD932 /* CryptCarotDAV.swift in Sources */,
@@ -287,6 +294,7 @@
287294
3FE23B1F2340B96200916DA6 /* UploadManager.swift in Sources */,
288295
3C14C454225DBB1A0044E4A7 /* LocalStorage.swift in Sources */,
289296
3CA2C952223495E700481DB5 /* GoogleDriveStorage.swift in Sources */,
297+
3FA46CE5238496B7007ACC82 /* cache.xcdatamodeld in Sources */,
290298
3C400922224190920028A45A /* OneDriveStorage.swift in Sources */,
291299
);
292300
runOnlyForDeploymentPostprocessing = 0;
@@ -569,6 +577,16 @@
569577
sourceTree = "<group>";
570578
versionGroupType = wrapper.xcdatamodel;
571579
};
580+
3FA46CE3238496B7007ACC82 /* cache.xcdatamodeld */ = {
581+
isa = XCVersionGroup;
582+
children = (
583+
3FA46CE4238496B7007ACC82 /* cache.xcdatamodel */,
584+
);
585+
currentVersion = 3FA46CE4238496B7007ACC82 /* cache.xcdatamodel */;
586+
path = cache.xcdatamodeld;
587+
sourceTree = "<group>";
588+
versionGroupType = wrapper.xcdatamodel;
589+
};
572590
/* End XCVersionGroup section */
573591
};
574592
rootObject = 3CA2C9252234952800481DB5 /* Project object */;
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
//
2+
// FileCache.swift
3+
// RemoteCloud
4+
//
5+
// Created by rei8 on 2019/11/20.
6+
// Copyright © 2019 lithium03. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import CoreData
11+
12+
public class FileCache {
13+
public var cacheMaxSize: Int {
14+
get {
15+
return UserDefaults.standard.integer(forKey: "networkCacheSize")
16+
}
17+
set {
18+
UserDefaults.standard.set(newValue, forKey: "networkCacheSize")
19+
}
20+
}
21+
22+
public func getCache(storage: String, id: String, offset: Int64, size: Int64) -> URL? {
23+
var ret: URL? = nil
24+
guard cacheMaxSize > 0 else {
25+
return nil
26+
}
27+
if Thread.isMainThread {
28+
guard let orgItem = CloudFactory.shared.data.getData(storage: storage, fileId: id) else {
29+
return nil
30+
}
31+
32+
let viewContext = self.persistentContainer.viewContext
33+
34+
let fetchrequest = NSFetchRequest<NSFetchRequestResult>(entityName: "FileCacheItem")
35+
fetchrequest.predicate = NSPredicate(format: "storage == %@ && id == %@ && chunkOffset == %lld", storage, id, offset)
36+
do{
37+
guard let item = try viewContext.fetch(fetchrequest).first as? FileCacheItem else {
38+
return nil
39+
}
40+
if orgItem.mdate != item.mdate || orgItem.size != item.orgSize {
41+
if let target = item.filename?.uuidString {
42+
let base = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("NetCache", isDirectory: true)
43+
let target_path = base.appendingPathComponent(String(target.prefix(2)), isDirectory: true).appendingPathComponent(String(target.prefix(4).suffix(2)), isDirectory: true).appendingPathComponent(target)
44+
try FileManager.default.removeItem(at: target_path)
45+
}
46+
viewContext.delete(item)
47+
try viewContext.save()
48+
return nil
49+
}
50+
if let target = item.filename?.uuidString, (size == item.chunkSize || size < 0) {
51+
let base = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("NetCache", isDirectory: true)
52+
let target_path = base.appendingPathComponent(String(target.prefix(2)), isDirectory: true).appendingPathComponent(String(target.prefix(4).suffix(2)), isDirectory: true).appendingPathComponent(target)
53+
if FileManager.default.fileExists(atPath: target_path.path) {
54+
ret = target_path
55+
item.rdate = Date()
56+
try? viewContext.save()
57+
}
58+
else {
59+
viewContext.delete(item)
60+
try viewContext.save()
61+
}
62+
}
63+
}
64+
catch{
65+
return nil
66+
}
67+
return ret
68+
}
69+
else {
70+
DispatchQueue.main.sync {
71+
ret = getCache(storage: storage, id: id, offset: offset, size: size)
72+
}
73+
return ret
74+
}
75+
}
76+
77+
public func saveCache(storage: String, id: String, offset: Int64, data: Data) {
78+
guard cacheMaxSize > 0 else {
79+
increseFreeSpace()
80+
return
81+
}
82+
do {
83+
let size = Int64(data.count)
84+
let base = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("NetCache", isDirectory: true)
85+
let newId = UUID()
86+
let target = newId.uuidString
87+
let target_path = base.appendingPathComponent(String(target.prefix(2)), isDirectory: true).appendingPathComponent(String(target.prefix(4).suffix(2)), isDirectory: true)
88+
try FileManager.default.createDirectory(at: target_path, withIntermediateDirectories: true, attributes: nil)
89+
try data.write(to: target_path.appendingPathComponent(target))
90+
91+
if getCacheSize() > cacheMaxSize {
92+
increseFreeSpace()
93+
}
94+
95+
DispatchQueue.main.async {
96+
guard let orgItem = CloudFactory.shared.data.getData(storage: storage, fileId: id) else {
97+
try? FileManager.default.removeItem(at: target_path.appendingPathComponent(target))
98+
return
99+
}
100+
101+
let viewContext = self.persistentContainer.viewContext
102+
103+
let fetchrequest = NSFetchRequest<NSFetchRequestResult>(entityName: "FileCacheItem")
104+
fetchrequest.predicate = NSPredicate(format: "storage == %@ && id == %@ && chunkOffset == %lld && chunkSize == %lld", storage, id, offset, size)
105+
do {
106+
if let items = try viewContext.fetch(fetchrequest) as? [FileCacheItem], items.count > 0 {
107+
try FileManager.default.removeItem(at: target_path.appendingPathComponent(target))
108+
return
109+
}
110+
}
111+
catch {
112+
print(error)
113+
}
114+
115+
let newitem = FileCacheItem(context: viewContext)
116+
newitem.filename = newId
117+
newitem.storage = storage
118+
newitem.id = id
119+
newitem.chunkSize = size
120+
newitem.chunkOffset = offset
121+
newitem.rdate = Date()
122+
newitem.mdate = orgItem.mdate
123+
newitem.orgSize = orgItem.size
124+
125+
try? viewContext.save()
126+
}
127+
}
128+
catch {
129+
print(error)
130+
}
131+
}
132+
133+
public func increseFreeSpace() {
134+
guard let attributes = try? FileManager.default.attributesOfFileSystem(forPath: NSTemporaryDirectory()) else {
135+
return
136+
}
137+
let freesize = (attributes[.systemFreeSize] as? NSNumber)?.int64Value ?? 0
138+
var incSize = getCacheSize() - cacheMaxSize
139+
if freesize < 512*1024*1024 {
140+
let incSize2 = 512*1024*1024 - Int(freesize)
141+
if incSize < 0 {
142+
incSize = incSize2
143+
}
144+
else {
145+
incSize += incSize2
146+
}
147+
}
148+
var delSize = 0
149+
guard let base = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("NetCache", isDirectory: true) else {
150+
return
151+
}
152+
DispatchQueue.main.async {
153+
let viewContext = self.persistentContainer.viewContext
154+
155+
let fetchrequest = NSFetchRequest<NSFetchRequestResult>(entityName: "FileCacheItem")
156+
fetchrequest.predicate = NSPredicate(value: true)
157+
fetchrequest.sortDescriptors = [NSSortDescriptor(key: "rdate", ascending: true)]
158+
do {
159+
if let items = try viewContext.fetch(fetchrequest) as? [FileCacheItem], items.count > 0 {
160+
for item in items {
161+
if delSize > incSize {
162+
break
163+
}
164+
guard let target = item.filename?.uuidString else {
165+
continue
166+
}
167+
let target_path = base.appendingPathComponent(String(target.prefix(2)), isDirectory: true).appendingPathComponent(String(target.prefix(4).suffix(2)), isDirectory: true).appendingPathComponent(target)
168+
try FileManager.default.removeItem(at: target_path)
169+
delSize += Int(item.chunkSize)
170+
viewContext.delete(item)
171+
}
172+
try viewContext.save()
173+
}
174+
}
175+
catch {
176+
print(error)
177+
}
178+
}
179+
}
180+
181+
public func getCacheSize() -> Int {
182+
guard let base = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("NetCache", isDirectory: true) else {
183+
return 0
184+
}
185+
186+
let resourceKeys = Set<URLResourceKey>([.fileAllocatedSizeKey])
187+
let directoryEnumerator = FileManager.default.enumerator(at: base, includingPropertiesForKeys: Array(resourceKeys), options: .skipsHiddenFiles)!
188+
189+
var allocSize = 0
190+
for case let fileURL as URL in directoryEnumerator {
191+
guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
192+
let size = resourceValues.fileAllocatedSize
193+
else {
194+
continue
195+
}
196+
allocSize += size
197+
}
198+
return allocSize
199+
}
200+
201+
// MARK: - Core Data stack
202+
public lazy var persistentContainer: NSPersistentContainer = {
203+
/*
204+
The persistent container for the application. This implementation
205+
creates and returns a container, having loaded the store for the
206+
application to it. This property is optional since there are legitimate
207+
error conditions that could cause the creation of the store to fail.
208+
*/
209+
let modelURL = Bundle(for: CloudFactory.self).url(forResource: "cache", withExtension: "momd")!
210+
let mom = NSManagedObjectModel(contentsOf: modelURL)!
211+
212+
let container = NSPersistentContainer(name: "cache", managedObjectModel: mom)
213+
let location = container.persistentStoreDescriptions.first!.url!
214+
let description = NSPersistentStoreDescription(url: location)
215+
description.shouldInferMappingModelAutomatically = true
216+
description.shouldMigrateStoreAutomatically = true
217+
description.setOption(FileProtectionType.completeUnlessOpen as NSObject, forKey: NSPersistentStoreFileProtectionKey)
218+
container.persistentStoreDescriptions = [description]
219+
220+
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
221+
if let error = error as NSError? {
222+
// Replace this implementation with code to handle the error appropriately.
223+
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
224+
225+
/*
226+
Typical reasons for an error here include:
227+
* The parent directory does not exist, cannot be created, or disallows writing.
228+
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
229+
* The device is out of space.
230+
* The store could not be migrated to the current model version.
231+
Check the error message to determine what the actual problem was.
232+
*/
233+
fatalError("Unresolved error \(error), \(error.userInfo)")
234+
}
235+
})
236+
return container
237+
}()
238+
239+
// MARK: - Core Data Saving support
240+
241+
public func saveContext () {
242+
let context = persistentContainer.viewContext
243+
if context.hasChanges {
244+
do {
245+
try context.save()
246+
} catch {
247+
// Replace this implementation with code to handle the error appropriately.
248+
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
249+
let nserror = error as NSError
250+
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
251+
}
252+
}
253+
}
254+
}

RemoteCloud/RemoteCloud/RemoteStorage.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ public class CloudFactory {
233233
}
234234

235235
public let data = dataItems()
236+
public let cache = FileCache()
236237

237238
public func getIcon(service: CloudStorages) -> UIImage? {
238239
switch service {

RemoteCloud/RemoteCloud/Storages/GoogleDriveStorage.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,13 @@ public class GoogleDriveStorage: NetworkStorage, URLSessionTaskDelegate, URLSess
686686
}
687687

688688
override func readFile(fileId: String, start: Int64? = nil, length: Int64? = nil, callCount: Int = 0, onFinish: ((Data?) -> Void)?) {
689+
if let cache = CloudFactory.shared.cache.getCache(storage: storageName!, id: fileId, offset: start ?? 0, size: length ?? -1) {
690+
if let data = try? Data(contentsOf: cache) {
691+
os_log("%{public}@", log: log, type: .debug, "hit cache(google:\(storageName ?? "") \(fileId) \(start ?? -1) \(length ?? -1) \((start ?? 0) + (length ?? 0))")
692+
onFinish?(data)
693+
return
694+
}
695+
}
689696
if lastCall.timeIntervalSinceNow > -callWait || callSemaphore.wait(wallTimeout: .now()+Double.random(in: 0..<callWait)) == .timedOut {
690697
if cancelTime.timeIntervalSinceNow > 0 {
691698
cancelTime = Date(timeIntervalSinceNow: 0.5)
@@ -748,6 +755,9 @@ public class GoogleDriveStorage: NetworkStorage, URLSessionTaskDelegate, URLSess
748755
return
749756
}
750757
}
758+
if let d = data {
759+
CloudFactory.shared.cache.saveCache(storage: self.storageName!, id: fileId, offset: start ?? 0, data: d)
760+
}
751761
onFinish?(data)
752762
}
753763
task.resume()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="15508" systemVersion="19B88" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
3+
<entity name="FileCacheItem" representedClassName="FileCacheItem" syncable="YES" codeGenerationType="class">
4+
<attribute name="chunkOffset" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
5+
<attribute name="chunkSize" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
6+
<attribute name="filename" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
7+
<attribute name="id" optional="YES" attributeType="String"/>
8+
<attribute name="mdate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
9+
<attribute name="orgSize" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
10+
<attribute name="rdate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
11+
<attribute name="storage" optional="YES" attributeType="String"/>
12+
</entity>
13+
<elements>
14+
<element name="FileCacheItem" positionX="-63" positionY="-18" width="128" height="163"/>
15+
</elements>
16+
</model>

ccViewer/CryptCloudViewer.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@
813813
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
814814
CODE_SIGN_ENTITLEMENTS = ccViewer/ccViewer.entitlements;
815815
CODE_SIGN_STYLE = Automatic;
816-
CURRENT_PROJECT_VERSION = 53;
816+
CURRENT_PROJECT_VERSION = 54;
817817
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
818818
DEVELOPMENT_TEAM = 7A9X38B4YU;
819819
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES;
@@ -848,7 +848,7 @@
848848
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
849849
CODE_SIGN_ENTITLEMENTS = ccViewer/ccViewer.entitlements;
850850
CODE_SIGN_STYLE = Automatic;
851-
CURRENT_PROJECT_VERSION = 53;
851+
CURRENT_PROJECT_VERSION = 54;
852852
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
853853
DEVELOPMENT_TEAM = 7A9X38B4YU;
854854
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES;

ccViewer/ccViewer/TableViewControllerItems.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1696,7 +1696,6 @@ class CustomPresentationController: UIPresentationController {
16961696
}
16971697

16981698
overlayView.frame = containerView.bounds
1699-
//overlayView.gestureRecognizers = [UITapGestureRecognizer(target: self, action: #selector(CustomPresentationController.overlayViewDidTouch(_:)))]
17001699
overlayView.backgroundColor = .black
17011700
overlayView.alpha = 0.0
17021701
containerView.insertSubview(overlayView, at: 0)

0 commit comments

Comments
 (0)