forked from rclone/rclone
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rclone.py
108 lines (89 loc) · 3.17 KB
/
rclone.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"""
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 *
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
class RcloneRPCResult(Structure):
"""
This is returned from the C API when calling RcloneRPC
"""
_fields_ = [("Output", RcloneRPCString),
("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
"""
def __init__(self, shared_object=f"./librclone{'.dll' if os.name == 'nt' else '.so'}"):
self.rclone = CDLL(shared_object)
self.rclone.RcloneRPC.restype = RcloneRPCResult
self.rclone.RcloneRPC.argtypes = (c_char_p, c_char_p)
self.rclone.RcloneFreeString.restype = None
self.rclone.RcloneFreeString.argtypes = (c_char_p,)
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)
output = json.loads(resp.Output.value.decode("utf-8"))
self.rclone.RcloneFreeString(resp.Output)
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"])