Merge branch 'mkirk/file-browser'

pull/1/head
Michael Kirk 7 years ago
commit fbbf432a48

@ -294,6 +294,7 @@
45A663C51F92EC760027B59E /* GroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A663C41F92EC760027B59E /* GroupTableViewCell.swift */; };
45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A6DAD51EBBF85500893231 /* ReminderView.swift */; };
45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; };
45B27B862037FFB400A539DF /* DebugUIFileBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B27B852037FFB400A539DF /* DebugUIFileBrowser.swift */; };
45B9EE9C200E91FB005D2F2D /* MediaDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 45B9EE9B200E91FB005D2F2D /* MediaDetailViewController.m */; };
45BB93381E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; };
45BC829D1FD9C4B400011CF3 /* ShareViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BC829C1FD9C4B400011CF3 /* ShareViewDelegate.swift */; };
@ -837,6 +838,7 @@
45A6DAD51EBBF85500893231 /* ReminderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReminderView.swift; sourceTree = "<group>"; };
45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = "<group>"; };
45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = "<group>"; };
45B27B852037FFB400A539DF /* DebugUIFileBrowser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugUIFileBrowser.swift; sourceTree = "<group>"; };
45B9EE9A200E91FB005D2F2D /* MediaDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaDetailViewController.h; sourceTree = "<group>"; };
45B9EE9B200E91FB005D2F2D /* MediaDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MediaDetailViewController.m; sourceTree = "<group>"; };
45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+featureSupport.swift"; sourceTree = "<group>"; };
@ -1474,6 +1476,7 @@
343A65941FC47D5E000477A1 /* DebugUISyncMessages.m */,
34D8C0251ED3673300188D7C /* DebugUITableViewController.h */,
34D8C0261ED3673300188D7C /* DebugUITableViewController.m */,
45B27B852037FFB400A539DF /* DebugUIFileBrowser.swift */,
);
path = DebugUI;
sourceTree = "<group>";
@ -2896,6 +2899,7 @@
34D1F0AC1F867BFC0066283D /* OWSExpirationTimerView.m in Sources */,
76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */,
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */,
45B27B862037FFB400A539DF /* DebugUIFileBrowser.swift in Sources */,
34CE88E71F2FB9A10098030F /* ProfileViewController.m in Sources */,
34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */,
34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */,

@ -12,6 +12,7 @@
#import "DebugUIPage.h"
#import "FingerprintViewController.h"
#import "HomeViewController.h"
#import "DebugUITableViewController.h"
#import "MediaDetailViewController.h"
#import "NSString+OWS.h"
#import "NotificationsManager.h"

