mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-17 03:40:18 +00:00
Storage management improvements
This commit is contained in:
parent
25e3c1447e
commit
a2da668fdf
@ -253,7 +253,7 @@
|
|||||||
"PUSH_CHAT_REACT_INVOICE" = "%2$@|%1$@ %3$@ to your invoice";
|
"PUSH_CHAT_REACT_INVOICE" = "%2$@|%1$@ %3$@ to your invoice";
|
||||||
"PUSH_CHAT_REACT_GIF" = "%2$@|%1$@ %3$@ to your GIF";
|
"PUSH_CHAT_REACT_GIF" = "%2$@|%1$@ %3$@ to your GIF";
|
||||||
|
|
||||||
"PUSH_MESSAGE_SUGGEST_USERPIC" = "%1$@ suggested you new profile photo";
|
"PUSH_MESSAGE_SUGGEST_USERPIC" = "%1$@|suggested you new profile photo";
|
||||||
|
|
||||||
|
|
||||||
"PUSH_REMINDER_TITLE" = "🗓 Reminder";
|
"PUSH_REMINDER_TITLE" = "🗓 Reminder";
|
||||||
|
|||||||
@ -57,6 +57,7 @@ public enum MediaResourceUserContentType: UInt8, Equatable {
|
|||||||
case file = 4
|
case file = 4
|
||||||
case sticker = 6
|
case sticker = 6
|
||||||
case avatar = 7
|
case avatar = 7
|
||||||
|
case audioVideoMessage = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct MediaResourceFetchParameters {
|
public struct MediaResourceFetchParameters {
|
||||||
|
|||||||
@ -230,6 +230,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func internalClose() {
|
func internalClose() {
|
||||||
|
self.clearStatements()
|
||||||
self.database = nil
|
self.database = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -766,7 +766,7 @@ public final class StorageBox {
|
|||||||
return allStats
|
return allStats
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(peerId: Int64?, contentTypes: [UInt8]) -> [Data] {
|
func remove(peerId: Int64?, contentTypes: [UInt8], excludeIds: [Data]) -> [Data] {
|
||||||
var resultIds: [Data] = []
|
var resultIds: [Data] = []
|
||||||
|
|
||||||
self.valueBox.begin()
|
self.valueBox.begin()
|
||||||
@ -786,17 +786,20 @@ public final class StorageBox {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let excludeIds = Set(excludeIds)
|
||||||
|
|
||||||
if let peerId = peerId {
|
if let peerId = peerId {
|
||||||
var filteredHashIds: [Data] = []
|
var filteredHashIds: [Data] = []
|
||||||
self.valueBox.scan(self.idToReferenceTable, keys: { key in
|
self.valueBox.scan(self.idToReferenceTable, keys: { key in
|
||||||
let id = key.getData(0, length: 16)
|
let id = key.getData(0, length: 16)
|
||||||
if scannedIds[id] == nil {
|
guard let realId = scannedIds[id] else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if excludeIds.contains(realId) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
let itemPeerId = key.getInt64(16)
|
let itemPeerId = key.getInt64(16)
|
||||||
//let messageNamespace: UInt8 = key.getUInt8(16 + 8)
|
|
||||||
//let messageId = key.getInt32(16 + 8 + 1)
|
|
||||||
|
|
||||||
if itemPeerId == peerId {
|
if itemPeerId == peerId {
|
||||||
filteredHashIds.append(id)
|
filteredHashIds.append(id)
|
||||||
@ -812,23 +815,20 @@ public final class StorageBox {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (hashId, id) in scannedIds {
|
for (hashId, id) in scannedIds {
|
||||||
|
if excludeIds.contains(id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
self.internalRemove(hashId: hashId)
|
self.internalRemove(hashId: hashId)
|
||||||
resultIds.append(id)
|
resultIds.append(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let peerId = peerId {
|
|
||||||
let _ = peerId
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
self.valueBox.commit()
|
self.valueBox.commit()
|
||||||
|
|
||||||
return Array(resultIds)
|
return Array(resultIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(peerIds: Set<PeerId>) -> [Data] {
|
func remove(peerIds: Set<PeerId>, includeIds: [Data], excludeIds: [Data]) -> [Data] {
|
||||||
var resultIds: [Data] = []
|
var resultIds: [Data] = []
|
||||||
|
|
||||||
self.valueBox.begin()
|
self.valueBox.begin()
|
||||||
@ -838,7 +838,16 @@ public final class StorageBox {
|
|||||||
scannedIds.formUnion(self.allInternal(peerId: peerId))
|
scannedIds.formUnion(self.allInternal(peerId: peerId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for id in includeIds {
|
||||||
|
scannedIds.insert(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
let excludedIds = Set(excludeIds)
|
||||||
|
|
||||||
for id in scannedIds {
|
for id in scannedIds {
|
||||||
|
if excludedIds.contains(id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
self.internalRemove(hashId: md5Hash(id).data)
|
self.internalRemove(hashId: md5Hash(id).data)
|
||||||
resultIds.append(id)
|
resultIds.append(id)
|
||||||
}
|
}
|
||||||
@ -936,16 +945,16 @@ public final class StorageBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func remove(peerId: PeerId?, contentTypes: [UInt8], completion: @escaping ([Data]) -> Void) {
|
public func remove(peerId: PeerId?, contentTypes: [UInt8], excludeIds: [Data], completion: @escaping ([Data]) -> Void) {
|
||||||
self.impl.with { impl in
|
self.impl.with { impl in
|
||||||
let ids = impl.remove(peerId: peerId?.toInt64(), contentTypes: contentTypes)
|
let ids = impl.remove(peerId: peerId?.toInt64(), contentTypes: contentTypes, excludeIds: excludeIds)
|
||||||
completion(ids)
|
completion(ids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func remove(peerIds: Set<PeerId>, completion: @escaping ([Data]) -> Void) {
|
public func remove(peerIds: Set<PeerId>, includeIds: [Data], excludeIds: [Data], completion: @escaping ([Data]) -> Void) {
|
||||||
self.impl.with { impl in
|
self.impl.with { impl in
|
||||||
let ids = impl.remove(peerIds: peerIds)
|
let ids = impl.remove(peerIds: peerIds, includeIds: includeIds, excludeIds: excludeIds)
|
||||||
completion(ids)
|
completion(ids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,95 @@ public func printOpenFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func scanFiles(at path: String, olderThan minTimestamp: Int32, inodes: inout [InodeInfo]) -> ScanFilesResult {
|
private final class TempScanDatabase {
|
||||||
|
private let queue: Queue
|
||||||
|
private let valueBox: SqliteValueBox
|
||||||
|
|
||||||
|
private let accessTimeTable: ValueBoxTable
|
||||||
|
|
||||||
|
private var nextId: Int32 = 0
|
||||||
|
|
||||||
|
private let accessTimeKey = ValueBoxKey(length: 4 + 4)
|
||||||
|
private let accessInfoBuffer = WriteBuffer()
|
||||||
|
|
||||||
|
init?(queue: Queue, basePath: String) {
|
||||||
|
self.queue = queue
|
||||||
|
guard let valueBox = SqliteValueBox(basePath: basePath, queue: queue, isTemporary: true, isReadOnly: false, useCaches: true, removeDatabaseOnError: true, encryptionParameters: nil, upgradeProgress: { _ in }) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.valueBox = valueBox
|
||||||
|
|
||||||
|
self.accessTimeTable = ValueBoxTable(id: 2, keyType: .binary, compactValuesOnCreation: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func begin() {
|
||||||
|
self.valueBox.begin()
|
||||||
|
}
|
||||||
|
|
||||||
|
func commit() {
|
||||||
|
self.valueBox.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dispose() {
|
||||||
|
self.valueBox.internalClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(pathBuffer: UnsafeMutablePointer<Int8>, pathSize: Int, size: Int64, timestamp: Int32) {
|
||||||
|
let id = self.nextId
|
||||||
|
self.nextId += 1
|
||||||
|
|
||||||
|
var size = size
|
||||||
|
self.accessInfoBuffer.reset()
|
||||||
|
self.accessInfoBuffer.write(&size, length: 8)
|
||||||
|
self.accessInfoBuffer.write(pathBuffer, length: pathSize)
|
||||||
|
|
||||||
|
self.accessTimeKey.setInt32(0, value: timestamp)
|
||||||
|
self.accessTimeKey.setInt32(4, value: id)
|
||||||
|
self.valueBox.set(self.accessTimeTable, key: self.accessTimeKey, value: self.accessInfoBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func topByAccessTime(_ f: (Int64, String) -> Bool) {
|
||||||
|
var startKey = ValueBoxKey(length: 4)
|
||||||
|
startKey.setInt32(0, value: 0)
|
||||||
|
|
||||||
|
let endKey = ValueBoxKey(length: 4)
|
||||||
|
endKey.setInt32(0, value: Int32.max)
|
||||||
|
|
||||||
|
while true {
|
||||||
|
var lastKey: ValueBoxKey?
|
||||||
|
self.valueBox.range(self.accessTimeTable, start: startKey, end: endKey, values: { key, value in
|
||||||
|
var result = true
|
||||||
|
withExtendedLifetime(value, {
|
||||||
|
let readBuffer = ReadBuffer(memoryBufferNoCopy: value)
|
||||||
|
|
||||||
|
var size: Int64 = 0
|
||||||
|
readBuffer.read(&size, offset: 0, length: 8)
|
||||||
|
|
||||||
|
var pathData = Data(count: value.length - 8)
|
||||||
|
pathData.withUnsafeMutableBytes { buffer -> Void in
|
||||||
|
readBuffer.read(buffer.baseAddress!, offset: 0, length: buffer.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let path = String(data: pathData, encoding: .utf8) {
|
||||||
|
result = f(size, path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
lastKey = key
|
||||||
|
|
||||||
|
return result
|
||||||
|
}, limit: 512)
|
||||||
|
|
||||||
|
if let lastKey = lastKey {
|
||||||
|
startKey = lastKey
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSubdirectories: Bool, tempDatabase: TempScanDatabase) -> ScanFilesResult {
|
||||||
var result = ScanFilesResult()
|
var result = ScanFilesResult()
|
||||||
|
|
||||||
if let dp = opendir(path) {
|
if let dp = opendir(path) {
|
||||||
@ -63,21 +151,24 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, inodes: i
|
|||||||
strncat(pathBuffer, "/", 1024)
|
strncat(pathBuffer, "/", 1024)
|
||||||
strncat(pathBuffer, &dirp.pointee.d_name.0, 1024)
|
strncat(pathBuffer, &dirp.pointee.d_name.0, 1024)
|
||||||
|
|
||||||
//puts(pathBuffer)
|
|
||||||
//puts("\n")
|
|
||||||
|
|
||||||
var value = stat()
|
var value = stat()
|
||||||
if stat(pathBuffer, &value) == 0 {
|
if stat(pathBuffer, &value) == 0 {
|
||||||
if value.st_mtimespec.tv_sec < minTimestamp {
|
if (((value.st_mode) & S_IFMT) == S_IFDIR) {
|
||||||
unlink(pathBuffer)
|
if includeSubdirectories {
|
||||||
result.unlinkedCount += 1
|
if let subPath = String(data: Data(bytes: pathBuffer, count: strnlen(pathBuffer, 1024)), encoding: .utf8) {
|
||||||
|
let subResult = scanFiles(at: subPath, olderThan: minTimestamp, includeSubdirectories: true, tempDatabase: tempDatabase)
|
||||||
|
result.totalSize += subResult.totalSize
|
||||||
|
result.unlinkedCount += subResult.unlinkedCount
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result.totalSize += UInt64(value.st_size)
|
if value.st_mtimespec.tv_sec < minTimestamp {
|
||||||
inodes.append(InodeInfo(
|
unlink(pathBuffer)
|
||||||
inode: value.st_ino,
|
result.unlinkedCount += 1
|
||||||
timestamp: Int32(clamping: value.st_mtimespec.tv_sec),
|
} else {
|
||||||
size: UInt32(clamping: value.st_size)
|
result.totalSize += UInt64(value.st_size)
|
||||||
))
|
tempDatabase.add(pathBuffer: pathBuffer, pathSize: strnlen(pathBuffer, 1024), size: Int64(value.st_size), timestamp: Int32(value.st_mtimespec.tv_sec))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,9 +178,8 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, inodes: i
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private func mapFiles(paths: [String], inodes: inout [InodeInfo], removeSize: UInt64, mainStoragePath: String, storageBox: StorageBox) {
|
/*private func mapFiles(paths: [String], inodes: inout [InodeInfo], removeSize: UInt64, mainStoragePath: String, storageBox: StorageBox) {
|
||||||
var removedSize: UInt64 = 0
|
var removedSize: UInt64 = 0
|
||||||
|
|
||||||
inodes.sort(by: { lhs, rhs in
|
inodes.sort(by: { lhs, rhs in
|
||||||
return lhs.timestamp < rhs.timestamp
|
return lhs.timestamp < rhs.timestamp
|
||||||
})
|
})
|
||||||
@ -138,19 +228,25 @@ private func mapFiles(paths: [String], inodes: inout [InodeInfo], removeSize: UI
|
|||||||
|
|
||||||
var value = stat()
|
var value = stat()
|
||||||
if stat(pathBuffer, &value) == 0 {
|
if stat(pathBuffer, &value) == 0 {
|
||||||
if inodesToDelete.contains(value.st_ino) {
|
if (((value.st_mode) & S_IFMT) == S_IFDIR) {
|
||||||
if isMainPath {
|
if let subPath = String(data: Data(bytes: pathBuffer, count: strnlen(pathBuffer, 1024)), encoding: .utf8) {
|
||||||
let nameLength = strnlen(&dirp.pointee.d_name.0, 1024)
|
mapFiles(paths: <#T##[String]#>, inodes: &<#T##[InodeInfo]#>, removeSize: remov, mainStoragePath: mainStoragePath, storageBox: storageBox)
|
||||||
let nameData = Data(bytesNoCopy: &dirp.pointee.d_name.0, count: Int(nameLength), deallocator: .none)
|
}
|
||||||
withExtendedLifetime(nameData, {
|
} else {
|
||||||
if let fileName = String(data: nameData, encoding: .utf8) {
|
if inodesToDelete.contains(value.st_ino) {
|
||||||
if let idData = MediaBox.idForFileName(name: fileName).data(using: .utf8) {
|
if isMainPath {
|
||||||
unlinkedResourceIds.append(idData)
|
let nameLength = strnlen(&dirp.pointee.d_name.0, 1024)
|
||||||
}
|
let nameData = Data(bytesNoCopy: &dirp.pointee.d_name.0, count: Int(nameLength), deallocator: .none)
|
||||||
}
|
withExtendedLifetime(nameData, {
|
||||||
})
|
if let fileName = String(data: nameData, encoding: .utf8) {
|
||||||
|
if let idData = MediaBox.idForFileName(name: fileName).data(using: .utf8) {
|
||||||
|
unlinkedResourceIds.append(idData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
unlink(pathBuffer)
|
||||||
}
|
}
|
||||||
unlink(pathBuffer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,7 +257,7 @@ private func mapFiles(paths: [String], inodes: inout [InodeInfo], removeSize: UI
|
|||||||
if !unlinkedResourceIds.isEmpty {
|
if !unlinkedResourceIds.isEmpty {
|
||||||
storageBox.remove(ids: unlinkedResourceIds)
|
storageBox.remove(ids: unlinkedResourceIds)
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
private final class TimeBasedCleanupImpl {
|
private final class TimeBasedCleanupImpl {
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
@ -178,19 +274,6 @@ private final class TimeBasedCleanupImpl {
|
|||||||
private var gigabytesLimit: Int32?
|
private var gigabytesLimit: Int32?
|
||||||
private let scheduledScanDisposable = MetaDisposable()
|
private let scheduledScanDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
|
||||||
private struct GeneralFile : Comparable, Equatable {
|
|
||||||
let file: String
|
|
||||||
let size: Int
|
|
||||||
let timestamp:Int32
|
|
||||||
static func == (lhs: GeneralFile, rhs: GeneralFile) -> Bool {
|
|
||||||
return lhs.timestamp == rhs.timestamp && lhs.size == rhs.size && lhs.file == rhs.file
|
|
||||||
}
|
|
||||||
static func < (lhs: GeneralFile, rhs: GeneralFile) -> Bool {
|
|
||||||
return lhs.timestamp < rhs.timestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(queue: Queue, storageBox: StorageBox, generalPaths: [String], totalSizeBasedPath: String, shortLivedPaths: [String]) {
|
init(queue: Queue, storageBox: StorageBox, generalPaths: [String], totalSizeBasedPath: String, shortLivedPaths: [String]) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.storageBox = storageBox
|
self.storageBox = storageBox
|
||||||
@ -220,14 +303,22 @@ private final class TimeBasedCleanupImpl {
|
|||||||
let shortLivedPaths = self.shortLivedPaths
|
let shortLivedPaths = self.shortLivedPaths
|
||||||
let storageBox = self.storageBox
|
let storageBox = self.storageBox
|
||||||
let scanOnce = Signal<Never, NoError> { subscriber in
|
let scanOnce = Signal<Never, NoError> { subscriber in
|
||||||
DispatchQueue.global(qos: .background).async {
|
let queue = Queue(name: "TimeBasedCleanupScan", qos: .background)
|
||||||
|
queue.async {
|
||||||
|
let tempDirectory = TempBox.shared.tempDirectory()
|
||||||
|
guard let tempDatabase = TempScanDatabase(queue: queue, basePath: tempDirectory.path) else {
|
||||||
|
postboxLog("TimeBasedCleanup: couldn't create temp database at \(tempDirectory.path)")
|
||||||
|
subscriber.putCompletion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tempDatabase.begin()
|
||||||
|
|
||||||
var removedShortLivedCount: Int = 0
|
var removedShortLivedCount: Int = 0
|
||||||
var removedGeneralCount: Int = 0
|
var removedGeneralCount: Int = 0
|
||||||
let removedGeneralLimitCount: Int = 0
|
let removedGeneralLimitCount: Int = 0
|
||||||
|
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
var inodes: [InodeInfo] = []
|
|
||||||
var paths: [String] = []
|
var paths: [String] = []
|
||||||
|
|
||||||
let timestamp = Int32(Date().timeIntervalSince1970)
|
let timestamp = Int32(Date().timeIntervalSince1970)
|
||||||
@ -241,7 +332,7 @@ private final class TimeBasedCleanupImpl {
|
|||||||
let oldestShortLivedTimestamp = timestamp - shortLived
|
let oldestShortLivedTimestamp = timestamp - shortLived
|
||||||
let oldestGeneralTimestamp = timestamp - general
|
let oldestGeneralTimestamp = timestamp - general
|
||||||
for path in shortLivedPaths {
|
for path in shortLivedPaths {
|
||||||
let scanResult = scanFiles(at: path, olderThan: oldestShortLivedTimestamp, inodes: &inodes)
|
let scanResult = scanFiles(at: path, olderThan: oldestShortLivedTimestamp, includeSubdirectories: true, tempDatabase: tempDatabase)
|
||||||
if !paths.contains(path) {
|
if !paths.contains(path) {
|
||||||
paths.append(path)
|
paths.append(path)
|
||||||
}
|
}
|
||||||
@ -251,7 +342,7 @@ private final class TimeBasedCleanupImpl {
|
|||||||
var totalLimitSize: UInt64 = 0
|
var totalLimitSize: UInt64 = 0
|
||||||
|
|
||||||
for path in generalPaths {
|
for path in generalPaths {
|
||||||
let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, inodes: &inodes)
|
let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, tempDatabase: tempDatabase)
|
||||||
if !paths.contains(path) {
|
if !paths.contains(path) {
|
||||||
paths.append(path)
|
paths.append(path)
|
||||||
}
|
}
|
||||||
@ -259,7 +350,7 @@ private final class TimeBasedCleanupImpl {
|
|||||||
totalLimitSize += scanResult.totalSize
|
totalLimitSize += scanResult.totalSize
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
let scanResult = scanFiles(at: totalSizeBasedPath, olderThan: 0, inodes: &inodes)
|
let scanResult = scanFiles(at: totalSizeBasedPath, olderThan: 0, includeSubdirectories: false, tempDatabase: tempDatabase)
|
||||||
if !paths.contains(totalSizeBasedPath) {
|
if !paths.contains(totalSizeBasedPath) {
|
||||||
paths.append(totalSizeBasedPath)
|
paths.append(totalSizeBasedPath)
|
||||||
}
|
}
|
||||||
@ -267,10 +358,40 @@ private final class TimeBasedCleanupImpl {
|
|||||||
totalLimitSize += scanResult.totalSize
|
totalLimitSize += scanResult.totalSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tempDatabase.commit()
|
||||||
|
|
||||||
|
var unlinkedResourceIds: [Data] = []
|
||||||
|
|
||||||
if totalLimitSize > bytesLimit {
|
if totalLimitSize > bytesLimit {
|
||||||
mapFiles(paths: paths, inodes: &inodes, removeSize: totalLimitSize - bytesLimit, mainStoragePath: totalSizeBasedPath, storageBox: storageBox)
|
var remainingSize = Int64(totalLimitSize)
|
||||||
|
tempDatabase.topByAccessTime { size, filePath in
|
||||||
|
remainingSize -= size
|
||||||
|
|
||||||
|
unlink(filePath)
|
||||||
|
|
||||||
|
if (filePath as NSString).deletingLastPathComponent == totalSizeBasedPath {
|
||||||
|
let fileName = (filePath as NSString).lastPathComponent
|
||||||
|
if let idData = MediaBox.idForFileName(name: fileName).data(using: .utf8) {
|
||||||
|
unlinkedResourceIds.append(idData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//let fileName = filePath.lastPathComponent
|
||||||
|
|
||||||
|
if remainingSize >= bytesLimit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !unlinkedResourceIds.isEmpty {
|
||||||
|
storageBox.remove(ids: unlinkedResourceIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempDatabase.dispose()
|
||||||
|
TempBox.shared.dispose(tempDirectory)
|
||||||
|
|
||||||
if removedShortLivedCount != 0 || removedGeneralCount != 0 || removedGeneralLimitCount != 0 {
|
if removedShortLivedCount != 0 || removedGeneralCount != 0 || removedGeneralLimitCount != 0 {
|
||||||
postboxLog("[TimeBasedCleanup] \(CFAbsoluteTimeGetCurrent() - startTime) s removed \(removedShortLivedCount) short-lived files, \(removedGeneralCount) general files, \(removedGeneralLimitCount) limit files")
|
postboxLog("[TimeBasedCleanup] \(CFAbsoluteTimeGetCurrent() - startTime) s removed \(removedShortLivedCount) short-lived files, \(removedGeneralCount) general files, \(removedGeneralLimitCount) limit files")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -284,29 +284,42 @@ func _internal_renderStorageUsageStatsMessages(account: Account, stats: StorageU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey]) -> Signal<Never, NoError> {
|
func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey], excludeMessages: [Message]) -> Signal<Never, NoError> {
|
||||||
let mediaBox = account.postbox.mediaBox
|
let mediaBox = account.postbox.mediaBox
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
mediaBox.storageBox.remove(peerId: peerId, contentTypes: categories.map { item -> UInt8 in
|
var resourceIds = Set<MediaResourceId>()
|
||||||
let mappedItem: MediaResourceUserContentType
|
for message in excludeMessages {
|
||||||
|
extractMediaResourceIds(message: message, resourceIds: &resourceIds)
|
||||||
|
}
|
||||||
|
var excludeIds: [Data] = []
|
||||||
|
for resourceId in resourceIds {
|
||||||
|
if let data = resourceId.stringRepresentation.data(using: .utf8) {
|
||||||
|
excludeIds.append(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mappedContentTypes: [UInt8] = []
|
||||||
|
for item in categories {
|
||||||
switch item {
|
switch item {
|
||||||
case .photos:
|
case .photos:
|
||||||
mappedItem = .image
|
mappedContentTypes.append(MediaResourceUserContentType.image.rawValue)
|
||||||
case .videos:
|
case .videos:
|
||||||
mappedItem = .video
|
mappedContentTypes.append(MediaResourceUserContentType.video.rawValue)
|
||||||
case .files:
|
case .files:
|
||||||
mappedItem = .file
|
mappedContentTypes.append(MediaResourceUserContentType.file.rawValue)
|
||||||
case .music:
|
case .music:
|
||||||
mappedItem = .audio
|
mappedContentTypes.append(MediaResourceUserContentType.audio.rawValue)
|
||||||
case .stickers:
|
case .stickers:
|
||||||
mappedItem = .sticker
|
mappedContentTypes.append(MediaResourceUserContentType.sticker.rawValue)
|
||||||
case .avatars:
|
case .avatars:
|
||||||
mappedItem = .avatar
|
mappedContentTypes.append(MediaResourceUserContentType.avatar.rawValue)
|
||||||
case .misc:
|
case .misc:
|
||||||
mappedItem = .other
|
mappedContentTypes.append(MediaResourceUserContentType.other.rawValue)
|
||||||
|
mappedContentTypes.append(MediaResourceUserContentType.audioVideoMessage.rawValue)
|
||||||
}
|
}
|
||||||
return mappedItem.rawValue
|
}
|
||||||
}, completion: { ids in
|
|
||||||
|
mediaBox.storageBox.remove(peerId: peerId, contentTypes: mappedContentTypes, excludeIds: excludeIds, completion: { ids in
|
||||||
var resourceIds: [MediaResourceId] = []
|
var resourceIds: [MediaResourceId] = []
|
||||||
for id in ids {
|
for id in ids {
|
||||||
if let value = String(data: id, encoding: .utf8) {
|
if let value = String(data: id, encoding: .utf8) {
|
||||||
@ -345,10 +358,32 @@ func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_clearStorage(account: Account, peerIds: Set<EnginePeer.Id>) -> Signal<Never, NoError> {
|
func _internal_clearStorage(account: Account, peerIds: Set<EnginePeer.Id>, includeMessages: [Message], excludeMessages: [Message]) -> Signal<Never, NoError> {
|
||||||
let mediaBox = account.postbox.mediaBox
|
let mediaBox = account.postbox.mediaBox
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
mediaBox.storageBox.remove(peerIds: peerIds, completion: { ids in
|
var includeResourceIds = Set<MediaResourceId>()
|
||||||
|
for message in includeMessages {
|
||||||
|
extractMediaResourceIds(message: message, resourceIds: &includeResourceIds)
|
||||||
|
}
|
||||||
|
var includeIds: [Data] = []
|
||||||
|
for resourceId in includeResourceIds {
|
||||||
|
if let data = resourceId.stringRepresentation.data(using: .utf8) {
|
||||||
|
includeIds.append(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var excludeResourceIds = Set<MediaResourceId>()
|
||||||
|
for message in excludeMessages {
|
||||||
|
extractMediaResourceIds(message: message, resourceIds: &excludeResourceIds)
|
||||||
|
}
|
||||||
|
var excludeIds: [Data] = []
|
||||||
|
for resourceId in excludeResourceIds {
|
||||||
|
if let data = resourceId.stringRepresentation.data(using: .utf8) {
|
||||||
|
excludeIds.append(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaBox.storageBox.remove(peerIds: peerIds, includeIds: includeIds, excludeIds: excludeIds, completion: { ids in
|
||||||
var resourceIds: [MediaResourceId] = []
|
var resourceIds: [MediaResourceId] = []
|
||||||
for id in ids {
|
for id in ids {
|
||||||
if let value = String(data: id, encoding: .utf8) {
|
if let value = String(data: id, encoding: .utf8) {
|
||||||
@ -365,6 +400,47 @@ func _internal_clearStorage(account: Account, peerIds: Set<EnginePeer.Id>) -> Si
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func extractMediaResourceIds(message: Message, resourceIds: inout Set<MediaResourceId>) {
|
||||||
|
for media in message.media {
|
||||||
|
if let image = media as? TelegramMediaImage {
|
||||||
|
for representation in image.representations {
|
||||||
|
resourceIds.insert(representation.resource.id)
|
||||||
|
}
|
||||||
|
} else if let file = media as? TelegramMediaFile {
|
||||||
|
for representation in file.previewRepresentations {
|
||||||
|
resourceIds.insert(representation.resource.id)
|
||||||
|
}
|
||||||
|
resourceIds.insert(file.resource.id)
|
||||||
|
} else if let webpage = media as? TelegramMediaWebpage {
|
||||||
|
if case let .Loaded(content) = webpage.content {
|
||||||
|
if let image = content.image {
|
||||||
|
for representation in image.representations {
|
||||||
|
resourceIds.insert(representation.resource.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let file = content.file {
|
||||||
|
for representation in file.previewRepresentations {
|
||||||
|
resourceIds.insert(representation.resource.id)
|
||||||
|
}
|
||||||
|
resourceIds.insert(file.resource.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let game = media as? TelegramMediaGame {
|
||||||
|
if let image = game.image {
|
||||||
|
for representation in image.representations {
|
||||||
|
resourceIds.insert(representation.resource.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let file = game.file {
|
||||||
|
for representation in file.previewRepresentations {
|
||||||
|
resourceIds.insert(representation.resource.id)
|
||||||
|
}
|
||||||
|
resourceIds.insert(file.resource.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func _internal_clearStorage(account: Account, messages: [Message]) -> Signal<Never, NoError> {
|
func _internal_clearStorage(account: Account, messages: [Message]) -> Signal<Never, NoError> {
|
||||||
let mediaBox = account.postbox.mediaBox
|
let mediaBox = account.postbox.mediaBox
|
||||||
|
|
||||||
@ -372,44 +448,7 @@ func _internal_clearStorage(account: Account, messages: [Message]) -> Signal<Nev
|
|||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async {
|
||||||
var resourceIds = Set<MediaResourceId>()
|
var resourceIds = Set<MediaResourceId>()
|
||||||
for message in messages {
|
for message in messages {
|
||||||
for media in message.media {
|
extractMediaResourceIds(message: message, resourceIds: &resourceIds)
|
||||||
if let image = media as? TelegramMediaImage {
|
|
||||||
for representation in image.representations {
|
|
||||||
resourceIds.insert(representation.resource.id)
|
|
||||||
}
|
|
||||||
} else if let file = media as? TelegramMediaFile {
|
|
||||||
for representation in file.previewRepresentations {
|
|
||||||
resourceIds.insert(representation.resource.id)
|
|
||||||
}
|
|
||||||
resourceIds.insert(file.resource.id)
|
|
||||||
} else if let webpage = media as? TelegramMediaWebpage {
|
|
||||||
if case let .Loaded(content) = webpage.content {
|
|
||||||
if let image = content.image {
|
|
||||||
for representation in image.representations {
|
|
||||||
resourceIds.insert(representation.resource.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let file = content.file {
|
|
||||||
for representation in file.previewRepresentations {
|
|
||||||
resourceIds.insert(representation.resource.id)
|
|
||||||
}
|
|
||||||
resourceIds.insert(file.resource.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let game = media as? TelegramMediaGame {
|
|
||||||
if let image = game.image {
|
|
||||||
for representation in image.representations {
|
|
||||||
resourceIds.insert(representation.resource.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let file = game.file {
|
|
||||||
for representation in file.previewRepresentations {
|
|
||||||
resourceIds.insert(representation.resource.id)
|
|
||||||
}
|
|
||||||
resourceIds.insert(file.resource.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var removeIds: [Data] = []
|
var removeIds: [Data] = []
|
||||||
|
|||||||
@ -8,7 +8,9 @@ public typealias EngineTempBoxFile = TempBoxFile
|
|||||||
|
|
||||||
public extension MediaResourceUserContentType {
|
public extension MediaResourceUserContentType {
|
||||||
init(file: TelegramMediaFile) {
|
init(file: TelegramMediaFile) {
|
||||||
if file.isMusic || file.isVoice {
|
if file.isInstantVideo || file.isVoice {
|
||||||
|
self = .audioVideoMessage
|
||||||
|
} else if file.isMusic {
|
||||||
self = .audio
|
self = .audio
|
||||||
} else if file.isSticker || file.isAnimatedSticker {
|
} else if file.isSticker || file.isAnimatedSticker {
|
||||||
self = .sticker
|
self = .sticker
|
||||||
@ -231,12 +233,12 @@ public extension TelegramEngine {
|
|||||||
return _internal_renderStorageUsageStatsMessages(account: self.account, stats: stats, categories: categories, existingMessages: existingMessages)
|
return _internal_renderStorageUsageStatsMessages(account: self.account, stats: stats, categories: categories, existingMessages: existingMessages)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func clearStorage(peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey]) -> Signal<Never, NoError> {
|
public func clearStorage(peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey], excludeMessages: [Message]) -> Signal<Never, NoError> {
|
||||||
return _internal_clearStorage(account: self.account, peerId: peerId, categories: categories)
|
return _internal_clearStorage(account: self.account, peerId: peerId, categories: categories, excludeMessages: excludeMessages)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func clearStorage(peerIds: Set<EnginePeer.Id>) -> Signal<Never, NoError> {
|
public func clearStorage(peerIds: Set<EnginePeer.Id>, includeMessages: [Message], excludeMessages: [Message]) -> Signal<Never, NoError> {
|
||||||
_internal_clearStorage(account: self.account, peerIds: peerIds)
|
_internal_clearStorage(account: self.account, peerIds: peerIds, includeMessages: includeMessages, excludeMessages: excludeMessages)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func clearStorage(messages: [Message]) -> Signal<Never, NoError> {
|
public func clearStorage(messages: [Message]) -> Signal<Never, NoError> {
|
||||||
|
|||||||
@ -22,6 +22,7 @@ private func interpolateChartData(start: PieChartComponent.ChartData, end: PieCh
|
|||||||
for i in 0 ..< result.items.count {
|
for i in 0 ..< result.items.count {
|
||||||
result.items[i].value = (1.0 - progress) * start.items[i].value + progress * end.items[i].value
|
result.items[i].value = (1.0 - progress) * start.items[i].value + progress * end.items[i].value
|
||||||
result.items[i].color = start.items[i].color.interpolateTo(end.items[i].color, fraction: progress) ?? end.items[i].color
|
result.items[i].color = start.items[i].color.interpolateTo(end.items[i].color, fraction: progress) ?? end.items[i].color
|
||||||
|
result.items[i].mergeFactor = (1.0 - progress) * start.items[i].mergeFactor + progress * end.items[i].mergeFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@ -139,12 +140,16 @@ final class PieChartComponent: Component {
|
|||||||
var displayValue: Double
|
var displayValue: Double
|
||||||
var value: Double
|
var value: Double
|
||||||
var color: UIColor
|
var color: UIColor
|
||||||
|
var mergeable: Bool
|
||||||
|
var mergeFactor: CGFloat
|
||||||
|
|
||||||
init(id: StorageUsageScreenComponent.Category, displayValue: Double, value: Double, color: UIColor) {
|
init(id: StorageUsageScreenComponent.Category, displayValue: Double, value: Double, color: UIColor, mergeable: Bool, mergeFactor: CGFloat) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.displayValue = displayValue
|
self.displayValue = displayValue
|
||||||
self.value = value
|
self.value = value
|
||||||
self.color = color
|
self.color = color
|
||||||
|
self.mergeable = mergeable
|
||||||
|
self.mergeFactor = mergeFactor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,12 +184,12 @@ final class PieChartComponent: Component {
|
|||||||
private final class ChartDataView: UIView {
|
private final class ChartDataView: UIView {
|
||||||
private(set) var theme: PresentationTheme?
|
private(set) var theme: PresentationTheme?
|
||||||
private(set) var data: ChartData?
|
private(set) var data: ChartData?
|
||||||
private(set) var selectedKey: StorageUsageScreenComponent.Category?
|
private(set) var selectedKey: AnyHashable?
|
||||||
|
|
||||||
private var currentAnimation: (start: ChartData, end: ChartData, current: ChartData, progress: CGFloat)?
|
private var currentAnimation: (start: ChartData, end: ChartData, current: ChartData, progress: CGFloat)?
|
||||||
private var animator: DisplayLinkAnimator?
|
private var animator: DisplayLinkAnimator?
|
||||||
|
|
||||||
private var labels: [StorageUsageScreenComponent.Category: ChartLabel] = [:]
|
private var labels: [AnyHashable: ChartLabel] = [:]
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@ -201,7 +206,7 @@ final class PieChartComponent: Component {
|
|||||||
self.animator?.invalidate()
|
self.animator?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setItems(theme: PresentationTheme, data: ChartData, selectedKey: StorageUsageScreenComponent.Category?, animated: Bool) {
|
func setItems(theme: PresentationTheme, data: ChartData, selectedKey: AnyHashable?, animated: Bool) {
|
||||||
let data = processChartData(data: data)
|
let data = processChartData(data: data)
|
||||||
|
|
||||||
if self.theme !== theme || self.data != data || self.selectedKey != selectedKey {
|
if self.theme !== theme || self.data != data || self.selectedKey != selectedKey {
|
||||||
@ -253,7 +258,6 @@ final class PieChartComponent: Component {
|
|||||||
let innerDiameter: CGFloat = 100.0
|
let innerDiameter: CGFloat = 100.0
|
||||||
let spacing: CGFloat = 2.0
|
let spacing: CGFloat = 2.0
|
||||||
let innerAngleSpacing: CGFloat = spacing / (innerDiameter * 0.5)
|
let innerAngleSpacing: CGFloat = spacing / (innerDiameter * 0.5)
|
||||||
//let minAngle: CGFloat = innerAngleSpacing * 2.0 + 2.0 / (innerDiameter * 0.5)
|
|
||||||
|
|
||||||
var angles: [Double] = []
|
var angles: [Double] = []
|
||||||
for i in 0 ..< data.items.count {
|
for i in 0 ..< data.items.count {
|
||||||
@ -265,13 +269,23 @@ final class PieChartComponent: Component {
|
|||||||
let diameter: CGFloat = 200.0
|
let diameter: CGFloat = 200.0
|
||||||
let reducedDiameter: CGFloat = 170.0
|
let reducedDiameter: CGFloat = 170.0
|
||||||
|
|
||||||
|
let shapeLayerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: diameter, height: diameter))
|
||||||
|
|
||||||
|
struct ItemAngleData {
|
||||||
|
var angleValue: CGFloat
|
||||||
|
var startAngle: CGFloat
|
||||||
|
var endAngle: CGFloat
|
||||||
|
}
|
||||||
|
|
||||||
|
var anglesData: [ItemAngleData] = []
|
||||||
|
|
||||||
var startAngle: CGFloat = 0.0
|
var startAngle: CGFloat = 0.0
|
||||||
for i in 0 ..< data.items.count {
|
for i in 0 ..< data.items.count {
|
||||||
let item = data.items[i]
|
let item = data.items[i]
|
||||||
|
|
||||||
let itemOuterDiameter: CGFloat
|
let itemOuterDiameter: CGFloat
|
||||||
if let selectedKey = self.selectedKey {
|
if let selectedKey = self.selectedKey {
|
||||||
if selectedKey == item.id {
|
if selectedKey == AnyHashable(item.id) {
|
||||||
itemOuterDiameter = diameter
|
itemOuterDiameter = diameter
|
||||||
} else {
|
} else {
|
||||||
itemOuterDiameter = reducedDiameter
|
itemOuterDiameter = reducedDiameter
|
||||||
@ -280,24 +294,54 @@ final class PieChartComponent: Component {
|
|||||||
itemOuterDiameter = diameter
|
itemOuterDiameter = diameter
|
||||||
}
|
}
|
||||||
|
|
||||||
let shapeLayerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: diameter, height: diameter))
|
|
||||||
|
|
||||||
let angleSpacing: CGFloat = spacing / (itemOuterDiameter * 0.5)
|
let angleSpacing: CGFloat = spacing / (itemOuterDiameter * 0.5)
|
||||||
|
|
||||||
let angleValue: CGFloat = angles[i]
|
let angleValue: CGFloat = angles[i]
|
||||||
|
|
||||||
|
var beforeSpacingFraction: CGFloat = 1.0
|
||||||
|
var afterSpacingFraction: CGFloat = 1.0
|
||||||
|
if item.mergeable {
|
||||||
|
let previousItem: ChartData.Item
|
||||||
|
if i == 0 {
|
||||||
|
previousItem = data.items[data.items.count - 1]
|
||||||
|
} else {
|
||||||
|
previousItem = data.items[i - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextItem: ChartData.Item
|
||||||
|
if i == data.items.count - 1 {
|
||||||
|
nextItem = data.items[0]
|
||||||
|
} else {
|
||||||
|
nextItem = data.items[i + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if previousItem.mergeable {
|
||||||
|
beforeSpacingFraction = item.mergeFactor * 1.0 + (1.0 - item.mergeFactor) * (-0.2)
|
||||||
|
}
|
||||||
|
if nextItem.mergeable {
|
||||||
|
afterSpacingFraction = item.mergeFactor * 1.0 + (1.0 - item.mergeFactor) * (-0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let innerStartAngle = startAngle + innerAngleSpacing * 0.5
|
let innerStartAngle = startAngle + innerAngleSpacing * 0.5
|
||||||
|
let arcInnerStartAngle = startAngle + innerAngleSpacing * 0.5 * beforeSpacingFraction
|
||||||
|
|
||||||
var innerEndAngle = startAngle + angleValue - innerAngleSpacing * 0.5
|
var innerEndAngle = startAngle + angleValue - innerAngleSpacing * 0.5
|
||||||
innerEndAngle = max(innerEndAngle, innerStartAngle)
|
innerEndAngle = max(innerEndAngle, innerStartAngle)
|
||||||
|
var arcInnerEndAngle = startAngle + angleValue - innerAngleSpacing * 0.5 * afterSpacingFraction
|
||||||
|
arcInnerEndAngle = max(arcInnerEndAngle, arcInnerStartAngle)
|
||||||
|
|
||||||
let outerStartAngle = startAngle + angleSpacing * 0.5
|
let outerStartAngle = startAngle + angleSpacing * 0.5
|
||||||
|
let arcOuterStartAngle = startAngle + angleSpacing * 0.5 * beforeSpacingFraction
|
||||||
var outerEndAngle = startAngle + angleValue - angleSpacing * 0.5
|
var outerEndAngle = startAngle + angleValue - angleSpacing * 0.5
|
||||||
outerEndAngle = max(outerEndAngle, outerStartAngle)
|
outerEndAngle = max(outerEndAngle, outerStartAngle)
|
||||||
|
var arcOuterEndAngle = startAngle + angleValue - angleSpacing * 0.5 * afterSpacingFraction
|
||||||
|
arcOuterEndAngle = max(arcOuterEndAngle, arcOuterStartAngle)
|
||||||
|
|
||||||
let path = CGMutablePath()
|
let path = CGMutablePath()
|
||||||
|
|
||||||
path.addArc(center: CGPoint(x: diameter * 0.5, y: diameter * 0.5), radius: innerDiameter * 0.5, startAngle: innerEndAngle, endAngle: innerStartAngle, clockwise: true)
|
path.addArc(center: CGPoint(x: diameter * 0.5, y: diameter * 0.5), radius: innerDiameter * 0.5, startAngle: arcInnerEndAngle, endAngle: arcInnerStartAngle, clockwise: true)
|
||||||
path.addArc(center: CGPoint(x: diameter * 0.5, y: diameter * 0.5), radius: itemOuterDiameter * 0.5, startAngle: outerStartAngle, endAngle: outerEndAngle, clockwise: false)
|
path.addArc(center: CGPoint(x: diameter * 0.5, y: diameter * 0.5), radius: itemOuterDiameter * 0.5, startAngle: arcOuterStartAngle, endAngle: arcOuterEndAngle, clockwise: false)
|
||||||
|
|
||||||
context.addPath(path)
|
context.addPath(path)
|
||||||
context.setFillColor(item.color.cgColor)
|
context.setFillColor(item.color.cgColor)
|
||||||
@ -305,7 +349,11 @@ final class PieChartComponent: Component {
|
|||||||
|
|
||||||
startAngle += angleValue
|
startAngle += angleValue
|
||||||
|
|
||||||
let fractionValue: Double = floor(item.displayValue * 100.0 * 10.0) / 10.0
|
anglesData.append(ItemAngleData(angleValue: angleValue, startAngle: innerStartAngle, endAngle: innerEndAngle))
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateItemLabel(id: AnyHashable, displayValue: Double, mergeFactor: CGFloat, angleData: ItemAngleData) {
|
||||||
|
let fractionValue: Double = floor(displayValue * 100.0 * 10.0) / 10.0
|
||||||
let fractionString: String
|
let fractionString: String
|
||||||
if fractionValue < 0.1 {
|
if fractionValue < 0.1 {
|
||||||
fractionString = "<0.1"
|
fractionString = "<0.1"
|
||||||
@ -316,16 +364,20 @@ final class PieChartComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let label: ChartLabel
|
let label: ChartLabel
|
||||||
if let current = self.labels[item.id] {
|
if let current = self.labels[id] {
|
||||||
label = current
|
label = current
|
||||||
} else {
|
} else {
|
||||||
label = ChartLabel()
|
label = ChartLabel()
|
||||||
self.labels[item.id] = label
|
self.labels[id] = label
|
||||||
}
|
}
|
||||||
let labelSize = label.update(text: "\(fractionString)%")
|
let labelSize = label.update(text: "\(fractionString)%")
|
||||||
|
|
||||||
var labelFrame: CGRect?
|
var labelFrame: CGRect?
|
||||||
|
|
||||||
|
let angleValue = angleData.angleValue
|
||||||
|
let innerStartAngle = angleData.startAngle
|
||||||
|
let innerEndAngle = angleData.endAngle
|
||||||
|
|
||||||
if angleValue >= 0.001 {
|
if angleValue >= 0.001 {
|
||||||
for step in 0 ... 20 {
|
for step in 0 ... 20 {
|
||||||
let stepFraction: CGFloat = CGFloat(step) / 20.0
|
let stepFraction: CGFloat = CGFloat(step) / 20.0
|
||||||
@ -472,7 +524,8 @@ final class PieChartComponent: Component {
|
|||||||
|
|
||||||
var labelScale = labelFrame.width / labelSize.width
|
var labelScale = labelFrame.width / labelSize.width
|
||||||
|
|
||||||
let normalAlpha: CGFloat = labelScale < 0.4 ? 0.0 : 1.0
|
var normalAlpha: CGFloat = labelScale < 0.4 ? 0.0 : 1.0
|
||||||
|
normalAlpha *= max(0.0, mergeFactor)
|
||||||
|
|
||||||
var relLabelCenter = CGPoint(
|
var relLabelCenter = CGPoint(
|
||||||
x: labelFrame.midX - shapeLayerFrame.midX,
|
x: labelFrame.midX - shapeLayerFrame.midX,
|
||||||
@ -481,7 +534,7 @@ final class PieChartComponent: Component {
|
|||||||
|
|
||||||
let labelAlpha: CGFloat
|
let labelAlpha: CGFloat
|
||||||
if let selectedKey = self.selectedKey {
|
if let selectedKey = self.selectedKey {
|
||||||
if selectedKey == item.id {
|
if selectedKey == id {
|
||||||
labelAlpha = normalAlpha
|
labelAlpha = normalAlpha
|
||||||
} else {
|
} else {
|
||||||
labelAlpha = 0.0
|
labelAlpha = 0.0
|
||||||
@ -499,7 +552,7 @@ final class PieChartComponent: Component {
|
|||||||
}
|
}
|
||||||
if labelView.alpha != labelAlpha {
|
if labelView.alpha != labelAlpha {
|
||||||
let transition: Transition
|
let transition: Transition
|
||||||
if animateIn {
|
if animateIn || "".isEmpty {
|
||||||
transition = .immediate
|
transition = .immediate
|
||||||
} else {
|
} else {
|
||||||
transition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut))
|
transition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut))
|
||||||
@ -516,6 +569,34 @@ final class PieChartComponent: Component {
|
|||||||
labelView.transform = CGAffineTransformMakeScale(labelScale, labelScale)
|
labelView.transform = CGAffineTransformMakeScale(labelScale, labelScale)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mergedItem: (displayValue: Double, angleData: ItemAngleData, mergeFactor: CGFloat)?
|
||||||
|
for i in 0 ..< data.items.count {
|
||||||
|
let item = data.items[i]
|
||||||
|
let angleData = anglesData[i]
|
||||||
|
updateItemLabel(id: item.id, displayValue: item.displayValue, mergeFactor: item.mergeFactor, angleData: angleData)
|
||||||
|
|
||||||
|
if item.mergeable {
|
||||||
|
if var currentMergedItem = mergedItem {
|
||||||
|
currentMergedItem.displayValue += item.displayValue
|
||||||
|
currentMergedItem.angleData.startAngle = min(currentMergedItem.angleData.startAngle, angleData.startAngle)
|
||||||
|
currentMergedItem.angleData.endAngle = max(currentMergedItem.angleData.endAngle, angleData.endAngle)
|
||||||
|
mergedItem = currentMergedItem
|
||||||
|
} else {
|
||||||
|
let invertedMergeFactor: CGFloat = 1.0 - max(0.0, item.mergeFactor)
|
||||||
|
mergedItem = (item.displayValue, angleData, invertedMergeFactor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let mergedItem {
|
||||||
|
updateItemLabel(id: "merged", displayValue: mergedItem.displayValue, mergeFactor: mergedItem.mergeFactor, angleData: mergedItem.angleData)
|
||||||
|
} else {
|
||||||
|
if let label = self.labels["merged"] {
|
||||||
|
self.labels.removeValue(forKey: "merged")
|
||||||
|
label.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -345,19 +345,22 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||||||
let dateTimeFormat: PresentationDateTimeFormat
|
let dateTimeFormat: PresentationDateTimeFormat
|
||||||
let insets: UIEdgeInsets
|
let insets: UIEdgeInsets
|
||||||
let items: [Item]
|
let items: [Item]
|
||||||
|
let currentPanelUpdated: (AnyHashable, Transition) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
dateTimeFormat: PresentationDateTimeFormat,
|
dateTimeFormat: PresentationDateTimeFormat,
|
||||||
insets: UIEdgeInsets,
|
insets: UIEdgeInsets,
|
||||||
items: [Item]
|
items: [Item],
|
||||||
|
currentPanelUpdated: @escaping (AnyHashable, Transition) -> Void
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
self.insets = insets
|
self.insets = insets
|
||||||
self.items = items
|
self.items = items
|
||||||
|
self.currentPanelUpdated = currentPanelUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: StorageUsagePanelContainerComponent, rhs: StorageUsagePanelContainerComponent) -> Bool {
|
static func ==(lhs: StorageUsagePanelContainerComponent, rhs: StorageUsagePanelContainerComponent) -> Bool {
|
||||||
@ -497,13 +500,6 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
self.transitionFraction = transitionFraction
|
self.transitionFraction = transitionFraction
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
// let nextKey = availablePanes[updatedIndex]
|
|
||||||
// print(transitionFraction)
|
|
||||||
//self.paneTransitionPromise.set(transitionFraction)
|
|
||||||
|
|
||||||
//self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .immediate)
|
|
||||||
//self.currentPaneUpdated?(false)
|
|
||||||
case .cancelled, .ended:
|
case .cancelled, .ended:
|
||||||
guard let component = self.component, let currentId = self.currentId else {
|
guard let component = self.component, let currentId = self.currentId else {
|
||||||
return
|
return
|
||||||
@ -533,7 +529,11 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
self.transitionFraction = 0.0
|
self.transitionFraction = 0.0
|
||||||
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
|
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
|
||||||
|
if let currentId = self.currentId {
|
||||||
|
self.state?.updated(transition: transition)
|
||||||
|
component.currentPanelUpdated(currentId, transition)
|
||||||
|
}
|
||||||
|
|
||||||
self.animatingTransition = false
|
self.animatingTransition = false
|
||||||
//self.currentPaneUpdated?(false)
|
//self.currentPaneUpdated?(false)
|
||||||
@ -615,7 +615,9 @@ final class StorageUsagePanelContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
if component.items.contains(where: { $0.id == id }) {
|
if component.items.contains(where: { $0.id == id }) {
|
||||||
self.currentId = id
|
self.currentId = id
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
|
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
|
||||||
|
self.state?.updated(transition: transition)
|
||||||
|
component.currentPanelUpdated(id, transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
|
|||||||
@ -175,6 +175,16 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
let selectedPeers: Set<EnginePeer.Id>
|
let selectedPeers: Set<EnginePeer.Id>
|
||||||
let selectedMessages: Set<EngineMessage.Id>
|
let selectedMessages: Set<EngineMessage.Id>
|
||||||
|
|
||||||
|
var isEmpty: Bool {
|
||||||
|
if !self.selectedPeers.isEmpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !self.selectedMessages.isEmpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
init(
|
init(
|
||||||
selectedPeers: Set<EnginePeer.Id>,
|
selectedPeers: Set<EnginePeer.Id>,
|
||||||
selectedMessages: Set<EngineMessage.Id>
|
selectedMessages: Set<EngineMessage.Id>
|
||||||
@ -200,17 +210,31 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func togglePeer(id: EnginePeer.Id) -> SelectionState {
|
func togglePeer(id: EnginePeer.Id, availableMessages: [EngineMessage.Id: Message]) -> SelectionState {
|
||||||
var selectedPeers = self.selectedPeers
|
var selectedPeers = self.selectedPeers
|
||||||
|
var selectedMessages = self.selectedMessages
|
||||||
|
|
||||||
if selectedPeers.contains(id) {
|
if selectedPeers.contains(id) {
|
||||||
selectedPeers.remove(id)
|
selectedPeers.remove(id)
|
||||||
|
|
||||||
|
for (messageId, _) in availableMessages {
|
||||||
|
if messageId.peerId == id {
|
||||||
|
selectedMessages.remove(messageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedPeers.insert(id)
|
selectedPeers.insert(id)
|
||||||
|
|
||||||
|
for (messageId, _) in availableMessages {
|
||||||
|
if messageId.peerId == id {
|
||||||
|
selectedMessages.insert(messageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SelectionState(
|
return SelectionState(
|
||||||
selectedPeers: selectedPeers,
|
selectedPeers: selectedPeers,
|
||||||
selectedMessages: Set()
|
selectedMessages: selectedMessages
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +247,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return SelectionState(
|
return SelectionState(
|
||||||
selectedPeers: Set(),
|
selectedPeers: self.selectedPeers,
|
||||||
selectedMessages: selectedMessages
|
selectedMessages: selectedMessages
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -287,6 +311,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
private var currentStats: AllStorageUsageStats?
|
private var currentStats: AllStorageUsageStats?
|
||||||
private var existingCategories: Set<Category> = Set()
|
private var existingCategories: Set<Category> = Set()
|
||||||
|
private var otherCategories: Set<Category> = Set()
|
||||||
|
|
||||||
private var currentMessages: [MessageId: Message] = [:]
|
private var currentMessages: [MessageId: Message] = [:]
|
||||||
private var cacheSettings: CacheStorageSettings?
|
private var cacheSettings: CacheStorageSettings?
|
||||||
@ -299,6 +324,8 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
private var selectionState: SelectionState?
|
private var selectionState: SelectionState?
|
||||||
|
|
||||||
|
private var currentSelectedPanelId: AnyHashable?
|
||||||
|
|
||||||
private var clearingDisplayTimestamp: Double?
|
private var clearingDisplayTimestamp: Double?
|
||||||
private var isClearing: Bool = false {
|
private var isClearing: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
@ -501,11 +528,22 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha)
|
animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha)
|
||||||
animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha)
|
animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha)
|
||||||
|
|
||||||
|
var buttonsMasterAlpha: CGFloat = 1.0
|
||||||
|
if let component = self.component, component.peer != nil {
|
||||||
|
buttonsMasterAlpha = 0.0
|
||||||
|
} else {
|
||||||
|
if self.currentSelectedPanelId == nil || self.currentSelectedPanelId == AnyHashable("peers") {
|
||||||
|
buttonsMasterAlpha = 1.0
|
||||||
|
} else {
|
||||||
|
buttonsMasterAlpha = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let navigationEditButtonView = self.navigationEditButton.view {
|
if let navigationEditButtonView = self.navigationEditButton.view {
|
||||||
animatedTransition.setAlpha(view: navigationEditButtonView, alpha: (self.selectionState == nil ? 1.0 : 0.0) * navigationBackgroundAlpha)
|
animatedTransition.setAlpha(view: navigationEditButtonView, alpha: (self.selectionState == nil ? 1.0 : 0.0) * buttonsMasterAlpha * navigationBackgroundAlpha)
|
||||||
}
|
}
|
||||||
if let navigationDoneButtonView = self.navigationDoneButton.view {
|
if let navigationDoneButtonView = self.navigationDoneButton.view {
|
||||||
animatedTransition.setAlpha(view: navigationDoneButtonView, alpha: (self.selectionState == nil ? 0.0 : 1.0) * navigationBackgroundAlpha)
|
animatedTransition.setAlpha(view: navigationDoneButtonView, alpha: (self.selectionState == nil ? 0.0 : 1.0) * buttonsMasterAlpha * navigationBackgroundAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
let expansionDistance: CGFloat = 32.0
|
let expansionDistance: CGFloat = 32.0
|
||||||
@ -766,7 +804,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||||
|
|
||||||
var bottomInset: CGFloat = environment.safeInsets.bottom
|
var bottomInset: CGFloat = environment.safeInsets.bottom
|
||||||
if let selectionState = self.selectionState {
|
if let selectionState = self.selectionState, !selectionState.isEmpty {
|
||||||
let selectionPanel: ComponentView<Empty>
|
let selectionPanel: ComponentView<Empty>
|
||||||
var selectionPanelTransition = transition
|
var selectionPanelTransition = transition
|
||||||
if let current = self.selectionPanel {
|
if let current = self.selectionPanel {
|
||||||
@ -779,15 +817,6 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
var selectedSize: Int64 = 0
|
var selectedSize: Int64 = 0
|
||||||
if let currentStats = self.currentStats {
|
if let currentStats = self.currentStats {
|
||||||
for peerId in selectionState.selectedPeers {
|
|
||||||
if let stats = currentStats.peers[peerId] {
|
|
||||||
let peerSize = stats.stats.categories.values.reduce(0, {
|
|
||||||
$0 + $1.size
|
|
||||||
})
|
|
||||||
selectedSize += peerSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let contextStats: StorageUsageStats
|
let contextStats: StorageUsageStats
|
||||||
if let peer = component.peer {
|
if let peer = component.peer {
|
||||||
contextStats = currentStats.peers[peer.id]?.stats ?? StorageUsageStats(categories: [:])
|
contextStats = currentStats.peers[peer.id]?.stats ?? StorageUsageStats(categories: [:])
|
||||||
@ -795,10 +824,34 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
contextStats = currentStats.totalStats
|
contextStats = currentStats.totalStats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for peerId in selectionState.selectedPeers {
|
||||||
|
if let stats = currentStats.peers[peerId] {
|
||||||
|
let peerSize = stats.stats.categories.values.reduce(0, {
|
||||||
|
$0 + $1.size
|
||||||
|
})
|
||||||
|
selectedSize += peerSize
|
||||||
|
|
||||||
|
for (messageId, _) in self.currentMessages {
|
||||||
|
if messageId.peerId == peerId {
|
||||||
|
if !selectionState.selectedMessages.contains(messageId) {
|
||||||
|
inner: for (_, category) in contextStats.categories {
|
||||||
|
if let messageSize = category.messages[messageId] {
|
||||||
|
selectedSize -= messageSize
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for messageId in selectionState.selectedMessages {
|
for messageId in selectionState.selectedMessages {
|
||||||
for (_, category) in contextStats.categories {
|
for (_, category) in contextStats.categories {
|
||||||
if let messageSize = category.messages[messageId] {
|
if let messageSize = category.messages[messageId] {
|
||||||
selectedSize += messageSize
|
if !selectionState.selectedPeers.contains(messageId.peerId) {
|
||||||
|
selectedSize += messageSize
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -846,7 +899,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
|
|
||||||
contentHeight += environment.statusBarHeight + topInset
|
contentHeight += environment.statusBarHeight + topInset
|
||||||
|
|
||||||
let chartOrder: [Category] = [
|
let allCategories: [Category] = [
|
||||||
.photos,
|
.photos,
|
||||||
.videos,
|
.videos,
|
||||||
.files,
|
.files,
|
||||||
@ -869,15 +922,8 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
self.selectedCategories.removeAll()
|
self.selectedCategories.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
var chartItems: [PieChartComponent.ChartData.Item] = []
|
|
||||||
var listCategories: [StorageCategoriesComponent.CategoryData] = []
|
var listCategories: [StorageCategoriesComponent.CategoryData] = []
|
||||||
|
|
||||||
let otherCategories: [Category] = [
|
|
||||||
.stickers,
|
|
||||||
.avatars,
|
|
||||||
.misc
|
|
||||||
]
|
|
||||||
|
|
||||||
var totalSize: Int64 = 0
|
var totalSize: Int64 = 0
|
||||||
if let currentStats = self.currentStats {
|
if let currentStats = self.currentStats {
|
||||||
let contextStats: StorageUsageStats
|
let contextStats: StorageUsageStats
|
||||||
@ -891,7 +937,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
totalSize += value.size
|
totalSize += value.size
|
||||||
}
|
}
|
||||||
|
|
||||||
for category in chartOrder {
|
for category in allCategories {
|
||||||
let mappedCategory: StorageUsageStats.CategoryKey
|
let mappedCategory: StorageUsageStats.CategoryKey
|
||||||
switch category {
|
switch category {
|
||||||
case .photos:
|
case .photos:
|
||||||
@ -924,18 +970,6 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
categoryFraction = Double(categorySize) / Double(totalSize)
|
categoryFraction = Double(categorySize) / Double(totalSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
var categoryChartFraction: CGFloat = categoryFraction
|
|
||||||
if !self.selectedCategories.isEmpty && !self.selectedCategories.contains(category) {
|
|
||||||
categoryChartFraction = 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
var chartCategoryColor = category.color
|
|
||||||
if !self.isOtherCategoryExpanded && otherCategories.contains(category) {
|
|
||||||
chartCategoryColor = Category.misc.color
|
|
||||||
}
|
|
||||||
|
|
||||||
chartItems.append(PieChartComponent.ChartData.Item(id: category, displayValue: categoryFraction, value: categoryChartFraction, color: chartCategoryColor))
|
|
||||||
|
|
||||||
if categorySize != 0 {
|
if categorySize != 0 {
|
||||||
listCategories.append(StorageCategoriesComponent.CategoryData(
|
listCategories.append(StorageCategoriesComponent.CategoryData(
|
||||||
key: category, color: category.color, title: category.title(strings: environment.strings), size: categorySize, sizeFraction: categoryFraction, isSelected: self.selectedCategories.contains(category), subcategories: []))
|
key: category, color: category.color, title: category.title(strings: environment.strings), size: categorySize, sizeFraction: categoryFraction, isSelected: self.selectedCategories.contains(category), subcategories: []))
|
||||||
@ -943,15 +977,18 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listCategories.sort(by: { $0.sizeFraction > $1.sizeFraction })
|
||||||
|
|
||||||
var otherListCategories: [StorageCategoriesComponent.CategoryData] = []
|
var otherListCategories: [StorageCategoriesComponent.CategoryData] = []
|
||||||
for listCategory in listCategories {
|
if listCategories.count > 5 {
|
||||||
if otherCategories.contains(where: { $0 == listCategory.key }) {
|
for i in (4 ..< listCategories.count).reversed() {
|
||||||
otherListCategories.append(listCategory)
|
if listCategories[i].sizeFraction < 0.04 {
|
||||||
|
otherListCategories.insert(listCategories[i], at: 0)
|
||||||
|
listCategories.remove(at: i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listCategories = listCategories.filter { item in
|
self.otherCategories = Set(otherListCategories.map(\.key))
|
||||||
return !otherCategories.contains(where: { $0 == item.key })
|
|
||||||
}
|
|
||||||
if !otherListCategories.isEmpty {
|
if !otherListCategories.isEmpty {
|
||||||
var totalOtherSize: Int64 = 0
|
var totalOtherSize: Int64 = 0
|
||||||
for listCategory in otherListCategories {
|
for listCategory in otherListCategories {
|
||||||
@ -978,27 +1015,28 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
key: Category.other, color: listColor, title: Category.other.title(strings: environment.strings), size: totalOtherSize, sizeFraction: categoryFraction, isSelected: isSelected, subcategories: otherListCategories))
|
key: Category.other, color: listColor, title: Category.other.title(strings: environment.strings), size: totalOtherSize, sizeFraction: categoryFraction, isSelected: isSelected, subcategories: otherListCategories))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.isOtherCategoryExpanded {
|
var chartItems: [PieChartComponent.ChartData.Item] = []
|
||||||
var otherSum: CGFloat = 0.0
|
for listCategory in listCategories {
|
||||||
var otherRealSum: CGFloat = 0.0
|
var categoryChartFraction: CGFloat = listCategory.sizeFraction
|
||||||
for i in 0 ..< chartItems.count {
|
if !self.selectedCategories.isEmpty && !self.selectedCategories.contains(listCategory.key) {
|
||||||
if otherCategories.contains(chartItems[i].id) {
|
categoryChartFraction = 0.0
|
||||||
var itemValue = chartItems[i].value
|
|
||||||
if itemValue > 0.00001 {
|
|
||||||
itemValue = max(itemValue, 0.01)
|
|
||||||
}
|
|
||||||
otherSum += itemValue
|
|
||||||
otherRealSum += chartItems[i].displayValue
|
|
||||||
if case .misc = chartItems[i].id {
|
|
||||||
} else {
|
|
||||||
chartItems[i].value = 0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let index = chartItems.firstIndex(where: { $0.id == .misc }) {
|
chartItems.append(PieChartComponent.ChartData.Item(id: listCategory.key, displayValue: listCategory.sizeFraction, value: categoryChartFraction, color: listCategory.color, mergeable: false, mergeFactor: 1.0))
|
||||||
chartItems[index].value = otherSum
|
}
|
||||||
chartItems[index].displayValue = otherRealSum
|
for listCategory in otherListCategories {
|
||||||
|
var categoryChartFraction: CGFloat = listCategory.sizeFraction
|
||||||
|
if !self.selectedCategories.isEmpty && !self.selectedCategories.contains(listCategory.key) {
|
||||||
|
categoryChartFraction = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let visualMergeFactor: CGFloat
|
||||||
|
if self.isOtherCategoryExpanded {
|
||||||
|
visualMergeFactor = 1.0
|
||||||
|
} else {
|
||||||
|
visualMergeFactor = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
chartItems.append(PieChartComponent.ChartData.Item(id: listCategory.key, displayValue: listCategory.sizeFraction, value: categoryChartFraction, color: self.isOtherCategoryExpanded ? listCategory.color : Category.misc.color, mergeable: true, mergeFactor: visualMergeFactor))
|
||||||
}
|
}
|
||||||
|
|
||||||
let chartData = PieChartComponent.ChartData(items: chartItems)
|
let chartData = PieChartComponent.ChartData(items: chartItems)
|
||||||
@ -1254,8 +1292,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if key == Category.other {
|
if key == Category.other {
|
||||||
var otherCategories: [Category] = [.stickers, .avatars, .misc]
|
let otherCategories = self.otherCategories.filter(self.existingCategories.contains)
|
||||||
otherCategories = otherCategories.filter(self.existingCategories.contains)
|
|
||||||
if !otherCategories.isEmpty {
|
if !otherCategories.isEmpty {
|
||||||
if otherCategories.allSatisfy(self.selectedCategories.contains) {
|
if otherCategories.allSatisfy(self.selectedCategories.contains) {
|
||||||
for item in otherCategories {
|
for item in otherCategories {
|
||||||
@ -1557,7 +1594,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let selectionState = self.selectionState {
|
if let selectionState = self.selectionState {
|
||||||
self.selectionState = selectionState.togglePeer(id: peer.id)
|
self.selectionState = selectionState.togglePeer(id: peer.id, availableMessages: self.currentMessages)
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
} else {
|
} else {
|
||||||
self.openPeer(peer: peer)
|
self.openPeer(peer: peer)
|
||||||
@ -1624,7 +1661,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
if self.selectionState == nil {
|
if self.selectionState == nil {
|
||||||
self.selectionState = SelectionState()
|
self.selectionState = SelectionState()
|
||||||
}
|
}
|
||||||
self.selectionState = self.selectionState?.togglePeer(id: peer.id)
|
self.selectionState = self.selectionState?.togglePeer(id: peer.id, availableMessages: self.currentMessages)
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
})
|
})
|
||||||
))
|
))
|
||||||
@ -1655,20 +1692,19 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
panel: AnyComponent(StorageMediaGridPanelComponent(
|
panel: AnyComponent(StorageMediaGridPanelComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
items: self.imageItems,
|
items: self.imageItems,
|
||||||
selectionState: self.selectionState,
|
selectionState: self.selectionState ?? SelectionState(),
|
||||||
action: { [weak self] messageId in
|
action: { [weak self] messageId in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let message = self.currentMessages[messageId] else {
|
guard let _ = self.currentMessages[messageId] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.selectionState == nil {
|
if self.selectionState == nil {
|
||||||
self.openMessage(message: message)
|
self.selectionState = SelectionState()
|
||||||
} else {
|
|
||||||
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
|
||||||
}
|
}
|
||||||
|
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
||||||
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
},
|
},
|
||||||
contextAction: { [weak self] messageId, containerView, sourceRect, gesture in
|
contextAction: { [weak self] messageId, containerView, sourceRect, gesture in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -1686,20 +1722,19 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
panel: AnyComponent(StorageFileListPanelComponent(
|
panel: AnyComponent(StorageFileListPanelComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
items: self.fileItems,
|
items: self.fileItems,
|
||||||
selectionState: self.selectionState,
|
selectionState: self.selectionState ?? SelectionState(),
|
||||||
action: { [weak self] messageId in
|
action: { [weak self] messageId in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let message = self.currentMessages[messageId] else {
|
guard let _ = self.currentMessages[messageId] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.selectionState == nil {
|
if self.selectionState == nil {
|
||||||
self.openMessage(message: message)
|
self.selectionState = SelectionState()
|
||||||
} else {
|
|
||||||
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
|
||||||
}
|
}
|
||||||
|
self.selectionState = self.selectionState?.toggleMessage(id: messageId)
|
||||||
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
},
|
},
|
||||||
contextAction: { [weak self] messageId, containerView, gesture in
|
contextAction: { [weak self] messageId, containerView, gesture in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -1717,7 +1752,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
panel: AnyComponent(StorageFileListPanelComponent(
|
panel: AnyComponent(StorageFileListPanelComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
items: self.musicItems,
|
items: self.musicItems,
|
||||||
selectionState: self.selectionState,
|
selectionState: self.selectionState ?? SelectionState(),
|
||||||
action: { [weak self] messageId in
|
action: { [weak self] messageId in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -1755,8 +1790,15 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
dateTimeFormat: environment.dateTimeFormat,
|
dateTimeFormat: environment.dateTimeFormat,
|
||||||
insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: bottomInset, right: environment.safeInsets.right),
|
insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: bottomInset, right: environment.safeInsets.right),
|
||||||
items: panelItems)
|
items: panelItems,
|
||||||
),
|
currentPanelUpdated: { [weak self] id, transition in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.currentSelectedPanelId = id
|
||||||
|
self.state?.updated(transition: transition)
|
||||||
|
}
|
||||||
|
)),
|
||||||
environment: {
|
environment: {
|
||||||
StorageUsagePanelContainerEnvironment(isScrollable: wasLockedAtPanels)
|
StorageUsagePanelContainerEnvironment(isScrollable: wasLockedAtPanels)
|
||||||
},
|
},
|
||||||
@ -2467,7 +2509,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
self.isClearing = true
|
self.isClearing = true
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
let _ = (component.context.engine.resources.clearStorage(peerId: peerId, categories: mappedCategories)
|
let _ = (component.context.engine.resources.clearStorage(peerId: peerId, categories: mappedCategories, excludeMessages: [])
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
guard let self, let component = self.component, let currentStats = self.currentStats else {
|
guard let self, let component = self.component, let currentStats = self.currentStats else {
|
||||||
return
|
return
|
||||||
@ -2516,7 +2558,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else if !peers.isEmpty {
|
} else if !peers.isEmpty || !messages.isEmpty {
|
||||||
self.isClearing = true
|
self.isClearing = true
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
@ -2529,7 +2571,22 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (component.context.engine.resources.clearStorage(peerIds: peers)
|
var includeMessages: [Message] = []
|
||||||
|
var excludeMessages: [Message] = []
|
||||||
|
|
||||||
|
for (id, message) in self.currentMessages {
|
||||||
|
if peers.contains(id.peerId) {
|
||||||
|
if !messages.contains(id) {
|
||||||
|
excludeMessages.append(message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if messages.contains(id) {
|
||||||
|
includeMessages.append(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (component.context.engine.resources.clearStorage(peerIds: peers, includeMessages: includeMessages, excludeMessages: excludeMessages)
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -2539,48 +2596,6 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if totalSize != 0 {
|
|
||||||
self.reportClearedStorage(size: totalSize)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else if !messages.isEmpty {
|
|
||||||
var messageItems: [Message] = []
|
|
||||||
var totalSize: Int64 = 0
|
|
||||||
|
|
||||||
let contextStats: StorageUsageStats
|
|
||||||
if let peer = component.peer {
|
|
||||||
contextStats = self.currentStats?.peers[peer.id]?.stats ?? StorageUsageStats(categories: [:])
|
|
||||||
} else {
|
|
||||||
contextStats = self.currentStats?.totalStats ?? StorageUsageStats(categories: [:])
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in messages {
|
|
||||||
if let message = self.currentMessages[id] {
|
|
||||||
messageItems.append(message)
|
|
||||||
|
|
||||||
for (_, value) in contextStats.categories {
|
|
||||||
if let size = value.messages[id] {
|
|
||||||
totalSize += size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.isClearing = true
|
|
||||||
self.state?.updated(transition: .immediate)
|
|
||||||
|
|
||||||
let _ = (component.context.engine.resources.clearStorage(messages: messageItems)
|
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.reloadStats(firstTime: false, completion: { [weak self] in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if totalSize != 0 {
|
if totalSize != 0 {
|
||||||
self.reportClearedStorage(size: totalSize)
|
self.reportClearedStorage(size: totalSize)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user