Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a possibility to delete downloaded media to free up storage #1189

Merged
merged 1 commit into from
Apr 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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