github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/librclone/python/rclone.py (about) 1 """ 2 Python interface to librclone.so using ctypes 3 4 Create an rclone object 5 6 rclone = Rclone(shared_object="/path/to/librclone.so") 7 8 Then call rpc calls on it 9 10 rclone.rpc("rc/noop", a=42, b="string", c=[1234]) 11 12 When finished, close it 13 14 rclone.close() 15 """ 16 17 __all__ = ('Rclone', 'RcloneException') 18 19 import os 20 import json 21 import subprocess 22 from ctypes import * 23 24 class RcloneRPCString(c_char_p): 25 """ 26 This is a raw string from the C API 27 28 With a plain c_char_p type, ctypes will replace it with a 29 regular Python string object that cannot be used with 30 RcloneFreeString. Subclassing prevents it, while the string 31 can still be retrieved from attribute value. 32 """ 33 pass 34 35 class RcloneRPCResult(Structure): 36 """ 37 This is returned from the C API when calling RcloneRPC 38 """ 39 _fields_ = [("Output", RcloneRPCString), 40 ("Status", c_int)] 41 42 class RcloneException(Exception): 43 """ 44 Exception raised from rclone 45 46 This will have the attributes: 47 48 output - a dictionary from the call 49 status - a status number 50 """ 51 def __init__(self, output, status): 52 self.output = output 53 self.status = status 54 message = self.output.get('error', 'Unknown rclone error') 55 super().__init__(message) 56 57 class Rclone(): 58 """ 59 Interface to Rclone via librclone.so 60 61 Initialise with shared_object as the file path of librclone.so 62 """ 63 def __init__(self, shared_object=f"./librclone{'.dll' if os.name == 'nt' else '.so'}"): 64 self.rclone = CDLL(shared_object) 65 self.rclone.RcloneRPC.restype = RcloneRPCResult 66 self.rclone.RcloneRPC.argtypes = (c_char_p, c_char_p) 67 self.rclone.RcloneFreeString.restype = None 68 self.rclone.RcloneFreeString.argtypes = (c_char_p,) 69 self.rclone.RcloneInitialize.restype = None 70 self.rclone.RcloneInitialize.argtypes = () 71 self.rclone.RcloneFinalize.restype = None 72 self.rclone.RcloneFinalize.argtypes = () 73 self.rclone.RcloneInitialize() 74 def rpc(self, method, **kwargs): 75 """ 76 Call an rclone RC API call with the kwargs given. 77 78 The result will be a dictionary. 79 80 If an exception is raised from rclone it will of type 81 RcloneException. 82 """ 83 method = method.encode("utf-8") 84 parameters = json.dumps(kwargs).encode("utf-8") 85 resp = self.rclone.RcloneRPC(method, parameters) 86 output = json.loads(resp.Output.value.decode("utf-8")) 87 self.rclone.RcloneFreeString(resp.Output) 88 status = resp.Status 89 if status != 200: 90 raise RcloneException(output, status) 91 return output 92 def close(self): 93 """ 94 Call to finish with the rclone connection 95 """ 96 self.rclone.RcloneFinalize() 97 self.rclone = None 98 @classmethod 99 def build(cls, shared_object): 100 """ 101 Builds rclone to shared_object if it doesn't already exist 102 103 Requires go to be installed 104 """ 105 if os.path.exists(shared_object): 106 return 107 print("Building "+shared_object) 108 subprocess.check_call(["go", "build", "--buildmode=c-shared", "-o", shared_object, "github.com/rclone/rclone/librclone"])