@ -0,0 +1,378 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
class DebugUIFileBrowser: OWSTableViewController {
// MARK: Dependencies
var fileManager: FileManager {
return FileManager.default
}
// MARK: Overrides
let fileURL: URL
init(fileURL: URL) {
self.fileURL = fileURL
super.init(nibName: nil, bundle: nil)
self.contents = buildContents()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let titleLabel = UILabel()
titleLabel.text = "\(fileURL)"
titleLabel.sizeToFit()
titleLabel.textColor = UIColor.white
titleLabel.lineBreakMode = .byTruncatingHead
self.navigationItem.titleView = titleLabel
}
fileprivate func updateContents() {
self.contents = buildContents()
self.tableView.reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// In case files were added / removed in child view controller
updateContents()
}
func buildContents() -> OWSTableContents {
let contents = OWSTableContents()
let isDirectoryPtr: UnsafeMutablePointer<ObjCBool> = UnsafeMutablePointer<ObjCBool>.allocate(capacity: 1)
guard fileManager.fileExists(atPath: fileURL.path, isDirectory: isDirectoryPtr) else {
contents.title = "File not found: \(fileURL)"
return contents
}
let isDirectory: Bool = isDirectoryPtr.pointee.boolValue
if isDirectory {
var fileItems: [OWSTableItem] = []
let resourceKeys: [URLResourceKey] = [.isDirectoryKey]
let directoryContents: [URL] = {
do {
return try fileManager.contentsOfDirectory(at: fileURL,
includingPropertiesForKeys: resourceKeys)
} catch {
owsFail("\(self.logTag) contentsOfDirectory(\(fileURL) failed with error: \(error)")
return []
}
}()
fileItems = directoryContents.map { fileInDirectory in
let fileIcon: String = {
do {
guard let isDirectory = try fileInDirectory.resourceValues(forKeys: Set(resourceKeys)).isDirectory else {
owsFail("\(logTag) unable to check isDirectory for file: \(fileInDirectory)")
return ""
}
return isDirectory ? "📁 " : ""
} catch {
owsFail("\(logTag) failed to check isDirectory for file: \(fileInDirectory) with error: \(error)")
return ""
}
}()
let labelText = "\(fileIcon)\(fileInDirectory.lastPathComponent)"
return OWSTableItem.disclosureItem(withText: labelText) { [weak self] in
let subBrowser = DebugUIFileBrowser(fileURL: fileInDirectory)
self?.navigationController?.pushViewController(subBrowser, animated: true)
}
}
let filesSection = OWSTableSection(title: "Dir with \(fileItems.count) files", items: fileItems)
contents.addSection(filesSection)
} // end `if isDirectory`
let attributeItems: [OWSTableItem] = {
do {
let attributes: [FileAttributeKey: Any] = try fileManager.attributesOfItem(atPath: fileURL.path)
return attributes.map { (fileAttribute: FileAttributeKey, value: Any) in
let title = fileAttribute.rawValue.replacingOccurrences(of: "NSFile", with: "")
return OWSTableItem(title: "\(title): \(value)") {
OWSAlerts.showAlert(withTitle: title, message: "\(value)")
}
}
} catch {
owsFail("\(logTag) failed getting attributes for file at path: \(fileURL)")
return []
}
}()
let attributesSection = OWSTableSection(title: "Attributes", items: attributeItems)
contents.addSection(attributesSection)
var managementItems = [
OWSTableItem.disclosureItem(withText: "✎ Rename") { [weak self] in
guard let strongSelf = self else {
return
}
let alert = UIAlertController(title: "Rename File",
message: "Will be created in \(strongSelf.fileURL.lastPathComponent)",
preferredStyle: .alert)
alert.addAction(OWSAlerts.cancelAction)
alert.addAction(UIAlertAction(title:"Rename \(strongSelf.fileURL.lastPathComponent)", style:.default) { _ in
guard let textField = alert.textFields?.first else {
owsFail("missing text field")
return
}
guard let inputString = textField.text, inputString.count >= 4 else {
OWSAlerts.showAlert(withTitle: "new file name missing or less than 4 chars")
return
}
let newURL = strongSelf.fileURL.deletingLastPathComponent().appendingPathComponent(inputString)
do {
try strongSelf.fileManager.moveItem(at: strongSelf.fileURL, to: newURL)
Logger.debug("\(strongSelf) moved \(strongSelf.fileURL) -> \(newURL)")
strongSelf.navigationController?.popViewController(animated: true)
} catch {
owsFail("\(strongSelf) failed to move \(strongSelf.fileURL) -> \(newURL) with error: \(error)")
}
})
alert.addTextField { textField in
textField.placeholder = "New Name"
textField.text = strongSelf.fileURL.lastPathComponent
}
strongSelf.present(alert, animated: true)
},
OWSTableItem.disclosureItem(withText: "➡ Move") { [weak self] in
guard let strongSelf = self else {
return
}
let fileURL: URL = strongSelf.fileURL
let filename: String = fileURL.lastPathComponent
let oldDirectory: URL = fileURL.deletingLastPathComponent()
let alert = UIAlertController(title: "Moving File: \(filename)",
message: "Currently in: \(oldDirectory)",
preferredStyle: .alert)
alert.addAction(OWSAlerts.cancelAction)
alert.addAction(UIAlertAction(title:"Moving \(filename)", style:.default) { _ in
guard let textField = alert.textFields?.first else {
owsFail("missing text field")
return
}
guard let inputString = textField.text, inputString.count >= 4 else {
OWSAlerts.showAlert(withTitle: "new file dir missing or less than 4 chars")
return
}
let newURL = URL(fileURLWithPath: inputString).appendingPathComponent(filename)
do {
try strongSelf.fileManager.moveItem(at: fileURL, to: newURL)
Logger.debug("\(strongSelf) moved \(fileURL) -> \(newURL)")
strongSelf.navigationController?.popViewController(animated: true)
} catch {
owsFail("\(strongSelf) failed to move \(fileURL) -> \(newURL) with error: \(error)")
}
})
alert.addTextField { textField in
textField.placeholder = "New Directory"
textField.text = oldDirectory.path
}
strongSelf.present(alert, animated: true)
},
OWSTableItem.disclosureItem(withText: "❌ Delete") { [weak self] in
guard let strongSelf = self else {
return
}
OWSAlerts.showConfirmationAlert(withTitle: "Delete \(strongSelf.fileURL.path)?") { _ in
Logger.debug("\(strongSelf.logTag) deleting file at \(strongSelf.fileURL.path)")
do {
try strongSelf.fileManager.removeItem(atPath: strongSelf.fileURL.path)
strongSelf.navigationController?.popViewController(animated: true)
} catch {
owsFail("\(strongSelf.logTag) failed to remove item: \(strongSelf.fileURL) with error: \(error)")
}
}
},
OWSTableItem.disclosureItem(withText: "📋 Copy Path to Clipboard") { [weak self] in
guard let strongSelf = self else {
return
}
UIPasteboard.general.string = strongSelf.fileURL.path
let alert = UIAlertController(title: "Path Copied to Clipboard!",
message: "\(strongSelf.fileURL.path)",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Copy Filename Instead", style: .default) { _ in
UIPasteboard.general.string = strongSelf.fileURL.lastPathComponent
})
alert.addAction(UIAlertAction(title: "Dismiss", style: .default))
strongSelf.present(alert, animated: true)
},
OWSTableItem.disclosureItem(withText: "🔒 Set File Protection") { [weak self] in
guard let strongSelf = self else {
return
}
let fileURL = strongSelf.fileURL
let currentFileProtection: FileProtectionType? = {
do {
let attributes = try strongSelf.fileManager.attributesOfItem(atPath: fileURL.path)
return attributes[FileAttributeKey.protectionKey] as? FileProtectionType
} catch {
owsFail("\(strongSelf.logTag) failed to get current file protection for file: \(fileURL)")
return nil
}
}()
let actionSheet = UIAlertController(title: "Set file protection level",
message: "Currently: \(currentFileProtection?.rawValue ?? "Unknown")",
preferredStyle: .actionSheet)
let protections: [FileProtectionType] = [.none, .complete, .completeUnlessOpen, .completeUntilFirstUserAuthentication]
protections.forEach { (protection: FileProtectionType) in
actionSheet.addAction(UIAlertAction(title: "\(protection.rawValue.replacingOccurrences(of:"NSFile", with: ""))", style: .default) { (_: UIAlertAction) in
Logger.debug("\(strongSelf.logTag) chose protection: \(protection) for file: \(fileURL)")
let fileAttributes: [FileAttributeKey: Any] = [.protectionKey: protection]
do {
try strongSelf.fileManager.setAttributes(fileAttributes, ofItemAtPath: strongSelf.fileURL.path)
Logger.debug("\(strongSelf.logTag) updated file protection at path:\(fileURL.path) to: \(protection.rawValue)")
strongSelf.updateContents()
} catch {
owsFail("\(strongSelf.logTag) failed to update file protection at path:\(fileURL.path) with error: \(error)")
}
})
}
actionSheet.addAction(OWSAlerts.cancelAction)
strongSelf.present(actionSheet, animated: true)
}
]
if isDirectory {
let createFileItem = OWSTableItem.disclosureItem(withText: "📝 Create File in this Dir") { [weak self] in
guard let strongSelf = self else {
return
}
let alert = UIAlertController(title: "Name of file",
message: "Will be created in \(strongSelf.fileURL.lastPathComponent)",
preferredStyle: .alert)
alert.addAction(OWSAlerts.cancelAction)
alert.addAction(UIAlertAction(title:"Create", style:.default) { _ in
guard let textField = alert.textFields?.first else {
owsFail("missing text field")
return
}
guard let inputString = textField.text, inputString.count >= 4 else {
OWSAlerts.showAlert(withTitle: "file name missing or less than 4 chars")
return
}
let newPath = strongSelf.fileURL.appendingPathComponent(inputString).path
Logger.debug("\(strongSelf.logTag) creating file at \(newPath)")
strongSelf.fileManager.createFile(atPath: newPath, contents: nil)
strongSelf.updateContents()
})
alert.addTextField { textField in
textField.placeholder = "File Name"
}
strongSelf.present(alert, animated: true)
}
managementItems.append(createFileItem)
let createDirItem = OWSTableItem.disclosureItem(withText: "📁 Create Dir in this Dir") { [weak self] in
guard let strongSelf = self else {
return
}
let alert = UIAlertController(title: "Name of Dir",
message: "Will be created in \(strongSelf.fileURL.lastPathComponent)",
preferredStyle: .alert)
alert.addAction(OWSAlerts.cancelAction)
alert.addAction(UIAlertAction(title:"Create", style:.default) { _ in
guard let textField = alert.textFields?.first else {
owsFail("missing text field")
return
}
guard let inputString = textField.text, inputString.count >= 4 else {
OWSAlerts.showAlert(withTitle: "dir name missing or less than 4 chars")
return
}
let newPath = strongSelf.fileURL.appendingPathComponent(inputString).path
Logger.debug("\(strongSelf.logTag) creating dir at \(newPath)")
do {
try strongSelf.fileManager.createDirectory(atPath: newPath, withIntermediateDirectories: false)
strongSelf.updateContents()
} catch {
owsFail("\(strongSelf.logTag) Failed to create dir: \(newPath) with error: \(error)")
}
})
alert.addTextField { textField in
textField.placeholder = "Dir Name"
}
strongSelf.present(alert, animated: true)
}
managementItems.append(createDirItem)
} else { // if not directory
let shareItem = OWSTableItem.disclosureItem(withText: "📩 Share") { [weak self] in
guard let strongSelf = self else {
return
}
AttachmentSharing.showShareUI(for: strongSelf.fileURL)
}
managementItems.append(shareItem)
}
let fileType = isDirectory ? "Dir" : "File"
let filesSection = OWSTableSection(title: "\(fileType): \(fileURL.lastPathComponent)", items: managementItems)
contents.addSection(filesSection)
contents.title = "\(fileType): \(fileURL)"
return contents
}
}

