CodeCast_Client/sync_client.py
2026-02-26 10:34:49 +01:00

362 lines
11 KiB
Python

import platform
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import os
import requests
import json
import threading
import queue
class SyncClient:
@classmethod
def __convert_to_unix_path(cls, path: str) -> str:
if platform.system() == "Windows":
return path.replace("\\", "/").removeprefix("/")
else:
return path.removeprefix("/")
def start(self) -> None:
_sself = self
self.__event_queue = queue.Queue()
def comm_thread_routine():
while True:
event = self.__event_queue.get()
rel_path = str(event.src_path).removeprefix(_sself.dir())
if event.event_type == "created" or event.event_type == "modified":
_sself.upload(rel_path)
elif event.event_type == "moved":
src_rel_path = rel_path
dst_rel_path = str(event.dest_path).removeprefix(_sself.dir())
_sself.move(src_rel_path, dst_rel_path)
pass
elif event.event_type == "deleted":
_sself.delete(rel_path)
pass
class FileChangeHandler(FileSystemEventHandler):
EVENTS_WATCHED = [ "moved", "deleted", "created", "modified" ]
def on_any_event(self, event):
if event.event_type in FileChangeHandler.EVENTS_WATCHED:
if event.src_path != _sself.dir():
_sself.__event_queue.put(event)
self.__comm_thread = threading.Thread()
observer = Observer()
event_handler = FileChangeHandler()
observer.schedule(event_handler, self.__dir, recursive=True)
observer.start()
self.__observer = observer
def __init__(self, dir: str, url: str, id: str, key: str) -> None:
self.__dir = dir
self.__url = url
self.__id = id
self.__key = key
self.__comm_thread: threading.Thread
self.__event_queue: queue.Queue
self.start()
def dir(self) -> str:
return self.__dir
def _get_file_path(self, filename: str) -> str:
return self.__dir + os.sep + filename
def _get_file_type(self, filename: str) -> str:
path = self._get_file_path(filename)
if os.path.isdir(path):
return "directory"
else:
return "file"
def upload(self, filename: str) -> bool:
print("[UPLOAD]", filename)
params = {
"action": "upload",
"filename": SyncClient.__convert_to_unix_path(filename),
"type": self._get_file_type(filename),
"session_id": self.__id,
"session_key": self.__key
}
if params["type"] == "file":
path = self.__dir + os.sep + filename
with open(path, "rb") as f:
requests.post(self.__url, data=f.read(), headers=params)
return True
elif params["type"] == "directory":
requests.post(self.__url, headers=params)
return True
return False
def delete(self, filename: str) -> bool:
print("[DELETE]", filename)
params = {
"action": "delete",
"filename": SyncClient.__convert_to_unix_path(filename),
"type": self._get_file_type(filename),
"session_id": self.__id,
"session_key": self.__key
}
requests.post(self.__url, headers=params)
return True
def wipe(self) -> bool:
print("[WIPE] workspace")
params = {
"action": "wipe",
"filename": "*",
"type": "*",
"session_id": self.__id,
"session_key": self.__key
}
requests.post(self.__url, headers=params)
return True
def move(self, src: str, dst: str) -> bool:
print("[MOVE] ", src, "->", dst)
params = {
"action": "move",
"filename": SyncClient.__convert_to_unix_path(src),
"filename2": SyncClient.__convert_to_unix_path(dst),
"type": self._get_file_type(dst),
"session_id": self.__id,
"session_key": self.__key
}
requests.post(self.__url, headers=params)
return True
def close(self) -> None:
self.__observer.stop()
def upload_all(self) -> None:
# create directories first
def diriter(path, rel_path = "", types = [ "directory", "file" ]):
listing = []
for entry in os.scandir(path):
path = (rel_path + os.sep + entry.name).removeprefix(os.sep)
name = entry.name.removeprefix(os.sep)
if entry.is_dir():
if "directory" in types:
listing.append({"type": "directory", "path": path, "name": name})
dir_listing = diriter(entry.path, rel_path=path, types=types)
listing.extend(dir_listing)
else:
if "file" in types:
listing.append({"type": "file",
"path": path,
"name": name})
return listing
dirs = diriter(self.dir(), types=["directory"])
for dir in dirs:
self.upload(dir["path"])
# then upload the files
files = diriter(self.dir(), types=["file"])
for file in files:
self.upload(file["path"])
pass
# ------------------
class SyncClientGui:
SETTINGS_FILE = "settings.json"
def __init_gui(self) -> None:
win = tk.Tk()
win.title("CodeCast Kliens")
win.geometry("400x120")
self.__win = win
frame = ttk.Frame(win)
frame.pack(fill="both")
dirsel_frame = ttk.Frame(frame)
dirsel_frame.pack(fill="x")
dirsel_label = ttk.Label(dirsel_frame, text="Könyvtár:", width=8)
dirsel_label.pack(side=tk.LEFT)
def select_directory():
sel_dir = filedialog.askdirectory()
if sel_dir is not None and sel_dir != "":
self.__dir = sel_dir
dirsel_btn.configure(text=sel_dir)
dirsel_btn = ttk.Button(dirsel_frame, text="(nincs kiválasztva)", command=select_directory)
dirsel_btn.pack(side=tk.RIGHT, fill="x")
url_frame = ttk.Frame(frame)
url_frame.pack(fill="x")
url_label = ttk.Label(url_frame, text="URL:", width=8)
url_label.pack(side=tk.LEFT)
url_tf = ttk.Entry(url_frame)
url_tf.pack(fill="x", expand=True, side=tk.RIGHT)
id_frame = ttk.Frame(frame)
id_frame.pack(fill="x")
id_label = ttk.Label(id_frame, text="ID:", width=8)
id_label.pack(side=tk.LEFT)
id_tf = ttk.Entry(id_frame)
id_tf.pack(fill="x", expand=True, side=tk.RIGHT)
key_frame = ttk.Frame(frame)
key_frame.pack(fill="x")
key_label = ttk.Label(key_frame, text="Kulcs:", width=8)
key_label.pack(side=tk.LEFT)
key_tf = ttk.Entry(key_frame, show="*")
key_tf.pack(fill="x", expand=True, side=tk.RIGHT)
action_frame = ttk.Frame(frame)
action_frame.pack(fill="x")
def toggle_sync():
if not self.__sync_enabled:
self.__url = url_tf.get().strip().removesuffix("/") + "/sync"
self.__id = id_tf.get().strip()
self.__key = key_tf.get().strip()
if self.__url != "" and self.__id != "" and self.__key != "" and self.__dir != "":
self.start_sync()
else:
self.stop_sync()
if self.__sync_enabled:
on_off_btn.config(text="Szinkronizálás KI")
state = tk.DISABLED
else:
on_off_btn.config(text="Szinkronizálás BE")
state = tk.NORMAL
dirsel_btn.configure(state=state)
url_tf.configure(state=state)
id_tf.configure(state=state)
key_tf.configure(state=state)
on_off_btn = ttk.Button(action_frame, text="Szinkronizálás BE", command=toggle_sync)
on_off_btn.pack(fill="x")
self.__url_tf = url_tf
self.__id_tf = id_tf
self.__key_tf = key_tf
self.__dirsel_btn = dirsel_btn
def __load_last_settings(self) -> None:
try:
with open(SyncClientGui.SETTINGS_FILE, "r") as sf:
settings = json.loads(sf.read())
self.__dirsel_btn.configure(text=settings.get("dir", "(nincs kiválasztva)"))
self.__dir = settings.get("dir", "")
self.__url_tf.insert(0, settings.get("url", ""))
self.__id_tf.insert(0, settings.get("id", ""))
self.__key_tf.insert(0, settings.get("key", ""))
except:
pass
def __save_settings(self) -> None:
with open(SyncClientGui.SETTINGS_FILE, "w") as sf:
settings = {
"dir": self.__dir,
"url": self.__url_tf.get(),
"id": self.__id_tf.get(),
"key": self.__key_tf.get(),
}
sf.write(json.dumps(settings))
def __init__(self) -> None:
self.__dir = ""
self.__url = ""
self.__id = ""
self.__key = ""
self.__sc: SyncClient
self.__sync_enabled = False
self.__init_gui()
self.__load_last_settings()
def on_close():
self.__save_settings()
self.__win.destroy()
self.stop_sync()
self.__win.protocol("WM_DELETE_WINDOW", on_close)
def start_sync(self) -> None:
if not self.__sync_enabled:
self.__sc = SyncClient(self.__dir, self.__url, self.__id, self.__key)
self.__sc.wipe()
self.__sc.upload_all()
self.__sc.start()
self.__sync_enabled = True
def stop_sync(self) -> None:
if self.__sync_enabled:
self.__sc.close()
self.__sync_enabled = False
def mainloop(self) -> None:
self.__win.mainloop()
# ------------------
if __name__ == "__main__":
gui = SyncClientGui()
gui.mainloop()