Is it possible to use pyobjc with a privilved XPC helper tool and XPCInterface API?
I believe the answer to this question is "no", but I'm putting it out to the community just in case someone has been more successful than I have.
I have a privileged helper tool that a client Cocoa application uses with the NSXPCConnection and NSXPCInterface. The interface itself includes a method that provides a return code through a completion handler block.
In Objective-C the client code looks like this:
NSXPCConnection * xpcConn = [NSXPCConnection alloc]
initWithMachServiceName:kSvcName
options:NSXPCConnectionPrivileged];
// MyProtocol defines an instance method runCommand:(NSString*) withReply:^(int result)
NSXPCInterface * mySvcIF = [NSXPCInterface interfaceWithProtocol:@protocol(MyProtocol)];
xpcConn.remoteObjectInterface = mySvcIF;
[xpcConn resume];
if (nil == xpcConn.remoteObjectProxy) {
NSLog(@"ERROR - remote interface is nil, can't communicate with service");
}
[[xpcConn remoteObjectProxy] runCommand:nsstrCmd withReply:^(int result) {
NSLog(@"service result is: %d", result);
if (result != 0) {
self.svcResult = result;
self.svcCommandComplete = YES;
}
}];
I also have a pyobjc / py2app Mac application that needs to use this helper tool's functionality. I've got the tool built into the pyobjc app bundle, signed, and authorizing via SMJobBless, but it is looking like there are several problems that make actual use of this API unsupported:
1) Bridging the invocation of runCommand:withReply:^ doesn't seem to be supported - if I understand correctly blocks are only supported for NS* framework method invocations not for 'custom' (i.e. user-defined) methods? Note, I could make a version of the method with no return code if this was the only blocking issue, but an attempt didn't quite work because...
2) In order to use the API in the way the Objective-C does I need to create a @selector reference to runCommand: that does not actually have any python function implementation - it needs to just be a function object that defines the signature for a function that will be furnished by the dynamically created remoteProxy. I don't define the remoteProxy implementation in python. This does not seem to be supported - I could not get the selector declaration without a python function to work via objc.selector().
3) I'm not positive that even if I could get 2) to work, that construction of the formal protocol would work the way it's expected to as a parameter to interfaceWithProtocol: from python - it needs to become a native custom @protocol that NSXPCInterface could use in its factory method to create the remoteProxy.
Thanks for any tips if you've figured out how to do this in pyobjc, or any definitive confirmation that this stuff just isn't possible based on your knowledge of it.
macos cocoa pyobjc xpc nsxpcconnection
add a comment |
I believe the answer to this question is "no", but I'm putting it out to the community just in case someone has been more successful than I have.
I have a privileged helper tool that a client Cocoa application uses with the NSXPCConnection and NSXPCInterface. The interface itself includes a method that provides a return code through a completion handler block.
In Objective-C the client code looks like this:
NSXPCConnection * xpcConn = [NSXPCConnection alloc]
initWithMachServiceName:kSvcName
options:NSXPCConnectionPrivileged];
// MyProtocol defines an instance method runCommand:(NSString*) withReply:^(int result)
NSXPCInterface * mySvcIF = [NSXPCInterface interfaceWithProtocol:@protocol(MyProtocol)];
xpcConn.remoteObjectInterface = mySvcIF;
[xpcConn resume];
if (nil == xpcConn.remoteObjectProxy) {
NSLog(@"ERROR - remote interface is nil, can't communicate with service");
}
[[xpcConn remoteObjectProxy] runCommand:nsstrCmd withReply:^(int result) {
NSLog(@"service result is: %d", result);
if (result != 0) {
self.svcResult = result;
self.svcCommandComplete = YES;
}
}];
I also have a pyobjc / py2app Mac application that needs to use this helper tool's functionality. I've got the tool built into the pyobjc app bundle, signed, and authorizing via SMJobBless, but it is looking like there are several problems that make actual use of this API unsupported:
1) Bridging the invocation of runCommand:withReply:^ doesn't seem to be supported - if I understand correctly blocks are only supported for NS* framework method invocations not for 'custom' (i.e. user-defined) methods? Note, I could make a version of the method with no return code if this was the only blocking issue, but an attempt didn't quite work because...
2) In order to use the API in the way the Objective-C does I need to create a @selector reference to runCommand: that does not actually have any python function implementation - it needs to just be a function object that defines the signature for a function that will be furnished by the dynamically created remoteProxy. I don't define the remoteProxy implementation in python. This does not seem to be supported - I could not get the selector declaration without a python function to work via objc.selector().
3) I'm not positive that even if I could get 2) to work, that construction of the formal protocol would work the way it's expected to as a parameter to interfaceWithProtocol: from python - it needs to become a native custom @protocol that NSXPCInterface could use in its factory method to create the remoteProxy.
Thanks for any tips if you've figured out how to do this in pyobjc, or any definitive confirmation that this stuff just isn't possible based on your knowledge of it.
macos cocoa pyobjc xpc nsxpcconnection
add a comment |
I believe the answer to this question is "no", but I'm putting it out to the community just in case someone has been more successful than I have.
I have a privileged helper tool that a client Cocoa application uses with the NSXPCConnection and NSXPCInterface. The interface itself includes a method that provides a return code through a completion handler block.
In Objective-C the client code looks like this:
NSXPCConnection * xpcConn = [NSXPCConnection alloc]
initWithMachServiceName:kSvcName
options:NSXPCConnectionPrivileged];
// MyProtocol defines an instance method runCommand:(NSString*) withReply:^(int result)
NSXPCInterface * mySvcIF = [NSXPCInterface interfaceWithProtocol:@protocol(MyProtocol)];
xpcConn.remoteObjectInterface = mySvcIF;
[xpcConn resume];
if (nil == xpcConn.remoteObjectProxy) {
NSLog(@"ERROR - remote interface is nil, can't communicate with service");
}
[[xpcConn remoteObjectProxy] runCommand:nsstrCmd withReply:^(int result) {
NSLog(@"service result is: %d", result);
if (result != 0) {
self.svcResult = result;
self.svcCommandComplete = YES;
}
}];
I also have a pyobjc / py2app Mac application that needs to use this helper tool's functionality. I've got the tool built into the pyobjc app bundle, signed, and authorizing via SMJobBless, but it is looking like there are several problems that make actual use of this API unsupported:
1) Bridging the invocation of runCommand:withReply:^ doesn't seem to be supported - if I understand correctly blocks are only supported for NS* framework method invocations not for 'custom' (i.e. user-defined) methods? Note, I could make a version of the method with no return code if this was the only blocking issue, but an attempt didn't quite work because...
2) In order to use the API in the way the Objective-C does I need to create a @selector reference to runCommand: that does not actually have any python function implementation - it needs to just be a function object that defines the signature for a function that will be furnished by the dynamically created remoteProxy. I don't define the remoteProxy implementation in python. This does not seem to be supported - I could not get the selector declaration without a python function to work via objc.selector().
3) I'm not positive that even if I could get 2) to work, that construction of the formal protocol would work the way it's expected to as a parameter to interfaceWithProtocol: from python - it needs to become a native custom @protocol that NSXPCInterface could use in its factory method to create the remoteProxy.
Thanks for any tips if you've figured out how to do this in pyobjc, or any definitive confirmation that this stuff just isn't possible based on your knowledge of it.
macos cocoa pyobjc xpc nsxpcconnection
I believe the answer to this question is "no", but I'm putting it out to the community just in case someone has been more successful than I have.
I have a privileged helper tool that a client Cocoa application uses with the NSXPCConnection and NSXPCInterface. The interface itself includes a method that provides a return code through a completion handler block.
In Objective-C the client code looks like this:
NSXPCConnection * xpcConn = [NSXPCConnection alloc]
initWithMachServiceName:kSvcName
options:NSXPCConnectionPrivileged];
// MyProtocol defines an instance method runCommand:(NSString*) withReply:^(int result)
NSXPCInterface * mySvcIF = [NSXPCInterface interfaceWithProtocol:@protocol(MyProtocol)];
xpcConn.remoteObjectInterface = mySvcIF;
[xpcConn resume];
if (nil == xpcConn.remoteObjectProxy) {
NSLog(@"ERROR - remote interface is nil, can't communicate with service");
}
[[xpcConn remoteObjectProxy] runCommand:nsstrCmd withReply:^(int result) {
NSLog(@"service result is: %d", result);
if (result != 0) {
self.svcResult = result;
self.svcCommandComplete = YES;
}
}];
I also have a pyobjc / py2app Mac application that needs to use this helper tool's functionality. I've got the tool built into the pyobjc app bundle, signed, and authorizing via SMJobBless, but it is looking like there are several problems that make actual use of this API unsupported:
1) Bridging the invocation of runCommand:withReply:^ doesn't seem to be supported - if I understand correctly blocks are only supported for NS* framework method invocations not for 'custom' (i.e. user-defined) methods? Note, I could make a version of the method with no return code if this was the only blocking issue, but an attempt didn't quite work because...
2) In order to use the API in the way the Objective-C does I need to create a @selector reference to runCommand: that does not actually have any python function implementation - it needs to just be a function object that defines the signature for a function that will be furnished by the dynamically created remoteProxy. I don't define the remoteProxy implementation in python. This does not seem to be supported - I could not get the selector declaration without a python function to work via objc.selector().
3) I'm not positive that even if I could get 2) to work, that construction of the formal protocol would work the way it's expected to as a parameter to interfaceWithProtocol: from python - it needs to become a native custom @protocol that NSXPCInterface could use in its factory method to create the remoteProxy.
Thanks for any tips if you've figured out how to do this in pyobjc, or any definitive confirmation that this stuff just isn't possible based on your knowledge of it.
macos cocoa pyobjc xpc nsxpcconnection
macos cocoa pyobjc xpc nsxpcconnection
asked Sep 11 '18 at 20:56
orpheist
532310
532310
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
The first two subquestions are straightforward to answer: It is possible to call APIs with blocks, even those in libraries that aren't Apple frameworks. This does require some more work in the python code because the Objective-C runtime doesn't expose enough information to fully automatically do the right thing.
For this particular example you could do something like this:
objc.registerMetaDataForSelector(b'NSObject', b'runCommand:withReply:', {
'arguments': {
3: {
'callable': {
'retval': {'type': b'@'},
'arguments': {
0: {'type': b'^v'},
1: {'type': b'i'},
},
},
}
}
})
This registers additional information for the method "-[NSObject runCommand:withReply:]". The block argument is number 3: counting starts at 0 and the first two arguments of Objective-C methods are "self" and "_sel" (the latter is not exposed to Python).
You normally use the actual class where the method is implemented, but I expect that this is a hidden class that might even be dynamically generated. Just registering metadata on NSObject should be safe as long as there is no conflict with other classes.
Creating protocols in Python is also possible:
MyProtocol = objc.formal_protocol('MyProtocol', (), [
objc.selector(None, b"runCommand:withReply:", signature=b"v@:@@?"),
])
And creating the XPC interface with:
mySvcIF = Foundation.NSXPCInterface.interfaceWithProtocol_(MyProtocol)
The latter step sadly enough does not work because NSXPCInterface raises an exception: NSInvalidArgumentException - NSXPCInterface: Unable to get extended method signature from Protocol data (MyProtocol / runCommand:withReply:). Use of clang is required for NSXPCInterface.
.
I've filed an issue for this in PyObjC's tracker: https://bitbucket.org/ronaldoussoren/pyobjc/issues/256/enable-using-xpcinterface-with-protocols.
A workaround for this issue is to create a Python extension that contains the protocol definition as well as an unused function that uses the protocol (see for example https://bitbucket.org/ronaldoussoren/pyobjc/src/default/pyobjc-framework-Cocoa/Modules/_AppKit_protocols.m for the latter part). After importing the extension you can use objc.protocolNamed("MyProtocol")
to access the protocol, which will then refer to the full protocol object created by clang and should work with NSXPCInterface.
P.S. I rarely visit stackoverflow, its often easier to get my attention by mailing to pyobjc-dev@lists.sourceforge.net (PyObjC mailinglist).
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52284089%2fis-it-possible-to-use-pyobjc-with-a-privilved-xpc-helper-tool-and-xpcinterface-a%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
The first two subquestions are straightforward to answer: It is possible to call APIs with blocks, even those in libraries that aren't Apple frameworks. This does require some more work in the python code because the Objective-C runtime doesn't expose enough information to fully automatically do the right thing.
For this particular example you could do something like this:
objc.registerMetaDataForSelector(b'NSObject', b'runCommand:withReply:', {
'arguments': {
3: {
'callable': {
'retval': {'type': b'@'},
'arguments': {
0: {'type': b'^v'},
1: {'type': b'i'},
},
},
}
}
})
This registers additional information for the method "-[NSObject runCommand:withReply:]". The block argument is number 3: counting starts at 0 and the first two arguments of Objective-C methods are "self" and "_sel" (the latter is not exposed to Python).
You normally use the actual class where the method is implemented, but I expect that this is a hidden class that might even be dynamically generated. Just registering metadata on NSObject should be safe as long as there is no conflict with other classes.
Creating protocols in Python is also possible:
MyProtocol = objc.formal_protocol('MyProtocol', (), [
objc.selector(None, b"runCommand:withReply:", signature=b"v@:@@?"),
])
And creating the XPC interface with:
mySvcIF = Foundation.NSXPCInterface.interfaceWithProtocol_(MyProtocol)
The latter step sadly enough does not work because NSXPCInterface raises an exception: NSInvalidArgumentException - NSXPCInterface: Unable to get extended method signature from Protocol data (MyProtocol / runCommand:withReply:). Use of clang is required for NSXPCInterface.
.
I've filed an issue for this in PyObjC's tracker: https://bitbucket.org/ronaldoussoren/pyobjc/issues/256/enable-using-xpcinterface-with-protocols.
A workaround for this issue is to create a Python extension that contains the protocol definition as well as an unused function that uses the protocol (see for example https://bitbucket.org/ronaldoussoren/pyobjc/src/default/pyobjc-framework-Cocoa/Modules/_AppKit_protocols.m for the latter part). After importing the extension you can use objc.protocolNamed("MyProtocol")
to access the protocol, which will then refer to the full protocol object created by clang and should work with NSXPCInterface.
P.S. I rarely visit stackoverflow, its often easier to get my attention by mailing to pyobjc-dev@lists.sourceforge.net (PyObjC mailinglist).
add a comment |
The first two subquestions are straightforward to answer: It is possible to call APIs with blocks, even those in libraries that aren't Apple frameworks. This does require some more work in the python code because the Objective-C runtime doesn't expose enough information to fully automatically do the right thing.
For this particular example you could do something like this:
objc.registerMetaDataForSelector(b'NSObject', b'runCommand:withReply:', {
'arguments': {
3: {
'callable': {
'retval': {'type': b'@'},
'arguments': {
0: {'type': b'^v'},
1: {'type': b'i'},
},
},
}
}
})
This registers additional information for the method "-[NSObject runCommand:withReply:]". The block argument is number 3: counting starts at 0 and the first two arguments of Objective-C methods are "self" and "_sel" (the latter is not exposed to Python).
You normally use the actual class where the method is implemented, but I expect that this is a hidden class that might even be dynamically generated. Just registering metadata on NSObject should be safe as long as there is no conflict with other classes.
Creating protocols in Python is also possible:
MyProtocol = objc.formal_protocol('MyProtocol', (), [
objc.selector(None, b"runCommand:withReply:", signature=b"v@:@@?"),
])
And creating the XPC interface with:
mySvcIF = Foundation.NSXPCInterface.interfaceWithProtocol_(MyProtocol)
The latter step sadly enough does not work because NSXPCInterface raises an exception: NSInvalidArgumentException - NSXPCInterface: Unable to get extended method signature from Protocol data (MyProtocol / runCommand:withReply:). Use of clang is required for NSXPCInterface.
.
I've filed an issue for this in PyObjC's tracker: https://bitbucket.org/ronaldoussoren/pyobjc/issues/256/enable-using-xpcinterface-with-protocols.
A workaround for this issue is to create a Python extension that contains the protocol definition as well as an unused function that uses the protocol (see for example https://bitbucket.org/ronaldoussoren/pyobjc/src/default/pyobjc-framework-Cocoa/Modules/_AppKit_protocols.m for the latter part). After importing the extension you can use objc.protocolNamed("MyProtocol")
to access the protocol, which will then refer to the full protocol object created by clang and should work with NSXPCInterface.
P.S. I rarely visit stackoverflow, its often easier to get my attention by mailing to pyobjc-dev@lists.sourceforge.net (PyObjC mailinglist).
add a comment |
The first two subquestions are straightforward to answer: It is possible to call APIs with blocks, even those in libraries that aren't Apple frameworks. This does require some more work in the python code because the Objective-C runtime doesn't expose enough information to fully automatically do the right thing.
For this particular example you could do something like this:
objc.registerMetaDataForSelector(b'NSObject', b'runCommand:withReply:', {
'arguments': {
3: {
'callable': {
'retval': {'type': b'@'},
'arguments': {
0: {'type': b'^v'},
1: {'type': b'i'},
},
},
}
}
})
This registers additional information for the method "-[NSObject runCommand:withReply:]". The block argument is number 3: counting starts at 0 and the first two arguments of Objective-C methods are "self" and "_sel" (the latter is not exposed to Python).
You normally use the actual class where the method is implemented, but I expect that this is a hidden class that might even be dynamically generated. Just registering metadata on NSObject should be safe as long as there is no conflict with other classes.
Creating protocols in Python is also possible:
MyProtocol = objc.formal_protocol('MyProtocol', (), [
objc.selector(None, b"runCommand:withReply:", signature=b"v@:@@?"),
])
And creating the XPC interface with:
mySvcIF = Foundation.NSXPCInterface.interfaceWithProtocol_(MyProtocol)
The latter step sadly enough does not work because NSXPCInterface raises an exception: NSInvalidArgumentException - NSXPCInterface: Unable to get extended method signature from Protocol data (MyProtocol / runCommand:withReply:). Use of clang is required for NSXPCInterface.
.
I've filed an issue for this in PyObjC's tracker: https://bitbucket.org/ronaldoussoren/pyobjc/issues/256/enable-using-xpcinterface-with-protocols.
A workaround for this issue is to create a Python extension that contains the protocol definition as well as an unused function that uses the protocol (see for example https://bitbucket.org/ronaldoussoren/pyobjc/src/default/pyobjc-framework-Cocoa/Modules/_AppKit_protocols.m for the latter part). After importing the extension you can use objc.protocolNamed("MyProtocol")
to access the protocol, which will then refer to the full protocol object created by clang and should work with NSXPCInterface.
P.S. I rarely visit stackoverflow, its often easier to get my attention by mailing to pyobjc-dev@lists.sourceforge.net (PyObjC mailinglist).
The first two subquestions are straightforward to answer: It is possible to call APIs with blocks, even those in libraries that aren't Apple frameworks. This does require some more work in the python code because the Objective-C runtime doesn't expose enough information to fully automatically do the right thing.
For this particular example you could do something like this:
objc.registerMetaDataForSelector(b'NSObject', b'runCommand:withReply:', {
'arguments': {
3: {
'callable': {
'retval': {'type': b'@'},
'arguments': {
0: {'type': b'^v'},
1: {'type': b'i'},
},
},
}
}
})
This registers additional information for the method "-[NSObject runCommand:withReply:]". The block argument is number 3: counting starts at 0 and the first two arguments of Objective-C methods are "self" and "_sel" (the latter is not exposed to Python).
You normally use the actual class where the method is implemented, but I expect that this is a hidden class that might even be dynamically generated. Just registering metadata on NSObject should be safe as long as there is no conflict with other classes.
Creating protocols in Python is also possible:
MyProtocol = objc.formal_protocol('MyProtocol', (), [
objc.selector(None, b"runCommand:withReply:", signature=b"v@:@@?"),
])
And creating the XPC interface with:
mySvcIF = Foundation.NSXPCInterface.interfaceWithProtocol_(MyProtocol)
The latter step sadly enough does not work because NSXPCInterface raises an exception: NSInvalidArgumentException - NSXPCInterface: Unable to get extended method signature from Protocol data (MyProtocol / runCommand:withReply:). Use of clang is required for NSXPCInterface.
.
I've filed an issue for this in PyObjC's tracker: https://bitbucket.org/ronaldoussoren/pyobjc/issues/256/enable-using-xpcinterface-with-protocols.
A workaround for this issue is to create a Python extension that contains the protocol definition as well as an unused function that uses the protocol (see for example https://bitbucket.org/ronaldoussoren/pyobjc/src/default/pyobjc-framework-Cocoa/Modules/_AppKit_protocols.m for the latter part). After importing the extension you can use objc.protocolNamed("MyProtocol")
to access the protocol, which will then refer to the full protocol object created by clang and should work with NSXPCInterface.
P.S. I rarely visit stackoverflow, its often easier to get my attention by mailing to pyobjc-dev@lists.sourceforge.net (PyObjC mailinglist).
answered Dec 30 '18 at 10:41
Ronald Oussoren
2,0651424
2,0651424
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52284089%2fis-it-possible-to-use-pyobjc-with-a-privilved-xpc-helper-tool-and-xpcinterface-a%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown