2021-04-28 23:53:35 +08:00
|
|
|
"""
|
|
|
|
Python interface to librclone.so using ctypes
|
|
|
|
|
|
|
|
Create an rclone object
|
|
|
|
|
|
|
|
rclone = Rclone(shared_object="/path/to/librclone.so")
|
|
|
|
|
|
|
|
Then call rpc calls on it
|
|
|
|
|
|
|
|
rclone.rpc("rc/noop", a=42, b="string", c=[1234])
|
|
|
|
|
|
|
|
When finished, close it
|
|
|
|
|
|
|
|
rclone.close()
|
|
|
|
"""
|
|
|
|
|
|
|
|
__all__ = ('Rclone', 'RcloneException')
|
|
|
|
|
|
|
|
import os
|
|
|
|
import json
|
|
|
|
import subprocess
|
|
|
|
from ctypes import *
|
|
|
|
|
2021-10-17 04:24:03 +08:00
|
|
|
class RcloneRPCString(c_char_p):
|
|
|
|
"""
|
|
|
|
This is a raw string from the C API
|
|
|
|
|
|
|
|
With a plain c_char_p type, ctypes will replace it with a
|
|
|
|
regular Python string object that cannot be used with
|
|
|
|
RcloneFreeString. Subclassing prevents it, while the string
|
|
|
|
can still be retrieved from attribute value.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2021-04-28 23:53:35 +08:00
|
|
|
class RcloneRPCResult(Structure):
|
|
|
|
"""
|
|
|
|
This is returned from the C API when calling RcloneRPC
|
|
|
|
"""
|
2021-10-17 04:24:03 +08:00
|
|
|
_fields_ = [("Output", RcloneRPCString),
|
2021-04-28 23:53:35 +08:00
|
|
|
("Status", c_int)]
|
|
|
|
|
|
|
|
class RcloneException(Exception):
|
|
|
|
"""
|
|
|
|
Exception raised from rclone
|
|
|
|
|
|
|
|
This will have the attributes:
|
|
|
|
|
|
|
|
output - a dictionary from the call
|
|
|
|
status - a status number
|
|
|
|
"""
|
|
|
|
def __init__(self, output, status):
|
|
|
|
self.output = output
|
|
|
|
self.status = status
|
|
|
|
message = self.output.get('error', 'Unknown rclone error')
|
|
|
|
super().__init__(message)
|
|
|
|
|
|
|
|
class Rclone():
|
|
|
|
"""
|
|
|
|
Interface to Rclone via librclone.so
|
|
|
|
|
|
|
|
Initialise with shared_object as the file path of librclone.so
|
|
|
|
"""
|
2021-10-17 04:24:03 +08:00
|
|
|
def __init__(self, shared_object=f"./librclone{'.dll' if os.name == 'nt' else '.so'}"):
|
2021-04-28 23:53:35 +08:00
|
|
|
self.rclone = CDLL(shared_object)
|
|
|
|
self.rclone.RcloneRPC.restype = RcloneRPCResult
|
|
|
|
self.rclone.RcloneRPC.argtypes = (c_char_p, c_char_p)
|
2021-10-17 04:24:03 +08:00
|
|
|
self.rclone.RcloneFreeString.restype = None
|
|
|
|
self.rclone.RcloneFreeString.argtypes = (c_char_p,)
|
2021-04-28 23:53:35 +08:00
|
|
|
self.rclone.RcloneInitialize.restype = None
|
|
|
|
self.rclone.RcloneInitialize.argtypes = ()
|
|
|
|
self.rclone.RcloneFinalize.restype = None
|
|
|
|
self.rclone.RcloneFinalize.argtypes = ()
|
|
|
|
self.rclone.RcloneInitialize()
|
|
|
|
def rpc(self, method, **kwargs):
|
|
|
|
"""
|
|
|
|
Call an rclone RC API call with the kwargs given.
|
|
|
|
|
|
|
|
The result will be a dictionary.
|
|
|
|
|
|
|
|
If an exception is raised from rclone it will of type
|
|
|
|
RcloneException.
|
|
|
|
"""
|
|
|
|
method = method.encode("utf-8")
|
|
|
|
parameters = json.dumps(kwargs).encode("utf-8")
|
|
|
|
resp = self.rclone.RcloneRPC(method, parameters)
|
2021-10-17 04:24:03 +08:00
|
|
|
output = json.loads(resp.Output.value.decode("utf-8"))
|
|
|
|
self.rclone.RcloneFreeString(resp.Output)
|
2021-04-28 23:53:35 +08:00
|
|
|
status = resp.Status
|
|
|
|
if status != 200:
|
|
|
|
raise RcloneException(output, status)
|
|
|
|
return output
|
|
|
|
def close(self):
|
|
|
|
"""
|
|
|
|
Call to finish with the rclone connection
|
|
|
|
"""
|
|
|
|
self.rclone.RcloneFinalize()
|
|
|
|
self.rclone = None
|
|
|
|
@classmethod
|
|
|
|
def build(cls, shared_object):
|
|
|
|
"""
|
|
|
|
Builds rclone to shared_object if it doesn't already exist
|
|
|
|
|
|
|
|
Requires go to be installed
|
|
|
|
"""
|
|
|
|
if os.path.exists(shared_object):
|
|
|
|
return
|
|
|
|
print("Building "+shared_object)
|
|
|
|
subprocess.check_call(["go", "build", "--buildmode=c-shared", "-o", shared_object, "github.com/rclone/rclone/librclone"])
|