codexctl 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
codexctl/server.py ADDED
@@ -0,0 +1,172 @@
1
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
2
+ import xml.etree.ElementTree as ET
3
+ import os
4
+ import hashlib
5
+ import binascii
6
+
7
+ response_ok = """<?xml version='1.0' encoding='UTF-8'?>
8
+ <response protocol="3.0" server="prod">
9
+ <daystart elapsed_seconds="42548" elapsed_days="5179"/>
10
+ <app status="ok" appid="{98DA7DF2-4E3E-4744-9DE6-EC931886ABAB}">
11
+ <event status="ok"/>
12
+ <ping status="ok"/>
13
+ </app>
14
+ </response>
15
+ """
16
+
17
+ response_template = """<?xml version="1.0" encoding="UTF-8"?>
18
+ <response protocol="3.0" server="prod">
19
+ <daystart elapsed_seconds="37145" elapsed_days="5179"/>
20
+ <app status="ok" appid="{{98DA7DF2-4E3E-4744-9DE6-EC931886ABAB}}">
21
+ <event status="ok"/>
22
+ <updatecheck status="ok">
23
+ <urls>
24
+ <url codebase="{codebase_url}"/>
25
+ </urls>
26
+ <manifest version="{version}">
27
+ <packages>
28
+ <package required="true" hash="{update_sha1}" name="{update_name}" size="{update_size}"/>
29
+ </packages>
30
+ <actions>
31
+ <action successsaction="default" sha256="{update_sha256}" event="postinstall" DisablePayloadBackoff="true"/>
32
+ </actions>
33
+ </manifest>
34
+ </updatecheck>
35
+ <ping status="ok"/>
36
+ </app>
37
+ </response>
38
+ """
39
+
40
+
41
+ def getupdateinfo(platform, version, update_name):
42
+ full_path = os.path.join("updates", update_name)
43
+
44
+ update_size = str(os.path.getsize(full_path))
45
+
46
+ BUF_SIZE = 8192
47
+
48
+ sha1 = hashlib.sha1()
49
+ sha256 = hashlib.sha256()
50
+ with open(full_path, "rb") as f:
51
+ while True:
52
+ data = f.read(BUF_SIZE)
53
+ if not data:
54
+ break
55
+ sha1.update(data)
56
+ sha256.update(data)
57
+ update_sha1 = binascii.b2a_base64(sha1.digest(), newline=False).decode()
58
+ update_sha256 = binascii.b2a_base64(sha256.digest(), newline=False).decode()
59
+ return (update_sha1, update_sha256, update_size)
60
+
61
+
62
+ def get_available_version(version):
63
+ available_versions = scanUpdates()
64
+
65
+ for device, ids in available_versions.items():
66
+ if version in ids:
67
+ available_version = {device: ids}
68
+
69
+ return available_version
70
+
71
+
72
+ def scanUpdates():
73
+ files = os.listdir("updates")
74
+ versions = {}
75
+
76
+ for f in files:
77
+ p = f.split("_")
78
+ if len(p) != 2:
79
+ continue
80
+ t = p[1].split(".")
81
+ if len(t) != 2:
82
+ continue
83
+
84
+ z = t[0].split("-")
85
+
86
+ version = p[0]
87
+ # print(version)
88
+ product = z[0]
89
+
90
+ if product not in versions or versions[product][0] < version:
91
+ versions[product] = (version, f)
92
+
93
+ return versions
94
+
95
+
96
+ class MySimpleHTTPRequestHandler(SimpleHTTPRequestHandler):
97
+ def do_POST(self):
98
+ length = int(self.headers.get("Content-Length"))
99
+ body = self.rfile.read(length).decode("utf-8")
100
+ # print(body)
101
+ print("Updating...")
102
+ xml = ET.fromstring(body)
103
+ updatecheck_node = xml.find("app/updatecheck")
104
+
105
+ # check for update
106
+ if updatecheck_node is not None:
107
+ version = xml.attrib["version"]
108
+ platform = xml.find("os").attrib["platform"]
109
+ print("requested: ", version)
110
+ print("platform: ", platform)
111
+
112
+ version, update_name = available_versions[platform]
113
+
114
+ update_sha1, update_sha256, update_size = getupdateinfo(
115
+ platform, version, update_name
116
+ )
117
+ params = {
118
+ "version": version,
119
+ "update_name": f"updates/{update_name}",
120
+ "update_sha1": update_sha1,
121
+ "update_sha256": update_sha256,
122
+ "update_size": update_size,
123
+ "codebase_url": host_url,
124
+ }
125
+
126
+ response = response_template.format(**params)
127
+ # print("Response:")
128
+ # print(response)
129
+ self.send_response(200)
130
+ self.end_headers()
131
+ self.wfile.write(response.encode())
132
+ return
133
+
134
+ event_node = xml.find("app/event")
135
+ event_type = int(event_node.attrib["eventtype"])
136
+ event_result = int(event_node.attrib["eventresult"])
137
+
138
+ # post install status
139
+ if event_result != 0:
140
+ print("Update downloaded, please wait for device to install...")
141
+ if "errorcode" in event_node.attrib:
142
+ print("With errorcode:", event_node.attrib["errorcode"])
143
+ return
144
+
145
+ # update done
146
+ if event_type == 14:
147
+ print("OK Response:")
148
+ print(response_ok)
149
+ self.send_response(200)
150
+ self.end_headers()
151
+ self.wfile.write(response_ok.encode())
152
+ return
153
+
154
+
155
+ def startUpdate(versionsGiven, host, port=8080):
156
+ global available_versions
157
+ global host_url # I am aware globals are generally bad practice, but this is a quick and dirty solution
158
+
159
+ host_url = f"http://{host}:{port}/"
160
+ available_versions = versionsGiven
161
+
162
+ if not available_versions:
163
+ raise FileNotFoundError("Could not find any update files")
164
+
165
+ handler = MySimpleHTTPRequestHandler
166
+ print(f"Starting fake updater at {host}:{port}")
167
+ try:
168
+ httpd = HTTPServer((host, port), handler)
169
+ except OSError:
170
+ print("Error: Could not start fake updater. Is the port already in use?")
171
+ return
172
+ httpd.serve_forever()
codexctl/sync.py ADDED
@@ -0,0 +1,218 @@
1
+ import requests
2
+ import os
3
+ import glob
4
+
5
+ import logging
6
+
7
+
8
+ class RmWebInterfaceAPI: # TODO: Add docstrings
9
+ def __init__(self, BASE="http://10.11.99.1/", logger=None):
10
+ self.logger = logger
11
+
12
+ if self.logger is None:
13
+ self.logger = logging
14
+
15
+ self.BASE = BASE
16
+ self.NAME_ATTRIBUTE = "VissibleName"
17
+ self.ID_ATTRIBUTE = "ID"
18
+
19
+ self.logger.debug(f"Base is: {BASE}")
20
+
21
+ def __POST(self, endpoint, data={}, fileUpload=False):
22
+ try:
23
+ logging.debug(
24
+ f"Sending POST request to {self.BASE + endpoint} with data {data}"
25
+ )
26
+
27
+ if fileUpload:
28
+ result = requests.post(self.BASE + endpoint, files=data)
29
+ else:
30
+ result = requests.post(self.BASE + endpoint, data=data)
31
+
32
+ if result.status_code == 408:
33
+ self.logger.error("Request timed out!")
34
+
35
+ logging.debug(f"Result headers: {result.headers}")
36
+ if "application/json" in result.headers["Content-Type"]:
37
+ return result.json()
38
+ return result.content
39
+
40
+ except Exception:
41
+ return None
42
+
43
+ def __get_documents_recursive(
44
+ self, folderId="", currentLocation="", currentDocuments=[]
45
+ ):
46
+ data = self.__POST(f"documents/{folderId}")
47
+
48
+ for item in data:
49
+ self.logger.debug(f"Checking item: {item}")
50
+
51
+ if "fileType" in item:
52
+ item["location"] = currentLocation
53
+ currentDocuments.append(item)
54
+ else:
55
+ self.logger.debug(
56
+ f"Getting documents over {item[self.ID_ATTRIBUTE]}, current location is {currentLocation}/{item[self.NAME_ATTRIBUTE]}"
57
+ )
58
+ self.__get_documents_recursive(
59
+ item[self.ID_ATTRIBUTE],
60
+ f"{currentLocation}/{item[self.NAME_ATTRIBUTE]}",
61
+ currentDocuments,
62
+ )
63
+
64
+ return currentDocuments
65
+
66
+ def __get_folder_id(self, folderName, _from=""):
67
+ results = self.__POST(f"documents/{_from}")
68
+
69
+ if results is None:
70
+ return None
71
+
72
+ results.reverse() # We only want folders
73
+
74
+ for data in results:
75
+ self.logger.debug(f"Folder: {data}")
76
+
77
+ if "fileType" in data:
78
+ return None
79
+
80
+ if data[self.NAME_ATTRIBUTE].strip() == folderName.strip():
81
+ return data[self.ID_ATTRIBUTE]
82
+
83
+ self.logger.debug(
84
+ f"Getting folders over {folderName}, {data[self.ID_ATTRIBUTE]}"
85
+ )
86
+
87
+ recursiveResults = self.__get_folder_id(folderName, data[self.ID_ATTRIBUTE])
88
+ if recursiveResults is None:
89
+ continue
90
+ else:
91
+ return recursiveResults
92
+
93
+ def __get_docs(self, folderName="", recursive=True):
94
+ folderId = ""
95
+
96
+ if folderName:
97
+ folderId = self.__get_folder_id(folderName)
98
+
99
+ if folderId is None:
100
+ return {}
101
+
102
+ if recursive:
103
+ self.logger.debug(f"Calling recursive function on {folderName}")
104
+ return self.__get_documents_recursive(
105
+ folderId=folderId, currentLocation=folderName
106
+ )
107
+
108
+ data = self.__POST(f"documents/{folderId}")
109
+
110
+ for item in data:
111
+ item["location"] = ""
112
+
113
+ return [item for item in data if "fileType" in item]
114
+
115
+ def download(self, document, location="", overwrite=False):
116
+ filename = document[self.NAME_ATTRIBUTE]
117
+ if "/" in filename:
118
+ filename = filename.replace("/", "_")
119
+
120
+ self.logger.debug(f"Downloading {filename}, location {location}")
121
+
122
+ if not os.path.exists(location):
123
+ self.logger.debug("Download folder does not exist, creating it")
124
+ os.makedirs(location)
125
+
126
+ try:
127
+ fileLocation = f"{location}/{filename}.pdf"
128
+
129
+ if os.path.isfile(fileLocation) and overwrite is False:
130
+ self.logger.debug("Not overwriting file")
131
+ return True
132
+
133
+ binaryData = self.__POST(
134
+ f"download/{document[self.ID_ATTRIBUTE]}/placeholder"
135
+ )
136
+
137
+ if isinstance(binaryData, dict):
138
+ print(f"Error trying to download {filename}: {binaryData}")
139
+ return False
140
+
141
+ with open(fileLocation, "wb") as outFile:
142
+ outFile.write(binaryData)
143
+
144
+ return True
145
+
146
+ except Exception as error:
147
+ print(f"Error trying to download {filename}: {error}")
148
+ return False
149
+
150
+ def upload(self, input_paths, remoteFolder):
151
+ folderId = ""
152
+ if remoteFolder:
153
+ folderId = self.__get_folder_id(remoteFolder)
154
+
155
+ if folderId is None:
156
+ raise SystemExit(f"Error: Folder {remoteFolder} does not exist!")
157
+
158
+ self.__POST(f"documents/{folderId}") # Setting up for upload...
159
+
160
+ errors, documents = [], []
161
+
162
+ for document in input_paths: # This needs improvement...
163
+ if os.path.isdir(document):
164
+ for file in glob.glob(f"{document}/*"):
165
+ if not file.endswith(".pdf"):
166
+ self.logger.error(f"Error: {document} is not a pdf!")
167
+ else:
168
+ documents.append(file)
169
+ elif os.path.isfile(document):
170
+ if not document.endswith(".pdf"):
171
+ errors.append(document)
172
+ self.logger.error(f"Error: {document} is not a pdf!")
173
+ else:
174
+ documents.append(document)
175
+ else:
176
+ errors.append(document)
177
+ self.logger.error(f"Error: {document} is not a file or directory!")
178
+
179
+ for document in documents:
180
+ self.logger.debug(
181
+ f"Uploading {document} to {remoteFolder if remoteFolder else 'root'}"
182
+ )
183
+ with open(document, "rb") as inFile:
184
+ response = self.__POST("upload", data={"file": inFile}, fileUpload=True)
185
+
186
+ if response is None:
187
+ self.logger.error(
188
+ f"Error: Unknown error while uploading {document}!"
189
+ )
190
+ errors.append(document)
191
+ elif response == {"status": "Upload successful"}:
192
+ self.logger.debug(f"Uploaded {document} successfully!")
193
+
194
+ if len(errors) > 0:
195
+ print("The following files failed to upload: " + ",".join(errors))
196
+
197
+ print(f"Done! {len(documents)-len(errors)} files were uploaded.")
198
+
199
+ def sync(self, localFolder, remoteFolder="", overwrite=False, recursive=True):
200
+ count = 0
201
+
202
+ if not os.path.exists(localFolder):
203
+ self.logger.debug("Local folder does not exist, creating it")
204
+ os.mkdir(localFolder)
205
+
206
+ documents = self.__get_docs(remoteFolder, recursive)
207
+
208
+ if documents == {}:
209
+ print("No documents were found!")
210
+
211
+ else:
212
+ for doc in documents:
213
+ self.logger.debug(f"Processing {doc}")
214
+ count += 1
215
+ self.download(
216
+ doc, f"{localFolder}/{doc['location']}", overwrite=overwrite
217
+ )
218
+ print(f"Done! {count} files were exported.")