nextcloud-desktop/src/gui/socketapisocket_mac.mm
Jocelyn Turcotte bfcfdeec64 shell_i: Use an NSConnection instead of a local socket #2340
This prepares the switch to the official FinderSync API on Yosemite
which requires the extension to run in a sandbox. This complicates
the usage of a local socket to communicate with a non-sandboxed GUI
client. An NSConnection is easier to use in this case, which we can
use as long as the server name (i.e. Mach port registered name) is
prefixed with the code signing Team Identifier.

A placeholder server implementation is also added to the client's
SocketApi which basically reproduces the interface of a QLocalSocket.
Most of the references to individual sockets we're only using
QIODevice methods so the type was simply reduced. A typedef to
replace the QLocalServer was the only other part needed.
2015-06-15 15:28:17 +02:00

225 lines
6.1 KiB
Text

/*
* Copyright (C) by Jocelyn Turcotte <jturcotte@woboq.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "socketapisocket_mac.h"
#import <Cocoa/Cocoa.h>
@protocol ChannelProtocol <NSObject>
- (void)sendMessage:(NSData*)msg;
@end
@protocol RemoteEndProtocol <NSObject, ChannelProtocol>
- (void)registerTransmitter:(id)tx;
@end
@interface LocalEnd : NSObject <ChannelProtocol>
@property SocketApiSocketPrivate *wrapper;
- (instancetype)initWithWrapper:(SocketApiSocketPrivate *)wrapper;
@end
@interface Server : NSObject
@property SocketApiServerPrivate *wrapper;
- (instancetype)initWithWrapper:(SocketApiServerPrivate *)wrapper;
- (void)registerClient:(NSDistantObject <RemoteEndProtocol> *)remoteEnd;
@end
class SocketApiSocketPrivate
{
public:
SocketApiSocket *q_ptr;
SocketApiSocketPrivate(NSDistantObject <ChannelProtocol> *remoteEnd);
~SocketApiSocketPrivate();
NSDistantObject <ChannelProtocol> *remoteEnd;
LocalEnd *localEnd;
QByteArray inBuffer;
};
class SocketApiServerPrivate
{
public:
SocketApiServer *q_ptr;
SocketApiServerPrivate();
~SocketApiServerPrivate();
QList<SocketApiSocket*> pendingConnections;
NSConnection *connection;
Server *server;
};
@implementation LocalEnd
- (instancetype)initWithWrapper:(SocketApiSocketPrivate *)wrapper
{
self = [super init];
self->_wrapper = wrapper;
return self;
}
- (void)sendMessage:(NSData*)msg
{
if (_wrapper) {
_wrapper->inBuffer += QByteArray::fromRawNSData(msg);
emit _wrapper->q_ptr->readyRead();
}
}
- (void)connectionDidDie:(NSNotification*)notification
{
#pragma unused(notification)
if (_wrapper)
emit _wrapper->q_ptr->disconnected();
}
@end
@implementation Server
- (instancetype)initWithWrapper:(SocketApiServerPrivate *)wrapper
{
self = [super init];
self->_wrapper = wrapper;
return self;
}
- (void)registerClient:(NSDistantObject <RemoteEndProtocol> *)remoteEnd
{
// This saves a few mach messages that would otherwise be needed to query the interface
[remoteEnd setProtocolForProxy:@protocol(RemoteEndProtocol)];
SocketApiServer *server = _wrapper->q_ptr;
SocketApiSocketPrivate *socketPrivate = new SocketApiSocketPrivate(remoteEnd);
SocketApiSocket *socket = new SocketApiSocket(server, socketPrivate);
_wrapper->pendingConnections.append(socket);
emit server->newConnection();
[remoteEnd registerTransmitter:socketPrivate->localEnd];
}
@end
SocketApiSocket::SocketApiSocket(QObject *parent, SocketApiSocketPrivate *p)
: QIODevice(parent)
, d_ptr(p)
{
Q_D(SocketApiSocket);
d->q_ptr = this;
open(ReadWrite);
}
SocketApiSocket::~SocketApiSocket()
{
}
qint64 SocketApiSocket::readData(char *data, qint64 maxlen)
{
Q_D(SocketApiSocket);
qint64 len = std::min(maxlen, static_cast<qint64>(d->inBuffer.size()));
memcpy(data, d->inBuffer.constData(), len);
d->inBuffer.remove(0, len);
return len;
}
qint64 SocketApiSocket::writeData(const char *data, qint64 len)
{
@try {
Q_D(SocketApiSocket);
// FIXME: The NSConnection will make this block unless the function is marked as "oneway"
// in the protocol. This isn't async and reduces our performances but this currectly avoids
// a Mach queue deadlock during requests bursts of the legacy OwnCloudFinder extension.
// Since FinderSync already runs in a separate process, blocking isn't too critical.
[d->remoteEnd sendMessage:[NSData dataWithBytesNoCopy:const_cast<char *>(data) length:len freeWhenDone:NO]];
return len;
} @catch(NSException* e) {
// connectionDidDie can be notified too late, also interpret any sending exception as a disconnection.
emit disconnected();
return -1;
}
}
qint64 SocketApiSocket::bytesAvailable() const
{
Q_D(const SocketApiSocket);
return d->inBuffer.size() + QIODevice::bytesAvailable();
}
bool SocketApiSocket::canReadLine() const
{
Q_D(const SocketApiSocket);
return d->inBuffer.indexOf('\n', int(pos())) != -1 || QIODevice::canReadLine();
}
SocketApiSocketPrivate::SocketApiSocketPrivate(NSDistantObject <ChannelProtocol> *remoteEnd)
: remoteEnd(remoteEnd)
, localEnd([[LocalEnd alloc] initWithWrapper:this])
{
[remoteEnd retain];
// (Ab)use our objective-c object just to catch the notification
[[NSNotificationCenter defaultCenter] addObserver:localEnd
selector:@selector(connectionDidDie:)
name:NSConnectionDidDieNotification
object:[remoteEnd connectionForProxy]];
}
SocketApiSocketPrivate::~SocketApiSocketPrivate()
{
[remoteEnd release];
// The DO vended localEnd might still be referenced by the connection
localEnd.wrapper = nil;
[localEnd release];
}
SocketApiServer::SocketApiServer()
: d_ptr(new SocketApiServerPrivate)
{
Q_D(SocketApiServer);
d->q_ptr = this;
}
SocketApiServer::~SocketApiServer()
{
}
void SocketApiServer::close()
{
// Assume we'll be destroyed right after
}
bool SocketApiServer::listen(const QString &name)
{
Q_D(SocketApiServer);
// Set the name of the root object
return [d->connection registerName:name.toNSString()];
}
SocketApiSocket *SocketApiServer::nextPendingConnection()
{
Q_D(SocketApiServer);
return d->pendingConnections.takeFirst();
}
SocketApiServerPrivate::SocketApiServerPrivate()
{
// Create the connection and server object to vend over Disributed Objects
connection = [[NSConnection alloc] init];
server = [[Server alloc] initWithWrapper:this];
[connection setRootObject:server];
}
SocketApiServerPrivate::~SocketApiServerPrivate()
{
[connection release];
server.wrapper = nil;
[server release];
}