@ -73,6 +73,7 @@ NS_ASSUME_NONNULL_BEGIN
contents.title = @"Debug: Conversation";
NSMutableArray<OWSTableItem *> *subsectionItems = [NSMutableArray new];
[subsectionItems
addObject:[self itemForSubsection:[DebugUIMessages new] viewController:viewController thread:thread]];
[subsectionItems
@ -92,6 +93,22 @@ NS_ASSUME_NONNULL_BEGIN
addObject:[self itemForSubsection:[DebugUIStress new] viewController:viewController thread:thread]];
[subsectionItems
addObject:[self itemForSubsection:[DebugUISyncMessages new] viewController:viewController thread:thread]];
OWSTableItem *sharedDataFileBrowserItem = [OWSTableItem
disclosureItemWithText:@"📁 Shared Container"
actionBlock:^{
NSURL *baseURL = [NSURL URLWithString:[OWSFileSystem appSharedDataDirectoryPath]];
DebugUIFileBrowser *fileBrowser = [[DebugUIFileBrowser alloc] initWithFileURL:baseURL];
[viewController.navigationController pushViewController:fileBrowser animated:YES];
}];
[subsectionItems addObject:sharedDataFileBrowserItem];
OWSTableItem *documentsFileBrowserItem = [OWSTableItem
disclosureItemWithText:@"📁 Document Dir"
actionBlock:^{
NSURL *baseURL = [NSURL URLWithString:[OWSFileSystem appDocumentDirectoryPath]];
DebugUIFileBrowser *fileBrowser = [[DebugUIFileBrowser alloc] initWithFileURL:baseURL];
[viewController.navigationController pushViewController:fileBrowser animated:YES];
}];
[subsectionItems addObject:documentsFileBrowserItem];
[subsectionItems addObject:[self itemForSubsection:[DebugUIMisc new] viewController:viewController thread:thread]];
[contents addSection:[OWSTableSection sectionWithTitle:@"Sections" items:subsectionItems]];

