tdf#128502: Chunk of work to enable "multi-tasking" in the iOS app
Seems to not cause any serious regressions in the iOS app or in "make run", but of course I am not able to run a comprehensive check of all functionality. Change-Id: I44a0e8d60bdbc0a885db88475961575c5e95ce88 Reviewed-on: https://gerrit.libreoffice.org/c/online/+/93037 Tested-by: Jenkins Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com> Reviewed-by: Tor Lillqvist <tml@collabora.com>private/tml/ios-gen2
parent
68e5901a8d
commit
7f25109f72
|
@ -93,6 +93,7 @@ shared_sources = common/FileUtil.cpp \
|
|||
common/Session.cpp \
|
||||
common/Seccomp.cpp \
|
||||
common/MessageQueue.cpp \
|
||||
common/MobileApp.cpp \
|
||||
common/SigUtil.cpp \
|
||||
common/SpookyV2.cpp \
|
||||
common/Unit.cpp \
|
||||
|
@ -250,6 +251,7 @@ shared_headers = common/Common.hpp \
|
|||
common/Authorization.hpp \
|
||||
common/MessageQueue.hpp \
|
||||
common/Message.hpp \
|
||||
common/MobileApp.hpp \
|
||||
common/Png.hpp \
|
||||
common/Rectangle.hpp \
|
||||
common/SigUtil.hpp \
|
||||
|
|
|
@ -225,10 +225,6 @@ Java_org_libreoffice_androidlib_LOActivity_postMobileMessageNative(JNIEnv *env,
|
|||
// is saved by closing it.
|
||||
fakeSocketClose(closeNotificationPipeForForwardingThread[1]);
|
||||
|
||||
// Flag to make the inter-thread plumbing in the Online
|
||||
// bits go away quicker.
|
||||
MobileTerminationFlag = true;
|
||||
|
||||
// Close our end of the fake socket connection to the
|
||||
// ClientSession thread, so that it terminates
|
||||
fakeSocketClose(currentFakeClientFd);
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include "MobileApp.hpp"
|
||||
|
||||
#if MOBILEAPP
|
||||
|
||||
static std::map<unsigned, DocumentData> idToDocDataMap;
|
||||
static std::mutex idToDocDataMapMutex;
|
||||
|
||||
DocumentData &allocateDocumentDataForMobileAppDocId(unsigned docId)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(idToDocDataMapMutex);
|
||||
|
||||
assert(idToDocDataMap.find(docId) == idToDocDataMap.end());
|
||||
idToDocDataMap[docId] = DocumentData();
|
||||
return idToDocDataMap[docId];
|
||||
}
|
||||
|
||||
DocumentData &getDocumentDataForMobileAppDocId(unsigned docId)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(idToDocDataMapMutex);
|
||||
|
||||
assert(idToDocDataMap.find(docId) != idToDocDataMap.end());
|
||||
return idToDocDataMap[docId];
|
||||
}
|
||||
|
||||
void deallocateDocumentDataForMobileAppDocId(unsigned docId)
|
||||
{
|
||||
assert(idToDocDataMap.find(docId) != idToDocDataMap.end());
|
||||
idToDocDataMap.erase(docId);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
|
@ -0,0 +1,57 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#if MOBILEAPP
|
||||
|
||||
#include <LibreOfficeKit/LibreOfficeKit.hxx>
|
||||
|
||||
#ifdef IOS
|
||||
#import "CODocument.h"
|
||||
#endif
|
||||
|
||||
// On iOS at least we want to be able to have several documents open in the same app process.
|
||||
|
||||
// It is somewhat complicated to make sure we access the same LibreOfficeKit object for the document
|
||||
// in both the iOS-specific Objective-C++ code and in the mostly generic Online C++ code.
|
||||
|
||||
// We pass around a numeric ever-increasing document identifier that gets biumped for each document
|
||||
// the system asks the app to open.
|
||||
|
||||
// For iOS, it is the static std::atomic<unsigned> appDocIdCounter in CODocument.mm.
|
||||
|
||||
// In practice it will probably be equivalent to the DocumentBroker::DocBrokerId or the number that
|
||||
// the core SfxViewShell::GetDocId() returns, but there might be situations where multi-threading
|
||||
// and opening of several documents in sequence very quickly might cause discrepancies, so it is
|
||||
// better to usea different counter to be sure. Patches to use just one counter welcome.
|
||||
|
||||
struct DocumentData
|
||||
{
|
||||
lok::Document *loKitDocument;
|
||||
#ifdef IOS
|
||||
CODocument *coDocument;
|
||||
#endif
|
||||
|
||||
DocumentData() :
|
||||
loKitDocument(nullptr)
|
||||
#ifdef IOS
|
||||
, coDocument(nil)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
DocumentData &allocateDocumentDataForMobileAppDocId(unsigned docId);
|
||||
DocumentData &getDocumentDataForMobileAppDocId(unsigned docId);
|
||||
void deallocateDocumentDataForMobileAppDocId(unsigned docId);
|
||||
|
||||
#endif
|
|
@ -38,14 +38,10 @@
|
|||
#include "Common.hpp"
|
||||
#include "Log.hpp"
|
||||
|
||||
#ifndef IOS
|
||||
static std::atomic<bool> TerminationFlag(false);
|
||||
static std::atomic<bool> DumpGlobalState(false);
|
||||
#if MOBILEAPP
|
||||
std::atomic<bool> MobileTerminationFlag(false);
|
||||
#else
|
||||
// Mobile defines its own, which is constexpr.
|
||||
static std::atomic<bool> ShutdownRequestFlag(false);
|
||||
#endif
|
||||
|
||||
namespace SigUtil
|
||||
{
|
||||
|
@ -71,6 +67,7 @@ namespace SigUtil
|
|||
}
|
||||
#endif
|
||||
|
||||
#if !MOBILEAPP
|
||||
bool getDumpGlobalState()
|
||||
{
|
||||
return DumpGlobalState;
|
||||
|
@ -80,11 +77,7 @@ namespace SigUtil
|
|||
{
|
||||
DumpGlobalState = false;
|
||||
}
|
||||
}
|
||||
|
||||
#if !MOBILEAPP
|
||||
namespace SigUtil
|
||||
{
|
||||
/// This traps the signal-handler so we don't _Exit
|
||||
/// while dumping stack trace. It's re-entrant.
|
||||
/// Used to safely increment and decrement the signal-handler trap.
|
||||
|
@ -384,8 +377,9 @@ namespace SigUtil
|
|||
|
||||
return false;
|
||||
}
|
||||
#endif // !MOBILEAPP
|
||||
}
|
||||
|
||||
#endif // !MOBILEAPP
|
||||
#endif // !IOS
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
|
|
@ -13,13 +13,9 @@
|
|||
#include <mutex>
|
||||
#include <signal.h>
|
||||
|
||||
#if MOBILEAPP
|
||||
static constexpr bool ShutdownRequestFlag(false);
|
||||
extern std::atomic<bool> MobileTerminationFlag;
|
||||
#endif
|
||||
|
||||
namespace SigUtil
|
||||
{
|
||||
#ifndef IOS
|
||||
/// Get the flag used to commence clean shutdown.
|
||||
/// requestShutdown() is used to set the flag.
|
||||
bool getShutdownRequestFlag();
|
||||
|
@ -33,14 +29,29 @@ namespace SigUtil
|
|||
/// Only necessary in Mobile.
|
||||
void resetTerminationFlag();
|
||||
#endif
|
||||
#else
|
||||
// In the mobile apps we have no need to shut down the app.
|
||||
inline constexpr bool getShutdownRequestFlag()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline constexpr bool getTerminationFlag()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void setTerminationFlag()
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !MOBILEAPP
|
||||
/// Get the flag to dump internal state.
|
||||
bool getDumpGlobalState();
|
||||
/// Reset the flag to dump internal state.
|
||||
void resetDumpGlobalState();
|
||||
|
||||
#if !MOBILEAPP
|
||||
|
||||
/// Wait for the signal handler, if any,
|
||||
/// and prevent _Exit while collecting backtrace.
|
||||
void waitSigHandlerTrap();
|
||||
|
|
|
@ -66,6 +66,8 @@
|
|||
BEBF3EB0246EB1C800415E87 /* RequestDetails.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BEBF3EAF246EB1C800415E87 /* RequestDetails.cpp */; };
|
||||
BECD984024336DD400016117 /* device-mobile.css in Resources */ = {isa = PBXBuildFile; fileRef = BECD983E24336DD400016117 /* device-mobile.css */; };
|
||||
BECD984124336DD400016117 /* device-tablet.css in Resources */ = {isa = PBXBuildFile; fileRef = BECD983F24336DD400016117 /* device-tablet.css */; };
|
||||
BEDCC84E2452F82800FB02BD /* MobileApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BEDCC84C2452F82800FB02BD /* MobileApp.cpp */; };
|
||||
BEDCC8992456FFAD00FB02BD /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = BEDCC8982456FFAC00FB02BD /* SceneDelegate.m */; };
|
||||
BEFB1EE121C29CC70081D757 /* L10n.mm in Sources */ = {isa = PBXBuildFile; fileRef = BEFB1EE021C29CC70081D757 /* L10n.mm */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -1176,6 +1178,10 @@
|
|||
BECBD41423D9C98500DA5582 /* svddrgmt.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = svddrgmt.cxx; path = "../../ios-device/svx/source/svdraw/svddrgmt.cxx"; sourceTree = "<group>"; };
|
||||
BECD983E24336DD400016117 /* device-mobile.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = "device-mobile.css"; path = "../../../loleaflet/dist/device-mobile.css"; sourceTree = "<group>"; };
|
||||
BECD983F24336DD400016117 /* device-tablet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = "device-tablet.css"; path = "../../../loleaflet/dist/device-tablet.css"; sourceTree = "<group>"; };
|
||||
BEDCC84C2452F82800FB02BD /* MobileApp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MobileApp.cpp; sourceTree = "<group>"; };
|
||||
BEDCC84D2452F82800FB02BD /* MobileApp.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MobileApp.hpp; sourceTree = "<group>"; };
|
||||
BEDCC8972456FFAC00FB02BD /* SceneDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = "<group>"; };
|
||||
BEDCC8982456FFAC00FB02BD /* SceneDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = "<group>"; };
|
||||
BEDCC943246175E100FB02BD /* sessionlistener.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sessionlistener.cxx; path = "../../ios-device/framework/source/services/sessionlistener.cxx"; sourceTree = "<group>"; };
|
||||
BEDCC944246175E100FB02BD /* substitutepathvars.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = substitutepathvars.cxx; path = "../../ios-device/framework/source/services/substitutepathvars.cxx"; sourceTree = "<group>"; };
|
||||
BEDCC945246175E100FB02BD /* pathsettings.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = pathsettings.cxx; path = "../../ios-device/framework/source/services/pathsettings.cxx"; sourceTree = "<group>"; };
|
||||
|
@ -1885,6 +1891,8 @@
|
|||
BE58E129217F295B00249358 /* Log.hpp */,
|
||||
BE5EB5BD213FE29900E0826C /* MessageQueue.cpp */,
|
||||
BE58E12D217F295B00249358 /* MessageQueue.hpp */,
|
||||
BEDCC84C2452F82800FB02BD /* MobileApp.cpp */,
|
||||
BEDCC84D2452F82800FB02BD /* MobileApp.hpp */,
|
||||
BE58E12A217F295B00249358 /* Png.hpp */,
|
||||
BE5EB5BF213FE29900E0826C /* Protocol.cpp */,
|
||||
BE58E12E217F295B00249358 /* Protocol.hpp */,
|
||||
|
@ -2276,6 +2284,8 @@
|
|||
BE8D77312136762500AC58EA /* DocumentViewController.mm */,
|
||||
BEFB1EDF21C29CC70081D757 /* L10n.h */,
|
||||
BEFB1EE021C29CC70081D757 /* L10n.mm */,
|
||||
BEDCC8972456FFAC00FB02BD /* SceneDelegate.h */,
|
||||
BEDCC8982456FFAC00FB02BD /* SceneDelegate.m */,
|
||||
BE80E45C21B6CEF100859C97 /* TemplateSectionHeaderView.h */,
|
||||
BE80E45D21B6CEF200859C97 /* TemplateSectionHeaderView.m */,
|
||||
BE80E45621B68F5000859C97 /* TemplateCollectionViewController.h */,
|
||||
|
@ -3032,8 +3042,10 @@
|
|||
BE5EB5C3213FE29900E0826C /* Session.cpp in Sources */,
|
||||
BE5EB5D22140039100E0826C /* LOOLWSD.cpp in Sources */,
|
||||
BEFB1EE121C29CC70081D757 /* L10n.mm in Sources */,
|
||||
BEDCC8992456FFAD00FB02BD /* SceneDelegate.m in Sources */,
|
||||
BE5EB5C6213FE29900E0826C /* SigUtil.cpp in Sources */,
|
||||
BE5EB5C1213FE29900E0826C /* Log.cpp in Sources */,
|
||||
BEDCC84E2452F82800FB02BD /* MobileApp.cpp in Sources */,
|
||||
BE5EB5DA2140363100E0826C /* ios.mm in Sources */,
|
||||
BE5EB5D421400DC100E0826C /* DocumentBroker.cpp in Sources */,
|
||||
BEA28377214FFD8C00848631 /* Unit.cpp in Sources */,
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
#import "LOOLWSD.hpp"
|
||||
#import "Util.hpp"
|
||||
|
||||
static LOOLWSD *loolwsd = nullptr;
|
||||
|
||||
NSString *app_locale;
|
||||
|
||||
static void download(NSURL *source, NSURL *destination) {
|
||||
|
@ -206,6 +204,9 @@ static void updateTemplates(NSData *data, NSURLResponse *response)
|
|||
|
||||
comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(OUString::fromUtf8(OString([app_locale UTF8String])), true));
|
||||
|
||||
// This fires off a thread running the LOKit runLoop()
|
||||
runKitLoopInAThread();
|
||||
|
||||
// Look for the setting indicating the URL for a file containing a list of URLs for template
|
||||
// documents to download. If set, start a task to download it, and then to download the listed
|
||||
// templates.
|
||||
|
@ -247,26 +248,30 @@ static void updateTemplates(NSData *data, NSURLResponse *response)
|
|||
|
||||
fakeSocketSetLoggingCallback([](const std::string& line)
|
||||
{
|
||||
LOG_TRC_NOFILE(line);
|
||||
LOG_INF(line);
|
||||
});
|
||||
|
||||
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
||||
^{
|
||||
assert(loolwsd == nullptr);
|
||||
char *argv[2];
|
||||
argv[0] = strdup([[NSBundle mainBundle].executablePath UTF8String]);
|
||||
argv[1] = nullptr;
|
||||
Util::setThreadName("app");
|
||||
while (true) {
|
||||
loolwsd = new LOOLWSD();
|
||||
loolwsd->run(1, argv);
|
||||
delete loolwsd;
|
||||
LOG_TRC("One run of LOOLWSD completed");
|
||||
}
|
||||
auto loolwsd = new LOOLWSD();
|
||||
loolwsd->run(1, argv);
|
||||
|
||||
// Should never return
|
||||
assert(false);
|
||||
NSLog(@"lolwsd->run() unexpectedly returned");
|
||||
std::abort();
|
||||
});
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0)) {
|
||||
return [UISceneConfiguration configurationWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
}
|
||||
|
||||
|
@ -285,17 +290,14 @@ static void updateTemplates(NSData *data, NSURLResponse *response)
|
|||
std::_Exit(1);
|
||||
}
|
||||
|
||||
// This method is called when you use the "Share > Open in Collabora Office" functionality in the
|
||||
// Files app. Possibly also in other use cases.
|
||||
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)inputURL options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
||||
// Ensure the URL is a file URL
|
||||
if (!inputURL.isFileURL) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// If we already have a document open, close it
|
||||
if (lok_document != nullptr && [DocumentViewController singleton] != nil) {
|
||||
[[DocumentViewController singleton] bye];
|
||||
}
|
||||
|
||||
// Reveal / import the document at the URL
|
||||
DocumentBrowserViewController *documentBrowserViewController = (DocumentBrowserViewController *)self.window.rootViewController;
|
||||
[documentBrowserViewController revealDocumentAtURL:inputURL importIfNeeded:YES completion:^(NSURL * _Nullable revealedDocumentURL, NSError * _Nullable error) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
@public
|
||||
int fakeClientFd;
|
||||
NSURL *copyFileURL;
|
||||
unsigned appDocId;
|
||||
}
|
||||
|
||||
@property (weak) DocumentViewController *viewController;
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#import "KitHelper.hpp"
|
||||
#import "Log.hpp"
|
||||
#import "LOOLWSD.hpp"
|
||||
#import "MobileApp.hpp"
|
||||
#import "Protocol.hpp"
|
||||
|
||||
@implementation CODocument
|
||||
|
@ -40,6 +41,12 @@
|
|||
return [NSData dataWithContentsOfFile:[copyFileURL path] options:0 error:errorPtr];
|
||||
}
|
||||
|
||||
// We keep a running count of opening documents here. This is not necessarily in sync with the
|
||||
// DocBrokerId in DocumentBroker due to potential parallelism when opening multiple documents in
|
||||
// quick succession.
|
||||
|
||||
static std::atomic<unsigned> appDocIdCounter(1);
|
||||
|
||||
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)errorPtr {
|
||||
|
||||
// If this method is called a second time on the same CODocument object, just ignore it. This
|
||||
|
@ -59,10 +66,13 @@
|
|||
|
||||
NSURL *url = [[NSBundle mainBundle] URLForResource:@"loleaflet" withExtension:@"html"];
|
||||
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
||||
appDocId = appDocIdCounter++;
|
||||
allocateDocumentDataForMobileAppDocId(appDocId).coDocument = self;
|
||||
components.queryItems = @[ [NSURLQueryItem queryItemWithName:@"file_path" value:[copyFileURL absoluteString]],
|
||||
[NSURLQueryItem queryItemWithName:@"closebutton" value:@"1"],
|
||||
[NSURLQueryItem queryItemWithName:@"permission" value:@"edit"],
|
||||
[NSURLQueryItem queryItemWithName:@"lang" value:app_locale]
|
||||
[NSURLQueryItem queryItemWithName:@"lang" value:app_locale],
|
||||
[NSURLQueryItem queryItemWithName:@"appdocid" value:[NSString stringWithFormat:@"%u", appDocId]],
|
||||
];
|
||||
|
||||
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:components.URL];
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
@property (strong) NSURL *slideshowURL;
|
||||
|
||||
- (void)bye;
|
||||
+ (DocumentViewController*)singleton;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -22,13 +22,12 @@
|
|||
#import "FakeSocket.hpp"
|
||||
#import "LOOLWSD.hpp"
|
||||
#import "Log.hpp"
|
||||
#import "MobileApp.hpp"
|
||||
#import "SigUtil.hpp"
|
||||
#import "Util.hpp"
|
||||
|
||||
#import "DocumentViewController.h"
|
||||
|
||||
static DocumentViewController* theSingleton = nil;
|
||||
|
||||
@interface DocumentViewController() <WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler, UIScrollViewDelegate, UIDocumentPickerDelegate> {
|
||||
int closeNotificationPipeForForwardingThread[2];
|
||||
NSURL *downloadAsTmpURL;
|
||||
|
@ -79,8 +78,6 @@ static IMP standardImpOfInputAccessoryView = nil;
|
|||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
theSingleton = self;
|
||||
|
||||
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
|
||||
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
|
||||
|
||||
|
@ -317,10 +314,6 @@ static IMP standardImpOfInputAccessoryView = nil;
|
|||
// is saved by closing it.
|
||||
fakeSocketClose(self->closeNotificationPipeForForwardingThread[1]);
|
||||
|
||||
// Flag to make the inter-thread plumbing in the Online
|
||||
// bits go away quicker.
|
||||
MobileTerminationFlag = true;
|
||||
|
||||
// Close our end of the fake socket connection to the
|
||||
// ClientSession thread, so that it terminates
|
||||
fakeSocketClose(self.document->fakeClientFd);
|
||||
|
@ -348,13 +341,14 @@ static IMP standardImpOfInputAccessoryView = nil;
|
|||
assert(false);
|
||||
});
|
||||
|
||||
// First we simply send it the URL. This corresponds to the GET request with Upgrade to
|
||||
// WebSocket.
|
||||
// First we simply send the Online C++ parts the URL and the appDocId. This corresponds
|
||||
// to the GET request with Upgrade to WebSocket.
|
||||
std::string url([[self.document->copyFileURL absoluteString] UTF8String]);
|
||||
p.fd = self.document->fakeClientFd;
|
||||
p.events = POLLOUT;
|
||||
fakeSocketPoll(&p, 1, -1);
|
||||
fakeSocketWrite(self.document->fakeClientFd, url.c_str(), url.size());
|
||||
std::string message(url + " " + std::to_string(self.document->appDocId));
|
||||
fakeSocketWrite(self.document->fakeClientFd, message.c_str(), message.size());
|
||||
|
||||
return;
|
||||
} else if ([message.body isEqualToString:@"BYE"]) {
|
||||
|
@ -369,7 +363,7 @@ static IMP standardImpOfInputAccessoryView = nil;
|
|||
self.slideshowFile = Util::createRandomTmpDir() + "/slideshow.svg";
|
||||
self.slideshowURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:self.slideshowFile.c_str()] isDirectory:NO];
|
||||
|
||||
lok_document->saveAs([[self.slideshowURL absoluteString] UTF8String], "svg", nullptr);
|
||||
getDocumentDataForMobileAppDocId(self.document->appDocId).loKitDocument->saveAs([[self.slideshowURL absoluteString] UTF8String], "svg", nullptr);
|
||||
|
||||
// Add a new full-screen WebView displaying the slideshow.
|
||||
|
||||
|
@ -423,7 +417,7 @@ static IMP standardImpOfInputAccessoryView = nil;
|
|||
|
||||
std::string printFile = Util::createRandomTmpDir() + "/print.pdf";
|
||||
NSURL *printURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:printFile.c_str()] isDirectory:NO];
|
||||
lok_document->saveAs([[printURL absoluteString] UTF8String], "pdf", nullptr);
|
||||
getDocumentDataForMobileAppDocId(self.document->appDocId).loKitDocument->saveAs([[printURL absoluteString] UTF8String], "pdf", nullptr);
|
||||
|
||||
UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];
|
||||
UIPrintInfo *printInfo = [UIPrintInfo printInfo];
|
||||
|
@ -476,7 +470,7 @@ static IMP standardImpOfInputAccessoryView = nil;
|
|||
|
||||
std::remove([[downloadAsTmpURL path] UTF8String]);
|
||||
|
||||
lok_document->saveAs([[downloadAsTmpURL absoluteString] UTF8String], [format UTF8String], nullptr);
|
||||
getDocumentDataForMobileAppDocId(self.document->appDocId).loKitDocument->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
|
||||
|
@ -526,31 +520,17 @@ static IMP standardImpOfInputAccessoryView = nil;
|
|||
// Close one end of the socket pair, that will wake up the forwarding thread above
|
||||
fakeSocketClose(closeNotificationPipeForForwardingThread[0]);
|
||||
|
||||
// We can't wait for the LOOLWSD::lokit_main_mutex directly here because this is called on the
|
||||
// main queue and the main queue must be ready to execute code dispatched by the system APIs
|
||||
// used to do document saving.
|
||||
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
||||
// deallocateDocumentDataForMobileAppDocId(self.document->appDocId);
|
||||
|
||||
[[NSFileManager defaultManager] removeItemAtURL:self.document->copyFileURL error:nil];
|
||||
|
||||
// The dismissViewControllerAnimated must be done on the main queue.
|
||||
dispatch_async(dispatch_get_main_queue(),
|
||||
^{
|
||||
// Wait for lokit_main thread to exit
|
||||
std::lock_guard<std::mutex> lock(LOOLWSD::lokit_main_mutex);
|
||||
|
||||
theSingleton = nil;
|
||||
|
||||
[[NSFileManager defaultManager] removeItemAtURL:self.document->copyFileURL error:nil];
|
||||
|
||||
// And only then let the document browsing view show up again. The
|
||||
// dismissViewControllerAnimated must be done on the main queue.
|
||||
dispatch_async(dispatch_get_main_queue(),
|
||||
^{
|
||||
[self dismissDocumentViewController];
|
||||
});
|
||||
[self dismissDocumentViewController];
|
||||
});
|
||||
}
|
||||
|
||||
+ (DocumentViewController*)singleton {
|
||||
return theSingleton;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// vim:set shiftwidth=4 softtabstop=4 expandtab:
|
||||
|
|
|
@ -421,6 +421,25 @@
|
|||
<string>share/fonts/truetype/opens___.ttf</string>
|
||||
@IOSAPP_FONTS@
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<true/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>SceneDelegate</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*-
|
||||
//
|
||||
// This file is part of the LibreOffice project.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow * window;
|
||||
|
||||
@end
|
||||
|
||||
// vim:set shiftwidth=4 softtabstop=4 expandtab:
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*-
|
||||
//
|
||||
// This file is part of the LibreOffice project.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "SceneDelegate.h"
|
||||
|
||||
@implementation SceneDelegate
|
||||
|
||||
// Nothing needed so far, the window property in the .h file is enough.
|
||||
|
||||
@end
|
||||
|
||||
// vim:set shiftwidth=4 softtabstop=4 expandtab:
|
||||
|
|
@ -89,10 +89,10 @@
|
|||
#undef LT_OBJDIR
|
||||
|
||||
/* Limit the maximum number of open connections */
|
||||
#define MAX_CONNECTIONS 3
|
||||
#define MAX_CONNECTIONS 100
|
||||
|
||||
/* Limit the maximum number of open documents */
|
||||
#define MAX_DOCUMENTS 1
|
||||
#define MAX_DOCUMENTS 100
|
||||
|
||||
/* Define to 1 if this is a mobileapp (eg. Android) build. */
|
||||
#define MOBILEAPP 1
|
||||
|
|
|
@ -7,12 +7,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <LibreOfficeKit/LibreOfficeKit.hxx>
|
||||
|
||||
extern int loolwsd_server_socket_fd;
|
||||
extern lok::Document *lok_document;
|
||||
|
||||
extern LibreOfficeKit *lo_kit;
|
||||
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "ios.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
@ -18,7 +16,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
int loolwsd_server_socket_fd = -1;
|
||||
lok::Document *lok_document = nullptr;
|
||||
|
||||
LibreOfficeKit *lo_kit;
|
||||
|
||||
// vim:set shiftwidth=4 softtabstop=4 expandtab:
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <config.h>
|
||||
|
||||
#include "ChildSession.hpp"
|
||||
#include "MobileApp.hpp"
|
||||
|
||||
#include <climits>
|
||||
#include <fstream>
|
||||
|
@ -32,10 +33,6 @@
|
|||
#include <Poco/Net/AcceptCertificateHandler.h>
|
||||
#endif
|
||||
|
||||
#ifdef IOS
|
||||
#import "DocumentViewController.h"
|
||||
#endif
|
||||
|
||||
#include <common/FileUtil.hpp>
|
||||
#include <common/JsonUtil.hpp>
|
||||
#include <common/Authorization.hpp>
|
||||
|
@ -2486,11 +2483,10 @@ void ChildSession::loKitCallback(const int type, const std::string& payload)
|
|||
|
||||
if (!commandName.isEmpty() && commandName.toString() == ".uno:Save" && !success.isEmpty() && success.toString() == "true")
|
||||
{
|
||||
CODocument *document = [[DocumentViewController singleton] document];
|
||||
|
||||
CODocument *document = getDocumentDataForMobileAppDocId(_docManager->getMobileAppDocId()).coDocument;
|
||||
[document saveToURL:[document fileURL]
|
||||
forSaveOperation:UIDocumentSaveForOverwriting
|
||||
completionHandler:^(BOOL success) {
|
||||
forSaveOperation:UIDocumentSaveForOverwriting
|
||||
completionHandler:^(BOOL success) {
|
||||
LOG_TRC("ChildSession::loKitCallback() save completion handler gets " << (success?"YES":"NO"));
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -71,6 +71,8 @@ public:
|
|||
virtual bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) = 0;
|
||||
|
||||
virtual void alertAllUsers(const std::string& cmd, const std::string& kind) = 0;
|
||||
|
||||
virtual unsigned getMobileAppDocId() = 0;
|
||||
};
|
||||
|
||||
struct RecordedEvent
|
||||
|
|
209
kit/Kit.cpp
209
kit/Kit.cpp
|
@ -52,6 +52,7 @@
|
|||
|
||||
#include "ChildSession.hpp"
|
||||
#include <Common.hpp>
|
||||
#include <MobileApp.hpp>
|
||||
#include <FileUtil.hpp>
|
||||
#include "KitHelper.hpp"
|
||||
#include "Kit.hpp"
|
||||
|
@ -768,7 +769,8 @@ public:
|
|||
const std::string& docId,
|
||||
const std::string& url,
|
||||
std::shared_ptr<TileQueue> tileQueue,
|
||||
const std::shared_ptr<WebSocketHandler>& websocketHandler)
|
||||
const std::shared_ptr<WebSocketHandler>& websocketHandler,
|
||||
unsigned mobileAppDocId)
|
||||
: _loKit(loKit),
|
||||
_jailId(jailId),
|
||||
_docKey(docKey),
|
||||
|
@ -784,7 +786,8 @@ public:
|
|||
_stop(false),
|
||||
_isLoading(0),
|
||||
_editorId(-1),
|
||||
_editorChangeWarning(false)
|
||||
_editorChangeWarning(false),
|
||||
_mobileAppDocId(mobileAppDocId)
|
||||
{
|
||||
LOG_INF("Document ctor for [" << _docKey <<
|
||||
"] url [" << anonymizeUrl(_url) << "] on child [" << _jailId <<
|
||||
|
@ -809,6 +812,11 @@ public:
|
|||
{
|
||||
session.second->resetDocManager();
|
||||
}
|
||||
|
||||
#ifdef IOS
|
||||
deallocateDocumentDataForMobileAppDocId(_mobileAppDocId);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
const std::string& getUrl() const { return _url; }
|
||||
|
@ -1247,6 +1255,11 @@ public:
|
|||
alertAllUsers("errortoall: cmd=" + cmd + " kind=" + kind);
|
||||
}
|
||||
|
||||
unsigned getMobileAppDocId() override
|
||||
{
|
||||
return _mobileAppDocId;
|
||||
}
|
||||
|
||||
static void GlobalCallback(const int type, const char* p, void* data)
|
||||
{
|
||||
if (SigUtil::getTerminationFlag())
|
||||
|
@ -1465,9 +1478,6 @@ private:
|
|||
#endif
|
||||
LOG_INF("Document [" << anonymizeUrl(_url) << "] has no more views, but has " <<
|
||||
_sessions.size() << " sessions still. Destroying the document.");
|
||||
#ifdef IOS
|
||||
lok_document = nullptr;
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
_loKitDocumentForAndroidOnly.reset();
|
||||
#endif
|
||||
|
@ -1716,9 +1726,7 @@ private:
|
|||
const double totalTime = elapsed/1000.;
|
||||
LOG_DBG("Returned lokit::documentLoad(" << FileUtil::anonymizeUrl(pURL) << ") in " << totalTime << "ms.");
|
||||
#ifdef IOS
|
||||
// The iOS app (and the Android one) has max one document open at a time, so we can keep
|
||||
// a pointer to it in a global.
|
||||
lok_document = _loKitDocument.get();
|
||||
getDocumentDataForMobileAppDocId(_mobileAppDocId).loKitDocument = _loKitDocument.get();
|
||||
#endif
|
||||
if (!_loKitDocument || !_loKitDocument->get())
|
||||
{
|
||||
|
@ -2135,6 +2143,8 @@ private:
|
|||
#ifdef __ANDROID__
|
||||
friend std::shared_ptr<lok::Document> getLOKDocumentForAndroidOnly();
|
||||
#endif
|
||||
|
||||
const unsigned _mobileAppDocId;
|
||||
};
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
@ -2153,10 +2163,42 @@ class KitSocketPoll : public SocketPoll
|
|||
std::chrono::steady_clock::time_point _pollEnd;
|
||||
std::shared_ptr<Document> _document;
|
||||
|
||||
public:
|
||||
KitSocketPoll() :
|
||||
SocketPoll("kit")
|
||||
{
|
||||
#ifdef IOS
|
||||
terminationFlag = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
~KitSocketPoll()
|
||||
{
|
||||
#ifdef IOS
|
||||
std::unique_lock<std::mutex> lock(KSPollsMutex);
|
||||
std::shared_ptr<KitSocketPoll> p;
|
||||
auto i = KSPolls.begin();
|
||||
for (; i != KSPolls.end(); ++i)
|
||||
{
|
||||
p = i->lock();
|
||||
if (p && p.get() == this)
|
||||
break;
|
||||
}
|
||||
assert(i != KSPolls.end());
|
||||
KSPolls.erase(i);
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::shared_ptr<KitSocketPoll> create()
|
||||
{
|
||||
KitSocketPoll *p = new KitSocketPoll();
|
||||
auto result = std::shared_ptr<KitSocketPoll>(p);
|
||||
#ifdef IOS
|
||||
std::unique_lock<std::mutex> lock(KSPollsMutex);
|
||||
KSPolls.push_back(result);
|
||||
// KSPollsCV.notify_one();
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
// process pending message-queue events.
|
||||
|
@ -2229,8 +2271,26 @@ public:
|
|||
{
|
||||
_document = std::move(document);
|
||||
}
|
||||
|
||||
#ifdef IOS
|
||||
static std::mutex KSPollsMutex;
|
||||
// static std::condition_variable KSPollsCV;
|
||||
static std::vector<std::weak_ptr<KitSocketPoll>> KSPolls;
|
||||
|
||||
std::mutex terminationMutex;
|
||||
std::condition_variable terminationCV;
|
||||
bool terminationFlag;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef IOS
|
||||
|
||||
std::mutex KitSocketPoll::KSPollsMutex;
|
||||
// std::condition_variable KitSocketPoll::KSPollsCV;
|
||||
std::vector<std::weak_ptr<KitSocketPoll>> KitSocketPoll::KSPolls;
|
||||
|
||||
#endif
|
||||
|
||||
class KitWebSocketHandler final : public WebSocketHandler
|
||||
{
|
||||
std::shared_ptr<TileQueue> _queue;
|
||||
|
@ -2238,16 +2298,18 @@ class KitWebSocketHandler final : public WebSocketHandler
|
|||
std::shared_ptr<lok::Office> _loKit;
|
||||
std::string _jailId;
|
||||
std::shared_ptr<Document> _document;
|
||||
KitSocketPoll &_ksPoll;
|
||||
std::shared_ptr<KitSocketPoll> _ksPoll;
|
||||
const unsigned _mobileAppDocId;
|
||||
|
||||
public:
|
||||
KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit, const std::string& jailId, KitSocketPoll& ksPoll) :
|
||||
KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit, const std::string& jailId, std::shared_ptr<KitSocketPoll> ksPoll, unsigned mobileAppDocId) :
|
||||
WebSocketHandler(/* isClient = */ true, /* isMasking */ false),
|
||||
_queue(std::make_shared<TileQueue>()),
|
||||
_socketName(socketName),
|
||||
_loKit(loKit),
|
||||
_jailId(jailId),
|
||||
_ksPoll(ksPoll)
|
||||
_ksPoll(ksPoll),
|
||||
_mobileAppDocId(mobileAppDocId)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -2296,14 +2358,16 @@ protected:
|
|||
std::string url;
|
||||
URI::decode(docKey, url);
|
||||
LOG_INF("New session [" << sessionId << "] request on url [" << url << "].");
|
||||
#ifndef IOS
|
||||
Util::setThreadName("kit_" + docId);
|
||||
|
||||
#endif
|
||||
if (!_document)
|
||||
{
|
||||
_document = std::make_shared<Document>(
|
||||
_loKit, _jailId, docKey, docId, url, _queue,
|
||||
std::static_pointer_cast<WebSocketHandler>(shared_from_this()));
|
||||
_ksPoll.setDocument(_document);
|
||||
std::static_pointer_cast<WebSocketHandler>(shared_from_this()),
|
||||
_mobileAppDocId);
|
||||
_ksPoll->setDocument(_document);
|
||||
}
|
||||
|
||||
// Validate and create session.
|
||||
|
@ -2319,9 +2383,16 @@ protected:
|
|||
LOG_INF("Terminating immediately due to parent 'exit' command.");
|
||||
Log::shutdown();
|
||||
std::_Exit(EX_SOFTWARE);
|
||||
#else
|
||||
#ifdef IOS
|
||||
LOG_INF("Setting our KitSocketPoll's termination flag due to 'exit' command.");
|
||||
std::unique_lock<std::mutex> lock(_ksPoll->terminationMutex);
|
||||
_ksPoll->terminationFlag = true;
|
||||
_ksPoll->terminationCV.notify_all();
|
||||
#else
|
||||
LOG_INF("Setting TerminationFlag due to 'exit' command.");
|
||||
SigUtil::setTerminationFlag();
|
||||
#endif
|
||||
_document.reset();
|
||||
#endif
|
||||
}
|
||||
|
@ -2359,6 +2430,11 @@ protected:
|
|||
#if !MOBILEAPP
|
||||
LOG_WRN("Kit connection lost without exit arriving from wsd. Setting TerminationFlag");
|
||||
SigUtil::setTerminationFlag();
|
||||
#endif
|
||||
#ifdef IOS
|
||||
std::unique_lock<std::mutex> lock(_ksPoll->terminationMutex);
|
||||
_ksPoll->terminationFlag = true;
|
||||
_ksPoll->terminationCV.notify_all();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
@ -2371,19 +2447,62 @@ void documentViewCallback(const int type, const char* payload, void* data)
|
|||
/// Called by LOK main-loop the central location for data processing.
|
||||
int pollCallback(void* pData, int timeoutUs)
|
||||
{
|
||||
#ifndef IOS
|
||||
if (!pData)
|
||||
return 0;
|
||||
else
|
||||
return reinterpret_cast<KitSocketPoll*>(pData)->kitPoll(timeoutUs);
|
||||
#else
|
||||
std::unique_lock<std::mutex> lock(KitSocketPoll::KSPollsMutex);
|
||||
if (KitSocketPoll::KSPolls.size() == 0)
|
||||
{
|
||||
// KitSocketPoll::KSPollsCV.wait(lock);
|
||||
lock.unlock();
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(timeoutUs));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::shared_ptr<KitSocketPoll>> v;
|
||||
for (const auto &i : KitSocketPoll::KSPolls)
|
||||
{
|
||||
auto p = i.lock();
|
||||
if (p)
|
||||
v.push_back(p);
|
||||
}
|
||||
lock.unlock();
|
||||
for (const auto &p : v)
|
||||
p->kitPoll(timeoutUs);
|
||||
}
|
||||
|
||||
// We never want to exit the main loop
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Called by LOK main-loop
|
||||
void wakeCallback(void* pData)
|
||||
{
|
||||
#ifndef IOS
|
||||
if (!pData)
|
||||
return;
|
||||
else
|
||||
return reinterpret_cast<KitSocketPoll*>(pData)->wakeup();
|
||||
#else
|
||||
std::unique_lock<std::mutex> lock(KitSocketPoll::KSPollsMutex);
|
||||
if (KitSocketPoll::KSPolls.size() == 0)
|
||||
return;
|
||||
|
||||
std::vector<std::shared_ptr<KitSocketPoll>> v;
|
||||
for (const auto &i : KitSocketPoll::KSPolls)
|
||||
{
|
||||
auto p = i.lock();
|
||||
if (p)
|
||||
v.push_back(p);
|
||||
}
|
||||
lock.unlock();
|
||||
for (const auto &p : v)
|
||||
p->wakeup();
|
||||
#endif
|
||||
}
|
||||
|
||||
void setupKitEnvironment()
|
||||
|
@ -2435,10 +2554,9 @@ void lokit_main(
|
|||
bool queryVersion,
|
||||
bool displayVersion,
|
||||
#else
|
||||
const std::string& documentUri,
|
||||
int docBrokerSocket,
|
||||
#endif
|
||||
size_t spareKitId
|
||||
size_t numericIdentifier
|
||||
)
|
||||
{
|
||||
#if !MOBILEAPP
|
||||
|
@ -2448,7 +2566,7 @@ void lokit_main(
|
|||
SigUtil::setTerminationSignals();
|
||||
#endif
|
||||
|
||||
Util::setThreadName("kit_spare_" + Util::encodeId(spareKitId, 3));
|
||||
Util::setThreadName("kit_spare_" + Util::encodeId(numericIdentifier, 3));
|
||||
|
||||
// Reinitialize logging when forked.
|
||||
const bool logToFile = std::getenv("LOOL_LOGFILE");
|
||||
|
@ -2497,8 +2615,6 @@ void lokit_main(
|
|||
std::shared_ptr<lok::Office> loKit;
|
||||
Path jailPath;
|
||||
ChildSession::NoCapsForKit = noCapabilities;
|
||||
#else
|
||||
AnonymizeUserData = false;
|
||||
#endif // MOBILEAPP
|
||||
|
||||
try
|
||||
|
@ -2699,8 +2815,11 @@ void lokit_main(
|
|||
|
||||
#else // MOBILEAPP
|
||||
|
||||
// was not done by the preload
|
||||
#ifndef IOS
|
||||
// Was not done by the preload.
|
||||
// For iOS we call it in -[AppDelegate application: didFinishLaunchingWithOptions:]
|
||||
setupKitEnvironment();
|
||||
#endif
|
||||
|
||||
#if defined(__linux) && !defined(__ANDROID__)
|
||||
Poco::URI userInstallationURI("file", LO_PATH);
|
||||
|
@ -2727,16 +2846,16 @@ void lokit_main(
|
|||
|
||||
#endif // MOBILEAPP
|
||||
|
||||
KitSocketPoll mainKit;
|
||||
mainKit.runOnClientThread(); // We will do the polling on this thread.
|
||||
auto mainKit = KitSocketPoll::create();
|
||||
mainKit->runOnClientThread(); // We will do the polling on this thread.
|
||||
|
||||
std::shared_ptr<KitWebSocketHandler> websocketHandler =
|
||||
std::make_shared<KitWebSocketHandler>("child_ws", loKit, jailId, mainKit);
|
||||
std::make_shared<KitWebSocketHandler>("child_ws", loKit, jailId, mainKit, numericIdentifier);
|
||||
|
||||
#if !MOBILEAPP
|
||||
mainKit.insertNewUnixSocket(MasterLocation, pathAndQuery, websocketHandler, ProcSMapsFile);
|
||||
mainKit->insertNewUnixSocket(MasterLocation, pathAndQuery, websocketHandler, ProcSMapsFile);
|
||||
#else
|
||||
mainKit.insertNewFakeSocket(docBrokerSocket, websocketHandler);
|
||||
mainKit->insertNewFakeSocket(docBrokerSocket, websocketHandler);
|
||||
#endif
|
||||
|
||||
LOG_INF("New kit client websocket inserted.");
|
||||
|
@ -2749,6 +2868,7 @@ void lokit_main(
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifndef IOS
|
||||
if (!LIBREOFFICEKIT_HAS(kit, runLoop))
|
||||
{
|
||||
LOG_ERR("Kit is missing Unipoll API");
|
||||
|
@ -2758,7 +2878,7 @@ void lokit_main(
|
|||
|
||||
LOG_INF("Kit unipoll loop run");
|
||||
|
||||
loKit->runLoop(pollCallback, wakeCallback, &mainKit);
|
||||
loKit->runLoop(pollCallback, wakeCallback, mainKit.get());
|
||||
|
||||
LOG_INF("Kit unipoll loop run terminated.");
|
||||
|
||||
|
@ -2772,6 +2892,11 @@ void lokit_main(
|
|||
|
||||
// Let forkit handle the jail cleanup.
|
||||
#endif
|
||||
|
||||
#else // IOS
|
||||
std::unique_lock<std::mutex> lock(mainKit->terminationMutex);
|
||||
mainKit->terminationCV.wait(lock,[&]{ return mainKit->terminationFlag; } );
|
||||
#endif // !IOS
|
||||
}
|
||||
catch (const Exception& exc)
|
||||
{
|
||||
|
@ -2793,7 +2918,35 @@ void lokit_main(
|
|||
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef IOS
|
||||
|
||||
// In the iOS app we can have several documents open in the app process at the same time, thus
|
||||
// several lokit_main() functions running at the same time. We want just one LO main loop, though,
|
||||
// so we start it separately in its own thread.
|
||||
|
||||
void runKitLoopInAThread()
|
||||
{
|
||||
std::thread([&]
|
||||
{
|
||||
Util::setThreadName("lokit_runloop");
|
||||
|
||||
std::shared_ptr<lok::Office> loKit = std::make_shared<lok::Office>(lo_kit);
|
||||
int dummy;
|
||||
loKit->runLoop(pollCallback, wakeCallback, &dummy);
|
||||
|
||||
// Should never return
|
||||
assert(false);
|
||||
|
||||
NSLog(@"loKit->runLoop() unexpectedly returned");
|
||||
|
||||
std::abort();
|
||||
}).detach();
|
||||
}
|
||||
|
||||
#endif // IOS
|
||||
|
||||
#endif // !BUILDING_TESTS
|
||||
|
||||
std::string anonymizeUrl(const std::string& url)
|
||||
{
|
||||
|
|
|
@ -37,12 +37,15 @@ void lokit_main(
|
|||
bool queryVersionInfo,
|
||||
bool displayVersion,
|
||||
#else
|
||||
const std::string& documentUri,
|
||||
int docBrokerSocket,
|
||||
#endif
|
||||
size_t spareKitId
|
||||
size_t numericIdentifier
|
||||
);
|
||||
|
||||
#ifdef IOS
|
||||
void runKitLoopInAThread();
|
||||
#endif
|
||||
|
||||
/// We need to get several env. vars right
|
||||
void setupKitEnvironment();
|
||||
|
||||
|
|
|
@ -257,6 +257,11 @@ static bool checkForPoll(std::vector<FakeSocketPair>& fds, struct pollfd *pollfd
|
|||
retval = true;
|
||||
}
|
||||
}
|
||||
if (fds[pollfds[i].fd/2].shutdown[N])
|
||||
{
|
||||
pollfds[i].revents |= POLLHUP;
|
||||
retval = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
|
|
|
@ -653,6 +653,11 @@ public:
|
|||
void alertAllUsers(const std::string& /*cmd*/, const std::string& /*kind*/) override
|
||||
{
|
||||
}
|
||||
|
||||
unsigned getMobileAppDocId() override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
void WhiteBoxTests::testEmptyCellCursor()
|
||||
|
|
|
@ -153,14 +153,6 @@ public:
|
|||
|
||||
bool continuePolling() override
|
||||
{
|
||||
#if MOBILEAPP
|
||||
if (MobileTerminationFlag)
|
||||
{
|
||||
LOG_TRC("Noticed MobileTerminationFlag.");
|
||||
MobileTerminationFlag = false;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return TerminatingPoll::continuePolling();
|
||||
}
|
||||
|
||||
|
@ -176,7 +168,8 @@ std::atomic<unsigned> DocumentBroker::DocBrokerId(1);
|
|||
DocumentBroker::DocumentBroker(ChildType type,
|
||||
const std::string& uri,
|
||||
const Poco::URI& uriPublic,
|
||||
const std::string& docKey) :
|
||||
const std::string& docKey,
|
||||
unsigned mobileAppDocId) :
|
||||
_limitLifeSeconds(0),
|
||||
_uriOrig(uri),
|
||||
_type(type),
|
||||
|
@ -200,11 +193,16 @@ DocumentBroker::DocumentBroker(ChildType type,
|
|||
_lockCtx(new LockContext()),
|
||||
_tileVersion(0),
|
||||
_debugRenderedTileCount(0),
|
||||
_wopiLoadDuration(0)
|
||||
_wopiLoadDuration(0),
|
||||
_mobileAppDocId(mobileAppDocId)
|
||||
{
|
||||
assert(!_docKey.empty());
|
||||
assert(!LOOLWSD::ChildRoot.empty());
|
||||
|
||||
#ifdef IOS
|
||||
assert(_mobileAppDocId > 0);
|
||||
#endif
|
||||
|
||||
LOG_INF("DocumentBroker [" << LOOLWSD::anonymizeUrl(_uriPublic.toString()) <<
|
||||
"] created with docKey [" << _docKey << ']');
|
||||
}
|
||||
|
@ -242,7 +240,7 @@ void DocumentBroker::pollThread()
|
|||
do
|
||||
{
|
||||
static const int timeoutMs = COMMAND_TIMEOUT_MS * 5;
|
||||
_childProcess = getNewChild_Blocks(getPublicUri().getPath());
|
||||
_childProcess = getNewChild_Blocks();
|
||||
if (_childProcess ||
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
|
||||
_threadStart).count() > timeoutMs)
|
||||
|
@ -253,7 +251,8 @@ void DocumentBroker::pollThread()
|
|||
}
|
||||
while (!_stop && _poll->continuePolling() && !SigUtil::getTerminationFlag() && !SigUtil::getShutdownRequestFlag());
|
||||
#else
|
||||
_childProcess = getNewChild_Blocks(getPublicUri().getPath());
|
||||
assert(_mobileAppDocId > 0);
|
||||
_childProcess = getNewChild_Blocks(_mobileAppDocId);
|
||||
#endif
|
||||
|
||||
if (!_childProcess)
|
||||
|
|
|
@ -128,11 +128,11 @@ public:
|
|||
/// Dummy document broker that is marked to destroy.
|
||||
DocumentBroker();
|
||||
|
||||
/// Construct DocumentBroker with URI, docKey, and root path.
|
||||
DocumentBroker(ChildType type,
|
||||
const std::string& uri,
|
||||
const Poco::URI& uriPublic,
|
||||
const std::string& docKey);
|
||||
const std::string& docKey,
|
||||
unsigned mobileAppDocId = 0);
|
||||
|
||||
virtual ~DocumentBroker();
|
||||
|
||||
|
@ -439,6 +439,9 @@ private:
|
|||
|
||||
/// Unique DocBroker ID for tracing and debugging.
|
||||
static std::atomic<unsigned> DocBrokerId;
|
||||
|
||||
// Relevant only in the mobile apps
|
||||
const unsigned _mobileAppDocId;
|
||||
};
|
||||
|
||||
#if !MOBILEAPP
|
||||
|
|
|
@ -119,6 +119,7 @@ using Poco::Net::PartHandler;
|
|||
# include <Kit.hpp>
|
||||
#endif
|
||||
#include <Log.hpp>
|
||||
#include <MobileApp.hpp>
|
||||
#include <Protocol.hpp>
|
||||
#include <Session.hpp>
|
||||
#if ENABLE_SSL
|
||||
|
@ -486,17 +487,20 @@ static size_t addNewChild(const std::shared_ptr<ChildProcess>& child)
|
|||
}
|
||||
|
||||
#if MOBILEAPP
|
||||
#ifndef IOS
|
||||
std::mutex LOOLWSD::lokit_main_mutex;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
std::shared_ptr<ChildProcess> getNewChild_Blocks(const std::string& uri)
|
||||
std::shared_ptr<ChildProcess> getNewChild_Blocks(unsigned mobileAppDocId)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(NewChildrenMutex);
|
||||
|
||||
const auto startTime = std::chrono::steady_clock::now();
|
||||
|
||||
#if !MOBILEAPP
|
||||
(void)uri;
|
||||
(void) mobileAppDocId;
|
||||
|
||||
LOG_DBG("getNewChild: Rebalancing children.");
|
||||
int numPreSpawn = LOOLWSD::NumPreSpawnedChildren;
|
||||
++numPreSpawn; // Replace the one we'll dispatch just now.
|
||||
|
@ -519,12 +523,15 @@ std::shared_ptr<ChildProcess> getNewChild_Blocks(const std::string& uri)
|
|||
|
||||
std::thread([&]
|
||||
{
|
||||
std::lock_guard<std::mutex> lokit_main_lock(LOOLWSD::lokit_main_mutex);
|
||||
#ifndef IOS
|
||||
std::lock_guard<std::mutex> lock(LOOLWSD::lokit_main_mutex);
|
||||
Util::setThreadName("lokit_main");
|
||||
|
||||
// Ugly to have that static global, otoh we know there is just one LOOLWSD
|
||||
// object. (Even in real Online.)
|
||||
lokit_main(uri, LOOLWSD::prisonerServerSocketFD, 1);
|
||||
#else
|
||||
Util::setThreadName("lokit_main_" + Util::encodeId(mobileAppDocId, 3));
|
||||
#endif
|
||||
// Ugly to have that static global LOOLWSD::prisonerServerSocketFD, Otoh we know
|
||||
// there is just one LOOLWSD object. (Even in real Online.)
|
||||
lokit_main(LOOLWSD::prisonerServerSocketFD, mobileAppDocId);
|
||||
}).detach();
|
||||
#endif
|
||||
|
||||
|
@ -1853,7 +1860,8 @@ static std::shared_ptr<DocumentBroker>
|
|||
const std::string& uri,
|
||||
const std::string& docKey,
|
||||
const std::string& id,
|
||||
const Poco::URI& uriPublic)
|
||||
const Poco::URI& uriPublic,
|
||||
unsigned mobileAppDocId = 0)
|
||||
{
|
||||
LOG_INF("Find or create DocBroker for docKey [" << docKey <<
|
||||
"] for session [" << id << "] on url [" << LOOLWSD::anonymizeUrl(uriPublic.toString()) << "].");
|
||||
|
@ -1925,7 +1933,7 @@ static std::shared_ptr<DocumentBroker>
|
|||
|
||||
// Set the one we just created.
|
||||
LOG_DBG("New DocumentBroker for docKey [" << docKey << "].");
|
||||
docBroker = std::make_shared<DocumentBroker>(type, uri, uriPublic, docKey);
|
||||
docBroker = std::make_shared<DocumentBroker>(type, uri, uriPublic, docKey, mobileAppDocId);
|
||||
DocBrokers.emplace(docKey, docBroker);
|
||||
LOG_TRC("Have " << DocBrokers.size() << " DocBrokers after inserting [" << docKey << "].");
|
||||
}
|
||||
|
@ -2453,12 +2461,26 @@ private:
|
|||
socket->eraseFirstInputBytes(map);
|
||||
#else
|
||||
Poco::Net::HTTPRequest request;
|
||||
// The 2nd parameter is the response to the HULLO message (which we
|
||||
// respond with the path of the document)
|
||||
|
||||
#ifdef IOS
|
||||
// The URL of the document is sent over the FakeSocket by the code in
|
||||
// -[DocumentViewController userContentController:didReceiveScriptMessage:] when it gets the
|
||||
// HULLO message from the JavaScript in global.js.
|
||||
|
||||
// The "app document id", the numeric id of the document, from the appDocIdCounter in CODocument.mm.
|
||||
char *space = strchr(socket->getInBuffer().data(), ' ');
|
||||
assert(space != nullptr);
|
||||
unsigned appDocId = std::strtoul(space + 1, nullptr, 10);
|
||||
|
||||
handleClientWsUpgrade(
|
||||
request, std::string(socket->getInBuffer().data(), space - socket->getInBuffer().data()),
|
||||
disposition, socket, appDocId);
|
||||
#else
|
||||
handleClientWsUpgrade(
|
||||
request, RequestDetails(std::string(socket->getInBuffer().data(),
|
||||
socket->getInBuffer().size())),
|
||||
disposition, socket);
|
||||
#endif
|
||||
socket->getInBuffer().clear();
|
||||
#endif
|
||||
}
|
||||
|
@ -2946,7 +2968,6 @@ private:
|
|||
|
||||
throw BadRequestException("Invalid or unknown request.");
|
||||
}
|
||||
#endif
|
||||
|
||||
void handleClientProxyRequest(const Poco::Net::HTTPRequest& request,
|
||||
const RequestDetails &requestDetails,
|
||||
|
@ -3053,11 +3074,13 @@ private:
|
|||
streamSocket->shutdown();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void handleClientWsUpgrade(const Poco::Net::HTTPRequest& request,
|
||||
const RequestDetails &requestDetails,
|
||||
SocketDisposition& disposition,
|
||||
const std::shared_ptr<StreamSocket>& socket)
|
||||
const std::shared_ptr<StreamSocket>& socket,
|
||||
unsigned mobileAppDocId = 0)
|
||||
{
|
||||
const std::string url = requestDetails.getDocumentURI();
|
||||
assert(socket && "Must have a valid socket");
|
||||
|
@ -3114,7 +3137,7 @@ private:
|
|||
// Request a kit process for this doc.
|
||||
std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker(
|
||||
std::static_pointer_cast<ProtocolHandlerInterface>(ws),
|
||||
DocumentBroker::ChildType::Interactive, url, docKey, _id, uriPublic);
|
||||
DocumentBroker::ChildType::Interactive, url, docKey, _id, uriPublic, mobileAppDocId);
|
||||
if (docBroker)
|
||||
{
|
||||
std::shared_ptr<ClientSession> clientSession =
|
||||
|
@ -3510,11 +3533,13 @@ private:
|
|||
|
||||
void wakeupHook() override
|
||||
{
|
||||
#if !MOBILEAPP
|
||||
if (SigUtil::getDumpGlobalState())
|
||||
{
|
||||
dump_state();
|
||||
SigUtil::resetDumpGlobalState();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
/// This thread & poll accepts incoming connections.
|
||||
|
@ -3971,7 +3996,7 @@ void LOOLWSD::cleanup()
|
|||
|
||||
int LOOLWSD::main(const std::vector<std::string>& /*args*/)
|
||||
{
|
||||
#if MOBILEAPP
|
||||
#if MOBILEAPP && !defined IOS
|
||||
SigUtil::resetTerminationFlag();
|
||||
#endif
|
||||
|
||||
|
@ -3992,10 +4017,6 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
|
|||
|
||||
UnitWSD::get().returnValue(returnValue);
|
||||
|
||||
#if MOBILEAPP
|
||||
fakeSocketDumpState();
|
||||
#endif
|
||||
|
||||
LOG_INF("Process [loolwsd] finished.");
|
||||
return returnValue;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class TraceFileWriter;
|
|||
class DocumentBroker;
|
||||
class ClipboardCache;
|
||||
|
||||
std::shared_ptr<ChildProcess> getNewChild_Blocks(const std::string& uri);
|
||||
std::shared_ptr<ChildProcess> getNewChild_Blocks(unsigned mobileAppDocId = 0);
|
||||
|
||||
// A WSProcess object in the WSD process represents a descendant process, either the direct child
|
||||
// process FORKIT or a grandchild KIT process, with which the WSD process communicates through a
|
||||
|
@ -257,8 +257,10 @@ public:
|
|||
static std::set<const Poco::Util::AbstractConfiguration*> PluginConfigurations;
|
||||
static std::chrono::time_point<std::chrono::system_clock> StartTime;
|
||||
#if MOBILEAPP
|
||||
#ifndef IOS
|
||||
/// This is used to be able to wait until the lokit main thread has finished (and it is safe to load a new document).
|
||||
static std::mutex lokit_main_mutex;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/// For testing only [!]
|
||||
|
|
Loading…
Reference in New Issue