Implement the "download as" functionality properly in the iOS app

We need to catch the downloadas message already in
-[DocumentViewController
userContentController:didReceiveScriptMessage:] and use an
UIDocumentPickerViewController to let the user choose where to
download (or export) the document. The iOS-specific code in
ChildSession::downloadAs() can go away.

Change-Id: I626b9986ec6156f7e83bda02b04e65f7819f8017
Reviewed-on: https://gerrit.libreoffice.org/c/online/+/92112
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Tor Lillqvist <tml@collabora.com>
This commit is contained in:
Tor Lillqvist 2020-04-13 15:48:51 +03:00
parent 34c27a6b9b
commit 909734e261
2 changed files with 52 additions and 51 deletions

View file

@ -28,8 +28,9 @@
static DocumentViewController* theSingleton = nil; static DocumentViewController* theSingleton = nil;
@interface DocumentViewController() <WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler, UIScrollViewDelegate> { @interface DocumentViewController() <WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler, UIScrollViewDelegate, UIDocumentPickerDelegate> {
int closeNotificationPipeForForwardingThread[2]; int closeNotificationPipeForForwardingThread[2];
NSURL *downloadAsTmpURL;
} }
@end @end
@ -449,6 +450,46 @@ static IMP standardImpOfInputAccessoryView = nil;
[application openURL:url options:@{} completionHandler:nil]; [application openURL:url options:@{} completionHandler:nil];
return; return;
} }
} else if ([message.body hasPrefix:@"downloadas "]) {
NSArray<NSString*> *messageBodyItems = [message.body componentsSeparatedByString:@" "];
NSString *format = nil;
if ([messageBodyItems count] >= 2) {
for (int i = 1; i < [messageBodyItems count]; i++) {
if ([messageBodyItems[i] hasPrefix:@"format="])
format = [messageBodyItems[i] substringFromIndex:[@"format=" length]];
}
if (format == nil)
return; // Warn?
// First save it in the requested format to a temporary location. First remove any
// leftover identically named temporary file.
NSString *tmpFileName = [[[self.document->copyFileURL lastPathComponent] stringByDeletingPathExtension] stringByAppendingString:[@"." stringByAppendingString:format]];
downloadAsTmpURL = [[NSFileManager.defaultManager temporaryDirectory] URLByAppendingPathComponent:tmpFileName];
std::remove([[downloadAsTmpURL path] UTF8String]);
lok_document->saveAs([[downloadAsTmpURL absoluteString] UTF8String], [format UTF8String], nullptr);
// Then verify that it indeed was saved, and then use an
// UIDocumentPickerViewController to ask the user where to store the exported
// document.
struct stat statBuf;
if (stat([[downloadAsTmpURL path] UTF8String], &statBuf) == -1) {
LOG_ERR("Could apparently not save to '" << [[downloadAsTmpURL path] UTF8String] << "'");
} else {
UIDocumentPickerViewController *picker =
[[UIDocumentPickerViewController alloc] initWithURL:downloadAsTmpURL
inMode:UIDocumentPickerModeExportToService];
picker.delegate = self;
[self presentViewController:picker
animated:YES
completion:nil];
}
return;
}
} }
const char *buf = [message.body UTF8String]; const char *buf = [message.body UTF8String];
@ -461,6 +502,14 @@ static IMP standardImpOfInputAccessoryView = nil;
} }
} }
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
std::remove([[downloadAsTmpURL path] UTF8String]);
}
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
std::remove([[downloadAsTmpURL path] UTF8String]);
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
scrollView.pinchGestureRecognizer.enabled = NO; scrollView.pinchGestureRecognizer.enabled = NO;
} }

View file

@ -921,56 +921,8 @@ bool ChildSession::downloadAs(const char* /*buffer*/, int /*length*/, const Stri
} }
#ifdef IOS #ifdef IOS
NSURL *docURL = [NSURL URLWithString:[NSString stringWithUTF8String:getDocURL().c_str()]]; NSLog(@"We should never come here, aborting");
std::abort();
#if 0
// Experimentation
// Check if we can figure out the name of the file provider service the document is on. (No, the
// services dictionary passed to the completion handler is always empty, except for On My iPad
// and iCloud Drive.)
[NSFileManager.defaultManager
getFileProviderServicesForItemAtURL:docURL
completionHandler:^(NSDictionary<NSFileProviderServiceName,NSFileProviderService *> *services,
NSError *error) {
if (services == nil) {
std::cerr << "Could not get file provider services for " << [[docURL absoluteString] UTF8String] << "\n";
} else if ([services count] == 0) {
std::cerr << "No file provider services returned for " << [[docURL absoluteString] UTF8String] << "\n";
} else {
std::cerr << "File provider services for " << [[docURL absoluteString] UTF8String] << ":\n";
for (auto key in [services allKeys]) {
std::cerr << " " << [(NSString*)key UTF8String] << "\n";
}
}
}];
// Check if we can figure out the "ubiquitous item container" name, which apparently means the file provider extension name.
// Alas, this seems to work only for documents on iCloud Drive.
NSError *error;
auto resources = [docURL promisedItemResourceValuesForKeys:@[NSURLUbiquitousItemContainerDisplayNameKey] error:&error];
if (resources == nil) {
std::cerr << "Could not get ubiquitous container names for " << [[docURL absoluteString] UTF8String] << "\n";
} else if ([resources count] == 0) {
std::cerr << "No ubiquitous container names for " << [[docURL absoluteString] UTF8String] << "\n";
} else {
std::cerr << "Ubiquitous container names for " << [[docURL absoluteString] UTF8String] << ":\n";
for (auto name in [resources allValues]) {
std::cerr << " " << [(NSString*)name UTF8String] << "\n";
}
}
#endif
NSArray<NSString *> *pathComponents = [docURL pathComponents];
NSString *baseName = [[pathComponents lastObject] stringByDeletingPathExtension];
NSURL *documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0];
NSString *dotFormat = [@"." stringByAppendingString:[NSString stringWithUTF8String:format.c_str()]];
NSURL *exportedURL = [documentDirectory URLByAppendingPathComponent:[baseName stringByAppendingString:dotFormat]];
LOG_TRC("Exporting as " << [[exportedURL absoluteString] UTF8String]);
getLOKitDocument()->saveAs([[exportedURL absoluteString] UTF8String],
format.empty() ? nullptr : format.c_str(),
filterOptions.empty() ? nullptr : filterOptions.c_str());
#else #else
// Prevent user inputting anything funny here. // Prevent user inputting anything funny here.
// A "name" should always be a name, not a path // A "name" should always be a name, not a path