@ -155,12 +155,6 @@ void runAsyncRegistrationsForStorage(OWSStorage *storage)
DDLogInfo(@"%@ \t SHM file size: %@", self.logTag, [OWSFileSystem fileSizeOfPath:self.sharedDataDatabaseFilePath_SHM]);
DDLogInfo(@"%@ \t WAL file size: %@", self.logTag, [OWSFileSystem fileSizeOfPath:self.sharedDataDatabaseFilePath_WAL]);
// The old database location was in the Document directory,
// so protect the database files individually.
[OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath];
[OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath_SHM];
[OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath_WAL];
// Protect the entire new database directory.
[OWSFileSystem protectFileOrFolderAtPath:self.sharedDataDatabaseDirPath];
}
@ -228,6 +222,19 @@ void runAsyncRegistrationsForStorage(OWSStorage *storage)
+ (void)migrateToSharedData
{
// We protect the db files here, which is somewhat redundant with what will happen in
// `moveAppFilePath:` which also ensures file protection.
// However that method dispatches async, since it can take a while with large attachment directories.
//
// Since we only have three files here it'll be quick to do it sync, and we want to make
// sure it happens as part of the migration.
//
// FileProtection attributes move with the file, so we do it on the legacy files before moving
// them.
[OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath];
[OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath_SHM];
[OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath_WAL];
[OWSFileSystem moveAppFilePath:self.legacyDatabaseFilePath
sharedDataFilePath:self.sharedDataDatabaseFilePath
exceptionName:TSStorageManagerExceptionName_CouldNotMoveDatabaseFile];

@ -31,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
success = success && [self protectFileOrFolderAtPath:filePath];
}
DDLogInfo(@"%@ protected contents at path: %@", self.logTag, path);
return success;
}
@ -147,7 +148,11 @@ NS_ASSUME_NONNULL_BEGIN
fabs([startDate timeIntervalSinceNow]));
// Ensure all files moved have the proper data protection class.
[self protectRecursiveContentsAtPath:newFilePath];
// On large directories this can take a while, so we dispatch async
// since we're in the launch path.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self protectRecursiveContentsAtPath:newFilePath];
});
}
+ (BOOL)ensureDirectoryExists:(NSString *)dirPath

Loading…
Cancel
Save