Skip to content

Commit

Permalink
Merge pull request #1189 from stigger/storage_management
Browse files Browse the repository at this point in the history
Add a possibility to delete downloaded media to free up storage
  • Loading branch information
chrisballinger committed Apr 14, 2020
2 parents 4293466 + 930d311 commit aa6c3bb
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 6 deletions.
8 changes: 8 additions & 0 deletions ChatSecure.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
63F0CAFB1E60C1B40045359C /* OTRYapViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63F0CAFA1E60C1B40045359C /* OTRYapViewTest.swift */; };
63F614DC1BB214660083A06A /* ChatSecureModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63F614DB1BB214660083A06A /* ChatSecureModelTest.swift */; };
7CD871CB705CA365E0755104 /* libPods-ChatSecureCorePods-ChatSecureTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5179DA87B83F57EEA9589733 /* libPods-ChatSecureCorePods-ChatSecureTests.a */; };
8F56C60D22313225DC3E3E4E /* OTRStorageUsageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F56C0C0D2783FF60F293884 /* OTRStorageUsageViewController.swift */; };
8F56CEB16F4C0412C383BCF8 /* OTRStorageUsageSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F56CDC5F0CD5BA670188689 /* OTRStorageUsageSetting.swift */; };
D9108AA023F9ABDF00B1280D /* AESGCMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9108A9F23F9ABDF00B1280D /* AESGCMTests.swift */; };
D91F9EFE1ED645F100AEA62C /* FileTransferIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D91F9EFD1ED645F100AEA62C /* FileTransferIntegrationTests.swift */; };
D9365E7A1A1EB0050006434A /* torrc in Resources */ = {isa = PBXBuildFile; fileRef = D9365E791A1EB0050006434A /* torrc */; };
Expand Down Expand Up @@ -635,7 +637,9 @@
6C1E59A7F629602AA386C2B8 /* Pods-ChatSecureCorePods-ChatSecure.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChatSecureCorePods-ChatSecure.debug.xcconfig"; path = "Target Support Files/Pods-ChatSecureCorePods-ChatSecure/Pods-ChatSecureCorePods-ChatSecure.debug.xcconfig"; sourceTree = "<group>"; };
83C35A70D105953D80691D31 /* libPods-ChatSecureCorePods-ChatSecureCore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ChatSecureCorePods-ChatSecureCore.a"; sourceTree = BUILT_PRODUCTS_DIR; };
8B0F7D8477AAAE9D06628430 /* Pods-ChatSecureCore.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChatSecureCore.release.xcconfig"; path = "Target Support Files/Pods-ChatSecureCore/Pods-ChatSecureCore.release.xcconfig"; sourceTree = "<group>"; };
8F56C0C0D2783FF60F293884 /* OTRStorageUsageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTRStorageUsageViewController.swift; sourceTree = "<group>"; };
8F56C50436DA64774EBB16E3 /* OTRMessagesLoadingView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OTRMessagesLoadingView.xib; sourceTree = "<group>"; };
8F56CDC5F0CD5BA670188689 /* OTRStorageUsageSetting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTRStorageUsageSetting.swift; sourceTree = "<group>"; };
9093D0A3DB37442CFB9718F8 /* Pods-ChatSecureCorePods-ChatSecureTests.macos_release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChatSecureCorePods-ChatSecureTests.macos_release.xcconfig"; path = "Target Support Files/Pods-ChatSecureCorePods-ChatSecureTests/Pods-ChatSecureCorePods-ChatSecureTests.macos_release.xcconfig"; sourceTree = "<group>"; };
9224A5F2207E3BD800A044BF /* JoinRoomView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = JoinRoomView.xib; path = Interface/JoinRoomView.xib; sourceTree = "<group>"; };
924F67C41EA5541C00528FB6 /* MigrationInfoHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MigrationInfoHeaderView.xib; path = Interface/MigrationInfoHeaderView.xib; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1431,6 +1435,7 @@
D9C52842235CB580002B213A /* OTRCertificateDomainViewController.h */,
D9C52843235CB580002B213A /* OTRComposeViewController.m */,
D9C52844235CB580002B213A /* OTRLogListViewController.swift */,
8F56C0C0D2783FF60F293884 /* OTRStorageUsageViewController.swift */,
);
path = "View Controllers";
sourceTree = "<group>";
Expand Down Expand Up @@ -1538,6 +1543,7 @@
D9C5286C235CB580002B213A /* OTRCertificateSetting.h */,
D9C5286D235CB580002B213A /* OTRListSettingValue.m */,
D9C5286E235CB580002B213A /* OTRFeedbackSetting.m */,
8F56CDC5F0CD5BA670188689 /* OTRStorageUsageSetting.swift */,
);
path = Settings;
sourceTree = "<group>";
Expand Down Expand Up @@ -2854,6 +2860,8 @@
D9C52A17235CB580002B213A /* PushMessage.swift in Sources */,
D9C529D5235CB580002B213A /* OTRSettingsViewController.m in Sources */,
D9C52A1F235CB581002B213A /* BuddySubscriptions.swift in Sources */,
8F56C60D22313225DC3E3E4E /* OTRStorageUsageViewController.swift in Sources */,
8F56CEB16F4C0412C383BCF8 /* OTRStorageUsageSetting.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
10 changes: 9 additions & 1 deletion ChatSecureCore/Classes/Controllers/OTRMediaFileManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ - (void)setData:(NSData *)data
//#865
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
buddyUniqueId:(NSString *)buddyUniqueId
completion:(void (^)(BOOL success, NSError * _Nullable error))completion
completion:(nullable void (^)(BOOL success, NSError * _Nullable error))completion
completionQueue:(nullable dispatch_queue_t)completionQueue {
if (!completionQueue) {
completionQueue = dispatch_get_main_queue();
Expand Down Expand Up @@ -284,4 +284,12 @@ - (void) performAsyncWrite:(dispatch_block_t)block {
}
}

- (void)vacuum:(dispatch_block_t)completion {
[self performAsyncWrite:^{
[self.ioCipher vacuum];
if (completion != nil) {
dispatch_async(dispatch_get_main_queue(), completion);
}
}];
}
@end
7 changes: 5 additions & 2 deletions ChatSecureCore/Classes/Controllers/OTRSettingsManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,11 @@ - (void) populateSettings
[settingsGroups addObject:pushGroup];
}


NSArray *chatSettings = @[deletedDisconnectedConversations];
OTRStorageUsageSetting *storageUsageSetting = [[OTRStorageUsageSetting alloc] initWithTitle:STORAGE_USAGE_TITLE()
description:STORAGE_USAGE_DESCRIPTION()];
storageUsageSetting.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

NSArray *chatSettings = @[deletedDisconnectedConversations, storageUsageSetting];
OTRSettingsGroup *chatSettingsGroup = [[OTRSettingsGroup alloc] initWithTitle:CHAT_STRING() settings:chatSettings];
[settingsGroups addObject:chatSettingsGroup];

Expand Down
12 changes: 12 additions & 0 deletions ChatSecureCore/Classes/Model/Settings/OTRStorageUsageSetting.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// Created by Vyacheslav Karpukhin on 20.02.20.
// Copyright (c) 2020 Chris Ballinger. All rights reserved.
//

import Foundation

open class OTRStorageUsageSetting : OTRViewSetting {
public override init!(title newTitle: String!, description newDescription: String!) {
super.init(title: newTitle, description: newDescription, viewControllerClass: OTRStorageUsageViewController.self)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
//
// Created by Vyacheslav Karpukhin on 19.02.20.
// Copyright (c) 2020 Chris Ballinger. All rights reserved.
//

import Foundation
import OTRAssets
import MBProgressHUD

class OTRStorageUsageViewController : XLFormViewController {
private let ROOT_SECTION_TAG = "rootSection"
private let DELETE_ALL_TAG = "deleteAll"
private let NO_MEDIA_FOUND_TAG = "noMediaFound"

private let connections = OTRDatabaseManager.shared.connections

init() {
super.init(nibName: nil, bundle: nil)
self.form = self.formDescriptor()
}

required public init!(coder aDecoder: NSCoder!) {
fatalError("init(coder:) has not been implemented")
}

func formDescriptor() -> XLFormDescriptor {
let form = XLFormDescriptor(title: STORAGE_USAGE_TITLE())

let firstSection = XLFormSectionDescriptor()
firstSection.multivaluedTag = ROOT_SECTION_TAG
form.addFormSection(firstSection)

let deleteAll = XLFormRowDescriptor(tag: DELETE_ALL_TAG, rowType: XLFormRowDescriptorTypeButton, title: STORAGE_USAGE_DELETE_ALL_BUTTON())
deleteAll.action.formBlock = { row in
self.deselectFormRow(row)
self.deleteMedia()
}
firstSection.addFormRow(deleteAll)

let noMediaFound = XLFormRowDescriptor(tag: NO_MEDIA_FOUND_TAG, rowType: XLFormRowDescriptorTypeText)
noMediaFound.value = STORAGE_USAGE_NO_MEDIA_FOUND()
noMediaFound.disabled = true
firstSection.addFormRow(noMediaFound)

return form
}

override func viewDidLoad() {
super.viewDidLoad()
self.tableView.isEditing = false
DispatchQueue.global().async {
self.processAllMedia()
}
}

private func processAllMedia() {
var empty = true
connections?.read.read { (transaction: YapDatabaseReadTransaction) in
transaction.enumerateKeysAndObjects(inCollection: OTRMediaItem.collection, using: { (key, object, stop) in
if let mediaItem = object as? OTRMediaItem {
let parentMessage = mediaItem.parentMessage(with: transaction) as? OTRBaseMessage

if let threadOwner = parentMessage?.threadOwner(with: transaction),
let account = threadOwner.account(with: transaction) {
do {
let length = try OTRMediaFileManager.shared.dataLength(for: mediaItem, buddyUniqueId: threadOwner.threadIdentifier)
empty = false

let section = sectionForAccount(account)
let row = rowForThreadOwner(threadOwner, section)
let value = row.value as? Int ?? 0
row.value = value + length.intValue

DispatchQueue.main.async {
self.updateFormRow(row)
}
} catch {
return
}
}
}
})
}
if let deleteAll = self.form.formRow(withTag: DELETE_ALL_TAG),
let noMediaFound = self.form.formRow(withTag: NO_MEDIA_FOUND_TAG){
DispatchQueue.main.async {
deleteAll.hidden = empty
noMediaFound.hidden = !empty
}
}
}

private func rowForThreadOwner(_ threadOwner: OTRThreadOwner, _ section: XLFormSectionDescriptor) -> XLFormRowDescriptor {
var row = self.form.formRow(withTag: threadOwner.threadIdentifier)
if row == nil {
row = XLFormRowDescriptor(tag: threadOwner.threadIdentifier, rowType: XLFormRowDescriptorTypeInfo, title: threadOwner.threadName)
row?.valueFormatter = ByteCountFormatter()
DispatchQueue.main.sync {
section.addFormRow(row!)
}
}
return row!
}

private func sectionForAccount(_ account: OTRAccount) -> XLFormSectionDescriptor {
var section = (self.form.formSections as! [XLFormSectionDescriptor]).first { $0.title == account.displayName }
if section == nil {
section = XLFormSectionDescriptor.formSection(withTitle: account.displayName, sectionOptions: .canDelete)
DispatchQueue.main.sync {
self.form.addFormSection(section!)
}
}
return section!
}

override func formRowHasBeenRemoved(_ formRow: XLFormRowDescriptor!, at indexPath: IndexPath!) {
super.formRowHasBeenRemoved(formRow, at: indexPath)
deleteMedia(formRow.tag)
}

private func deleteMedia(_ threadIdentifier: String? = nil) {
DispatchQueue.main.async { [weak self] in
guard let strongSelf = self else { return }
MBProgressHUD.showAdded(to: strongSelf.view, animated: true)
}
connections?.write.asyncReadWrite { [weak self] (transaction: YapDatabaseReadWriteTransaction) in
guard let strongSelf = self else { return }
let mediaItemsToDelete = strongSelf.findItemsToDelete(transaction, threadIdentifier)
strongSelf.doDelete(transaction, mediaItemsToDelete)
DispatchQueue.global().async {
strongSelf.vacuumAndUpdateUI(deleteAll: threadIdentifier == nil)
}
}
}

private func findItemsToDelete(_ transaction: YapDatabaseReadWriteTransaction, _ threadIdentifier: String?) -> [OTRMediaItem] {
var mediaItemsToDelete: [OTRMediaItem] = []
transaction.enumerateKeysAndObjects(inCollection: OTRMediaItem.collection, using: { (key, object, stop) in
if let mediaItem = object as? OTRMediaItem {
let parentMessage = mediaItem.parentMessage(with: transaction) as? OTRBaseMessage
if threadIdentifier != nil,
let threadOwner = parentMessage?.threadOwner(with: transaction),
threadOwner.threadIdentifier != threadIdentifier {
return
}
if (parentMessage?.mediaItemUniqueId != nil) {
mediaItemsToDelete.append(mediaItem)
}
}
})
return mediaItemsToDelete
}

private func doDelete(_ transaction: YapDatabaseReadWriteTransaction, _ mediaItemsToDelete: [OTRMediaItem]) {
mediaItemsToDelete.forEach { mediaItem in
if let parentMessage = mediaItem.parentMessage(with: transaction) as? OTRBaseMessage,
let threadOwner = parentMessage.threadOwner(with: transaction) {

mediaItem.remove(with: transaction)

let media = OTRMediaItem.incomingItem(withFilename: mediaItem.filename, mimeType: nil)
media.parentObjectKey = parentMessage.uniqueId
media.parentObjectCollection = parentMessage.messageCollection
media.save(with: transaction)

parentMessage.mediaItemUniqueId = media.uniqueId
parentMessage.messageError = FileTransferError.automaticDownloadsDisabled
parentMessage.save(with: transaction)

OTRMediaFileManager.shared.deleteData(for: mediaItem,
buddyUniqueId: threadOwner.threadIdentifier, completion: nil, completionQueue: nil)
}
}
}

private func vacuumAndUpdateUI(deleteAll: Bool) {
OTRMediaFileManager.shared.vacuum { [weak self] in
guard let strongSelf = self else { return }
if deleteAll {
strongSelf.form.formSections.forEach {
let element = $0 as! XLFormSectionDescriptor
if element.multivaluedTag != strongSelf.ROOT_SECTION_TAG {
strongSelf.form.removeFormSection(element)
}
}
if let deleteAll = strongSelf.form.formRow(withTag: strongSelf.DELETE_ALL_TAG),
let noMediaFound = strongSelf.form.formRow(withTag: strongSelf.NO_MEDIA_FOUND_TAG){
deleteAll.hidden = true
noMediaFound.hidden = false
}
}
MBProgressHUD.hide(for: strongSelf.view, animated: true)
}
}
}
4 changes: 3 additions & 1 deletion ChatSecureCore/Public/OTRMediaFileManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ completionQueue:(nullable dispatch_queue_t)completionQueue;
//#865
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
buddyUniqueId:(NSString *)buddyUniqueId
completion:(void (^)(BOOL success, NSError * _Nullable error))completion
completion:(nullable void (^)(BOOL success, NSError * _Nullable error))completion
completionQueue:(nullable dispatch_queue_t)completionQueue;

- (nullable NSData*)dataForItem:(OTRMediaItem *)mediaItem
Expand All @@ -46,6 +46,8 @@ completionQueue:(nullable dispatch_queue_t)completionQueue;
+ (nullable NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId;
+ (nullable NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId withLeadingSlash:(BOOL)includeLeadingSlash;

- (void)vacuum:(dispatch_block_t)completion;

@property (class, nonatomic, readonly) OTRMediaFileManager *shared;

+ (instancetype)sharedInstance;
Expand Down
8 changes: 8 additions & 0 deletions OTRAssets/Strings/OTRStrings.h
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,14 @@ FOUNDATION_EXPORT NSString* SOMEONE_IS_TYPING_STRING();
FOUNDATION_EXPORT NSString* SOMEONE_STRING();
/** "Check out the source here on Github", let users know source is on Github */
FOUNDATION_EXPORT NSString* SOURCE_STRING();
/** "Delete All", Delete all media button */
FOUNDATION_EXPORT NSString* STORAGE_USAGE_DELETE_ALL_BUTTON();
/** "Manage downloaded media", Storage usage setting description */
FOUNDATION_EXPORT NSString* STORAGE_USAGE_DESCRIPTION();
/** "No downloaded media found in chats", No media found clarification */
FOUNDATION_EXPORT NSString* STORAGE_USAGE_NO_MEDIA_FOUND();
/** "Storage Usage", Storage usage setting title */
FOUNDATION_EXPORT NSString* STORAGE_USAGE_TITLE();
/** "Server", server selection section title */
FOUNDATION_EXPORT NSString* Server_String();
/** "Choose from a selection of public servers, or use your own.", server selection footer */
Expand Down
8 changes: 8 additions & 0 deletions OTRAssets/Strings/OTRStrings.m
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,14 @@
NSString* SOMEONE_STRING() { return [OTRLanguageManager translatedString:@"Someone"]; }
/** "Check out the source here on Github", let users know source is on Github */
NSString* SOURCE_STRING() { return [OTRLanguageManager translatedString:@"Check out the source here on Github"]; }
/** "Delete All", Delete all media button */
NSString* STORAGE_USAGE_DELETE_ALL_BUTTON() { return [OTRLanguageManager translatedString:@"Delete All"]; }
/** "Manage downloaded media", Storage usage setting description */
NSString* STORAGE_USAGE_DESCRIPTION() { return [OTRLanguageManager translatedString:@"Manage downloaded media"]; }
/** "No downloaded media found in chats", No media found clarification */
NSString* STORAGE_USAGE_NO_MEDIA_FOUND() { return [OTRLanguageManager translatedString:@"No downloaded media found in chats"]; }
/** "Storage Usage", Storage usage setting title */
NSString* STORAGE_USAGE_TITLE() { return [OTRLanguageManager translatedString:@"Storage Usage"]; }
/** "Server", server selection section title */
NSString* Server_String() { return [OTRLanguageManager translatedString:@"Server"]; }
/** "Choose from a selection of public servers, or use your own.", server selection footer */
Expand Down
12 changes: 12 additions & 0 deletions OTRAssets/Strings/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1245,5 +1245,17 @@
}, "ADD_FRIEND_TO_AUTO_DOWNLOAD": {
"comment": "Shown in chat view to prompt user to add friend for auto-download of group media messages.",
"string": "Is %@ your friend? Add him/her to auto-download pictures in the future."
}, "STORAGE_USAGE_TITLE": {
"comment": "Storage usage setting title",
"string": "Storage Usage"
}, "STORAGE_USAGE_DESCRIPTION": {
"comment": "Storage usage setting description",
"string": "Manage downloaded media"
}, "STORAGE_USAGE_DELETE_ALL_BUTTON": {
"comment": "Delete all media button",
"string": "Delete All"
}, "STORAGE_USAGE_NO_MEDIA_FOUND": {
"comment": "No media found clarification",
"string": "No downloaded media found in chats"
}
}
Binary file modified OTRResources/Localizations/Base.lproj/Localizable.strings
Binary file not shown.
2 changes: 1 addition & 1 deletion Submodules/IOCipher
2 changes: 1 addition & 1 deletion Submodules/libsqlfs
Submodule libsqlfs updated 2 files
+6 −1 sqlfs.c
+3 −0 sqlfs.h

0 comments on commit aa6c3bb

Please sign in to comment.