From 30256ba6de2d666ec6d986d99c48f785306019b5 Mon Sep 17 00:00:00 2001 From: Lorenz H <31415724+lorenzhohmann@users.noreply.github.com> Date: Tue, 10 Jan 2023 10:40:04 +0100 Subject: [PATCH 1/3] fixed localSyncPath (#50) --- ui/settingsui.py | 2 +- utils/file.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/settingsui.py b/ui/settingsui.py index 6ea1f4b..b776c3d 100644 --- a/ui/settingsui.py +++ b/ui/settingsui.py @@ -167,7 +167,7 @@ def createLocalSyncPathInput(self): syncDirectoryPathLabel = QtWidgets.QLabel("Local Sync Directory") rowLayout.addWidget(syncDirectoryPathLabel) - syncFolderPath = LocalAppManager.getSetting("localSyncFolderPath") + syncFolderPath = LocalAppManager.getSetting("syncFolderPath") self.localSyncPathInput = QtWidgets.QLineEdit(syncFolderPath) rowLayout.addWidget(self.localSyncPathInput) diff --git a/utils/file.py b/utils/file.py index 1327dd8..d24339d 100644 --- a/utils/file.py +++ b/utils/file.py @@ -53,7 +53,7 @@ def uniqueFilePath(path): def removeBaseURL(path, isFile): syncDirectoryPath = LocalAppManager.getSetting( - "localSyncFolderPath") + "syncFolderPath") localPathLength = len(syncDirectoryPath) pathLength = len(path) path = path[localPathLength:pathLength] From f52fffbf7cd7da1ccc25ce741dc83bfc0e4efadd Mon Sep 17 00:00:00 2001 From: Lorenz H <31415724+lorenzhohmann@users.noreply.github.com> Date: Tue, 17 Jan 2023 11:15:06 +0100 Subject: [PATCH 2/3] Develop (#55) * fixed localSyncPath * added drive support for sync dir * finalized Status Badge on Settings UI (#51) * preapred syncstate, still in production (#54) * testing sync with 2 clients * testing sync again * prep gui for new version --- services/login.py | 14 +-- ...up_syncer.py => permanent_sync_handler.py} | 88 +++++++++++++++++-- .../sync_handlers/directorysynchandler.py | 22 +++-- services/sync_handlers/filesynchandler.py | 29 +++--- services/thundersynchandler.py | 5 +- test_sync/foo.txt | 1 + ui/settings_interval_handler.py | 6 +- ui/settingsui.py | 14 +-- utils/file.py | 8 ++ 9 files changed, 141 insertions(+), 46 deletions(-) rename services/{startup_syncer.py => permanent_sync_handler.py} (66%) create mode 100644 test_sync/foo.txt diff --git a/services/login.py b/services/login.py index d9142a9..ce89a90 100644 --- a/services/login.py +++ b/services/login.py @@ -3,12 +3,12 @@ from services.localappmanager import LocalAppManager from ui.settings_interval_handler import SettingsIntervalHandler from services.thundersynchandler import ThunderSyncHandler -from services.startup_syncer import StartupSyncer +from services.permanent_sync_handler import PermanentSyncHandler from utils.request import getRequestHeaders, getRequestURL def isLoggedIn(): - + if LocalAppManager.getSetting("serverURL") == "": return False @@ -32,8 +32,8 @@ def login(email, password, serverURL, openSetingsScreen): pwHash = hashPassword(password) # save ServerURL - LocalAppManager.saveSetting("serverURL", serverURL); - + LocalAppManager.saveSetting("serverURL", serverURL) + requestURL = getRequestURL("/user/login") loginData = {"email": email, "pw_hash": pwHash} response = requests.post(url=requestURL, json=loginData) @@ -42,7 +42,6 @@ def login(email, password, serverURL, openSetingsScreen): if response.status_code != 200: return "Login failed: " + response.text - jsonResponse = response.json() # handle unknown user @@ -64,6 +63,7 @@ def logout(openLoginScreen): if response.status_code == 200: ThunderSyncHandler.STATUS = 0 SettingsIntervalHandler.RUNNING = False + PermanentSyncHandler.STATUS = 0 LocalAppManager.removeJWTLocally() openLoginScreen() return True @@ -77,8 +77,8 @@ def hashPassword(string): def doAfterLoginActions(): if isLoggedIn(): - startupSyncer = StartupSyncer() - startupSyncer.start() + permanentSyncHandler = PermanentSyncHandler() + permanentSyncHandler.runStartup() sync_handler = ThunderSyncHandler() sync_handler.run() diff --git a/services/startup_syncer.py b/services/permanent_sync_handler.py similarity index 66% rename from services/startup_syncer.py rename to services/permanent_sync_handler.py index 253ff71..311942c 100644 --- a/services/startup_syncer.py +++ b/services/permanent_sync_handler.py @@ -1,6 +1,8 @@ import shutil import os +import time import io +import threading from glob import iglob from services.localappmanager import LocalAppManager import requests @@ -10,11 +12,72 @@ from services.sync_handlers.filesynchandler import FileSyncHandler -class StartupSyncer: +class PermanentSyncHandler: - def start(self): + # global variable to set the observers state (0 = offline, 1 = running, 2 = syncing) + STATUS = 1 + + def __init__(self): self.syncDirectoryPath = LocalAppManager.getSetting( "syncFolderPath") + self.lastCheck = self.getTimestamp() + self.run() + + def run(self): + t1 = threading.Thread(target=self.start) + t1.start() + + def start(self): + print("[INFO] Starting permanent syncronisation...") + + # self.runStartup() + + try: + while PermanentSyncHandler.STATUS != 0: + time.sleep(5) + + if PermanentSyncHandler.STATUS == 1: + self.runStartup() # Fallback for sync + except: + print("PermanentSyncHandler error") + + # TODO: IN PRODUCTION + def doRemoteCheck(self): + # do server request + request_url = getRequestURL("/user/syncstate") + request_url += "?since=" + str(self.lastCheck) + headers = getRequestHeaders() + + # build request data + response = requests.get(url=request_url, json={}, headers=headers) + changes = response.json() + + for change in changes: + # CREATE + if change["type"] == "File" and change["action"] == "create": + print("PermanentSyncHandler: Create File") + if change["type"] == "Directory" and change["action"] == "create": + print("PermanentSyncHandler: Create Directory") + + # RENAME + if change["type"] == "File" and change["action"] == "rename": + print("PermanentSyncHandler: Rename File") + if change["type"] == "Directory" and change["action"] == "rename": + print("PermanentSyncHandler: Rename Directory") + + # DELETE + if change["type"] == "File" and change["action"] == "delete": + print("PermanentSyncHandler: Delete File") + if change["type"] == "Directory" and change["action"] == "delete": + print("PermanentSyncHandler: Delete Directory") + + self.lastCheck = self.getTimestamp() + + def getTimestamp(self): + return int(time.time()*1000.0) + + def runStartup(self): + PermanentSyncHandler.STATUS = 2 print("[INFO] Sync local folder '" + self.syncDirectoryPath + "' with remote server...") @@ -26,15 +89,20 @@ def start(self): directoriesNotToSync = LocalAppManager.getSetting("notToSyncFolders") # create local directories - self.remoteFilesAndDirectories = StartupSyncer.__downloadRemoteContentRecursive( + self.remoteFilesAndDirectories = PermanentSyncHandler.__downloadRemoteContentRecursive( self, None, "", directoriesNotToSync) # delete local directories that does not exists on the server - StartupSyncer.__deleteFilesAndDirectoriessNotOnServer( + PermanentSyncHandler.__deleteFilesAndDirectoriessNotOnServer( self, directoriesNotToSync) print("[INFO] Sync local folder done") + PermanentSyncHandler.STATUS = 1 + # self.run() # TODO: incomment when using doRemoteCheck() + # if PermanentSyncHandler.STATUS == 2: + # self.run() + @staticmethod def __downloadRemoteContentRecursive(self, parent_id=None, path="", directoriesNotToSync=[]): result = [] @@ -58,7 +126,8 @@ def __downloadRemoteContentRecursive(self, parent_id=None, path="", directoriesN # download files if len(files): for file in files: - fileResult = StartupSyncer.__handleFile(self, file, path) + fileResult = PermanentSyncHandler.__handleFile( + self, file, path) result.append(fileResult) # loop dirs @@ -83,8 +152,8 @@ def __downloadRemoteContentRecursive(self, parent_id=None, path="", directoriesN # only get children if folder should be synced if not directoryID in directoriesNotToSync: - result += StartupSyncer.__downloadRemoteContentRecursive(self, - directoryID, childPath, directoriesNotToSync) + result += PermanentSyncHandler.__downloadRemoteContentRecursive(self, + directoryID, childPath, directoriesNotToSync) result.append(directory) @@ -106,7 +175,7 @@ def __handleFile(self, file, path): # if local file does not exists => download if not os.path.isfile(filePath): - StartupSyncer.__downloadFile(fileID, filePath) + PermanentSyncHandler.__downloadFile(fileID, filePath) else: # check dates for newer file @@ -117,7 +186,7 @@ def __handleFile(self, file, path): # if remote modified date is newer => download file, else upload file if remoteModifiedDate > localModifiedDate: - StartupSyncer.__downloadFile(fileID, filePath) + PermanentSyncHandler.__downloadFile(fileID, filePath) else: FileSyncHandler.createFile(filePath) @@ -136,6 +205,7 @@ def __downloadFile(fileID, filePath): headers=headers, stream=True) # create and write local file + filePath = uniqueFilePath(filePath) try: fileHandle = io.open(filePath, "wb") response.raw.decode_content = True diff --git a/services/sync_handlers/directorysynchandler.py b/services/sync_handlers/directorysynchandler.py index 6574630..51a9f4c 100644 --- a/services/sync_handlers/directorysynchandler.py +++ b/services/sync_handlers/directorysynchandler.py @@ -23,7 +23,9 @@ def handle(event): @staticmethod def __createDirectory(src): - # ThunderSyncHandler.STATUS = 2 + from services.thundersynchandler import ThunderSyncHandler + + ThunderSyncHandler.STATUS = 2 print("[INFO] Create directory " + src) directoryName = getDirectoryOrFileName(src) @@ -31,8 +33,6 @@ def __createDirectory(src): remoteDirectory = DirectorySyncHandler.__getRemoteDirectory( directoryPath) - print("remote dir: ") - print(remoteDirectory) # set parent id empty or get parent id if sub folder parentId = "" @@ -46,11 +46,13 @@ def __createDirectory(src): url=request_url, json=data, headers=headers) print("[INFO] Create directory done") - # ThunderSyncHandler.STATUS = 1 + ThunderSyncHandler.STATUS = 1 @staticmethod def __moveDirectory(src, dest): - # ThunderSyncHandler.STATUS = 2 + from services.thundersynchandler import ThunderSyncHandler + + ThunderSyncHandler.STATUS = 2 print("[INFO] Move/Rename directory from " + src + " to " + dest) remoteDirectory = DirectorySyncHandler.__getRemoteDirectory(src) @@ -77,12 +79,14 @@ def __moveDirectory(src, dest): url=request_url, json=data, headers=headers) print("[INFO] Move/Rename directory done") - # ThunderSyncHandler.STATUS = 1 + ThunderSyncHandler.STATUS = 1 @staticmethod def deleteDirectory(src_path): - # ThunderSyncHandler.STATUS = 2 - src_path = src_path.replace("\\", "/") + from services.thundersynchandler import ThunderSyncHandler + + ThunderSyncHandler.STATUS = 2 + src_path = uniqueDirectoryPath(src_path) print("[INFO] Delete directory: " + src_path) remoteDirectory = DirectorySyncHandler.__getRemoteDirectory(src_path) @@ -99,7 +103,7 @@ def deleteDirectory(src_path): " not possible. Directory not found on server") print("[INFO] Delete directory done") - # ThunderSyncHandler.STATUS = 1 + ThunderSyncHandler.STATUS = 1 # same as __getRemoteDirectory() in FileSyncHandler @staticmethod diff --git a/services/sync_handlers/filesynchandler.py b/services/sync_handlers/filesynchandler.py index 8c766df..f27468c 100644 --- a/services/sync_handlers/filesynchandler.py +++ b/services/sync_handlers/filesynchandler.py @@ -28,7 +28,9 @@ def handle(event): @staticmethod def createFile(src): - # ThunderSyncHandler.STATUS = 2 + from services.thundersynchandler import ThunderSyncHandler + + ThunderSyncHandler.STATUS = 2 print("[INFO] Create file: " + src) # get current directory @@ -37,6 +39,7 @@ def createFile(src): if remoteDirectory: try: + src = uniqueFilePath(src) fileHandle = open(src, "rb") files = {"file": fileHandle} @@ -56,11 +59,13 @@ def createFile(src): directoryPath + ", skip file") print("[INFO] Create file done") - # ThunderSyncHandler.STATUS = 1 + ThunderSyncHandler.STATUS = 1 @staticmethod def __renameFile(src, dest): - # ThunderSyncHandler.STATUS = 2 + from services.thundersynchandler import ThunderSyncHandler + + ThunderSyncHandler.STATUS = 2 print("[INFO] Rename file " + src + " to " + dest) remoteFile = FileSyncHandler.__getRemoteFile(src) @@ -77,12 +82,14 @@ def __renameFile(src, dest): url=request_url, json=data, headers=headers) print("[INFO] Renamed file done.") - # ThunderSyncHandler.STATUS = 1 + ThunderSyncHandler.STATUS = 1 @staticmethod def deleteFile(src_path): - # ThunderSyncHandler.STATUS = 2 - src_path = src_path.replace("\\", "/") + from services.thundersynchandler import ThunderSyncHandler + + ThunderSyncHandler.STATUS = 2 + src_path = uniqueFilePath(src_path) print("[INFO] Delete file: " + src_path) remoteFile = FileSyncHandler.__getRemoteFile(src_path) @@ -102,11 +109,13 @@ def deleteFile(src_path): " not possible, not found on the server") print("[INFO] Delete file done") - # ThunderSyncHandler.STATUS = 1 + ThunderSyncHandler.STATUS = 1 @staticmethod def __modifyFile(src_path): - # ThunderSyncHandler.STATUS = 2 + from services.thundersynchandler import ThunderSyncHandler + + ThunderSyncHandler.STATUS = 2 src_path = src_path.replace("\\", "/") print("[INFO] Modify file (delete and create): " + src_path) @@ -117,7 +126,7 @@ def __modifyFile(src_path): FileSyncHandler.createFile(src_path) print("[INFO] Modify file (delete and create) done") - # ThunderSyncHandler.STATUS = 1 + ThunderSyncHandler.STATUS = 1 @staticmethod def __getRemoteFile(path): @@ -138,7 +147,7 @@ def __getRemoteDirectory(dir_path): # search for sync directory by path syncDirectories = ServerSettings.getSyncDirectories(False, True) - print(syncDirectories) + # print(syncDirectories) for syncDirectory in syncDirectories: if "path" in syncDirectory and syncDirectory["path"] == dir_path: diff --git a/services/thundersynchandler.py b/services/thundersynchandler.py index 9ca47f6..1bf121f 100644 --- a/services/thundersynchandler.py +++ b/services/thundersynchandler.py @@ -36,7 +36,7 @@ def start(self): self.observer.start() try: while ThunderSyncHandler.STATUS != 0: - time.sleep(3) + time.sleep(10) except: self.observer.stop() print("ThunderSyncHandler Observer error") @@ -63,7 +63,8 @@ def __deleteFileOrDirectory(event): ThunderSyncHandler.STATUS = 2 filePath = removeBaseURL(event.src_path, True) - directoryPath = removeBaseURL(event.src_path, False) + directoryPath = uniqueDirectoryPath( + removeBaseURL(event.src_path, False)) deleteType = 0 # 0 = undefined, 1 = file, 2 = directory remoteObject = {} diff --git a/test_sync/foo.txt b/test_sync/foo.txt new file mode 100644 index 0000000..f6ea049 --- /dev/null +++ b/test_sync/foo.txt @@ -0,0 +1 @@ +foobar \ No newline at end of file diff --git a/ui/settings_interval_handler.py b/ui/settings_interval_handler.py index 1d442c7..4e606f6 100644 --- a/ui/settings_interval_handler.py +++ b/ui/settings_interval_handler.py @@ -9,6 +9,7 @@ class SettingsIntervalHandler: def run(self, statusBadge): self.statusBadge = statusBadge + self.i = 1 t1 = threading.Thread(target=self.start) t1.start() @@ -20,9 +21,10 @@ def start(self): if ThunderSyncHandler.STATUS == 0: status = "offline" if ThunderSyncHandler.STATUS == 1: - status = "waiting for changes" + status = "waiting for changes..." if ThunderSyncHandler.STATUS == 2: - status = "syncing" + status = "syncing..." + self.statusBadge.setText("Status: " + status) time.sleep(1) except: diff --git a/ui/settingsui.py b/ui/settingsui.py index b776c3d..e81582a 100644 --- a/ui/settingsui.py +++ b/ui/settingsui.py @@ -6,7 +6,7 @@ from services.localappmanager import LocalAppManager from services.login import logout from services.thundersynchandler import ThunderSyncHandler -from services.startup_syncer import StartupSyncer +from services.permanent_sync_handler import PermanentSyncHandler class SettingsUI(QtWidgets.QWidget): @@ -51,7 +51,7 @@ def createInfoArea(self): self.infoBoxLayout = QtWidgets.QVBoxLayout() infoBox.setLayout(self.infoBoxLayout) - # ServerURL + # add ServerURL urlString = "Server URL: " + LocalAppManager.getSetting("serverURL") serverURLLabel = QtWidgets.QLabel(urlString) font = serverURLLabel.font() @@ -59,9 +59,8 @@ def createInfoArea(self): serverURLLabel.setFont(font) self.infoBoxLayout.addWidget(serverURLLabel) - # + # add Status Badge with async Handler statusBadge = QtWidgets.QLabel("Status: unknown") - settingsIntervalHandler = SettingsIntervalHandler() settingsIntervalHandler.run(statusBadge) self.infoBoxLayout.addWidget(statusBadge) @@ -147,7 +146,7 @@ def createAboutArea(self): aboutLine1 = QtWidgets.QLabel("Thunderklaud Desktop-Client") self.aboutBoxLayout.addWidget(aboutLine1) - aboutLine2 = QtWidgets.QLabel("Version 1.1.3") + aboutLine2 = QtWidgets.QLabel("Version 1.1.5") self.aboutBoxLayout.addWidget(aboutLine2) aboutLine3 = QtWidgets.QLabel( @@ -207,8 +206,8 @@ def clickedLogout(self): self.showNotification("Error: Logout not possible!") def clickedReSync(self): - startupSyncer = StartupSyncer() - startupSyncer.start() + permanentSyncHandler = PermanentSyncHandler() + permanentSyncHandler.runStartup() def showNotification(self, text): # remove old notifcation if exists @@ -231,4 +230,5 @@ def getFoldersNotToSync(self): def closeEvent(self, event): ThunderSyncHandler.STATUS = 0 + PermanentSyncHandler.STATUS = 0 SettingsIntervalHandler.RUNNING = False diff --git a/utils/file.py b/utils/file.py index d24339d..5487b98 100644 --- a/utils/file.py +++ b/utils/file.py @@ -26,6 +26,10 @@ def uniqueDirectoryPath(path): if not beginningSlash and not beginningDot: path = "/" + path + # remove slash when drive + if path[0] == "/" and len(path) > 2 and path[2] == ":": + path = path[1:] + path = path.replace("//", "/") path = path.replace("\\", "/") @@ -45,6 +49,10 @@ def uniqueFilePath(path): if not beginningSlash and not beginningDot: path = "/" + path + # remove slash when drive + if path[0] == "/" and len(path) > 2 and path[2] == ":": + path = path[1:] + path = path.replace("//", "/") path = path.replace("\\", "/") From c2c48cf8a1a42d2fefa59ce315b0e84a3846a407 Mon Sep 17 00:00:00 2001 From: MaxusPowerus <83584730+MaxusPowerus@users.noreply.github.com> Date: Mon, 23 Jan 2023 10:36:16 +0100 Subject: [PATCH 3/3] Develop (#61) * default syncFolderPath: home * Filecounter (#57) * show filecount * Bugfix: RefreshButton disappear * Fix: SettingsUI refresh button dissappears * changed to server2client sync * server->client sync (#58) * add syncMode (#60) * prep for new version Co-authored-by: LH Co-authored-by: Lorenz H <31415724+lorenzhohmann@users.noreply.github.com> --- services/localappmanager.py | 4 ++- services/permanent_sync_handler.py | 40 +++++++++++++---------- services/server_settings.py | 3 +- services/sync_handlers/filesynchandler.py | 9 +++++ services/thundersynchandler.py | 5 +++ ui/settings_interval_handler.py | 3 +- ui/settingsui.py | 33 +++++++++++++++---- 7 files changed, 69 insertions(+), 28 deletions(-) diff --git a/services/localappmanager.py b/services/localappmanager.py index 5bb8aaa..57b3337 100644 --- a/services/localappmanager.py +++ b/services/localappmanager.py @@ -46,13 +46,15 @@ def createDefaultSettingsJson(): return defaultServerURL = "" - defaultSyncFolderPath = "./test/client/" + defaultSyncFolderPath = str(Path.home()) + "/ThunderklaudSyncFolder" + # "./test/client/" settings = {} settings["serverUrl"] = defaultServerURL settings["syncFolderPath"] = defaultSyncFolderPath settings["notToSyncFolders"] = [] + settings["syncMode"] = "Server > Client" settings = json.dumps(settings) diff --git a/services/permanent_sync_handler.py b/services/permanent_sync_handler.py index 311942c..f47e374 100644 --- a/services/permanent_sync_handler.py +++ b/services/permanent_sync_handler.py @@ -9,13 +9,14 @@ from config import Config from utils.file import uniqueDirectoryPath, uniqueFilePath, remoteFileOrDirectoryExists, removeBaseURL from utils.request import getRequestURL, getRequestHeaders -from services.sync_handlers.filesynchandler import FileSyncHandler +from services.thundersynchandler import ThunderSyncHandler class PermanentSyncHandler: # global variable to set the observers state (0 = offline, 1 = running, 2 = syncing) STATUS = 1 + SYNCED_PATHS = [] def __init__(self): self.syncDirectoryPath = LocalAppManager.getSetting( @@ -30,13 +31,12 @@ def run(self): def start(self): print("[INFO] Starting permanent syncronisation...") - # self.runStartup() - try: while PermanentSyncHandler.STATUS != 0: - time.sleep(5) + time.sleep(10) # every 10 seconds - if PermanentSyncHandler.STATUS == 1: + # mutex to prevent sync errors with watchdog + if PermanentSyncHandler.STATUS == 1 and ThunderSyncHandler.STATUS == 1: self.runStartup() # Fallback for sync except: print("PermanentSyncHandler error") @@ -78,6 +78,7 @@ def getTimestamp(self): def runStartup(self): PermanentSyncHandler.STATUS = 2 + SYNCED_PATHS = [] print("[INFO] Sync local folder '" + self.syncDirectoryPath + "' with remote server...") @@ -143,6 +144,8 @@ def __downloadRemoteContentRecursive(self, parent_id=None, path="", directoriesN # only create folder if folder should be synced if not directoryID in directoriesNotToSync: if not os.path.isdir(directoryPath): + if not directoryPath in PermanentSyncHandler.SYNCED_PATHS: + PermanentSyncHandler.SYNCED_PATHS.append(directoryPath) os.makedirs(directoryPath) childPath = uniqueDirectoryPath(path + "/" + directoryName) @@ -173,22 +176,25 @@ def __handleFile(self, file, path): fileResult["name"] = fileName fileResult["path"] = uniqueFilePath(path + "/" + fileName) + if not fileResult["path"] in PermanentSyncHandler.SYNCED_PATHS: + PermanentSyncHandler.SYNCED_PATHS.append(fileResult["path"]) + # if local file does not exists => download - if not os.path.isfile(filePath): - PermanentSyncHandler.__downloadFile(fileID, filePath) + # if not os.path.isfile(filePath): + PermanentSyncHandler.__downloadFile(fileID, filePath) - else: # check dates for newer file + # else: # check dates for newer file - # compare modified dates - remoteModifiedDate = float( - file["creation_date"]["$date"]["$numberLong"]) / 1000 # * 1000 to get timestamp in seconds - localModifiedDate = float(os.path.getmtime(filePath)) + # compare modified dates + # remoteModifiedDate = float( + # file["creation_date"]["$date"]["$numberLong"]) / 1000 # * 1000 to get timestamp in seconds + # localModifiedDate = float(os.path.getmtime(filePath)) - # if remote modified date is newer => download file, else upload file - if remoteModifiedDate > localModifiedDate: - PermanentSyncHandler.__downloadFile(fileID, filePath) - else: - FileSyncHandler.createFile(filePath) + # if remote modified date is newer => download file, else upload file + # if remoteModifiedDate > localModifiedDate: + # PermanentSyncHandler.__downloadFile(fileID, filePath) + # else: + # FileSyncHandler.createFile(filePath) return fileResult diff --git a/services/server_settings.py b/services/server_settings.py index fe565a5..bcfc707 100644 --- a/services/server_settings.py +++ b/services/server_settings.py @@ -48,7 +48,6 @@ def __getDirectoryRecursive(parentId=None, recPath="", multidimensionalArray=Tru return [] jsonResponse = response.json() dirs = jsonResponse["dirs"] - files = jsonResponse["files"] # loop the result for dir in dirs: @@ -62,7 +61,7 @@ def __getDirectoryRecursive(parentId=None, recPath="", multidimensionalArray=Tru directory["id"] = directoryID directory["name"] = directoryName directory["path"] = childPath - directory["childCount"] = len(files) + directory["childCount"] = dir["child_file_count"] # set directory to sync/not to sync directory["syncDir"] = not directoryID in directoriesNotToSync diff --git a/services/sync_handlers/filesynchandler.py b/services/sync_handlers/filesynchandler.py index f27468c..44e34e4 100644 --- a/services/sync_handlers/filesynchandler.py +++ b/services/sync_handlers/filesynchandler.py @@ -64,6 +64,7 @@ def createFile(src): @staticmethod def __renameFile(src, dest): from services.thundersynchandler import ThunderSyncHandler + from services.permanent_sync_handler import PermanentSyncHandler ThunderSyncHandler.STATUS = 2 print("[INFO] Rename file " + src + " to " + dest) @@ -114,11 +115,19 @@ def deleteFile(src_path): @staticmethod def __modifyFile(src_path): from services.thundersynchandler import ThunderSyncHandler + from services.permanent_sync_handler import PermanentSyncHandler ThunderSyncHandler.STATUS = 2 src_path = src_path.replace("\\", "/") print("[INFO] Modify file (delete and create): " + src_path) + toCheckSrcPath = removeBaseURL(src_path, True) + if toCheckSrcPath in PermanentSyncHandler.SYNCED_PATHS: + PermanentSyncHandler.SYNCED_PATHS.remove(toCheckSrcPath) + print("[INFO] Done, no action. Change came from PermanentSyncHandler.") + ThunderSyncHandler.STATUS = 1 + return + # delete old file FileSyncHandler.deleteFile(src_path) diff --git a/services/thundersynchandler.py b/services/thundersynchandler.py index 1bf121f..bd29025 100644 --- a/services/thundersynchandler.py +++ b/services/thundersynchandler.py @@ -47,6 +47,11 @@ class SyncHandlerHelper(FileSystemEventHandler): @staticmethod def on_any_event(event): + # mutex to prevent sync errors with PermanentSyncHandler + from services.permanent_sync_handler import PermanentSyncHandler + while PermanentSyncHandler.STATUS == 2: + time.sleep(3) + # delete files and directories if event.event_type == "deleted": SyncHandlerHelper.__deleteFileOrDirectory(event) diff --git a/ui/settings_interval_handler.py b/ui/settings_interval_handler.py index 4e606f6..ac950be 100644 --- a/ui/settings_interval_handler.py +++ b/ui/settings_interval_handler.py @@ -1,6 +1,7 @@ import threading import time from services.thundersynchandler import ThunderSyncHandler +from services.permanent_sync_handler import PermanentSyncHandler class SettingsIntervalHandler: @@ -22,7 +23,7 @@ def start(self): status = "offline" if ThunderSyncHandler.STATUS == 1: status = "waiting for changes..." - if ThunderSyncHandler.STATUS == 2: + if ThunderSyncHandler.STATUS == 2 or PermanentSyncHandler.STATUS == 2: status = "syncing..." self.statusBadge.setText("Status: " + status) diff --git a/ui/settingsui.py b/ui/settingsui.py index e81582a..6b83cef 100644 --- a/ui/settingsui.py +++ b/ui/settingsui.py @@ -105,23 +105,22 @@ def createSyncDirectoriesArea(self): self.contentLayout.addWidget(syncDirectoriesBox) def addSyncDirectories(self): - self.refresh_button.setText("Loading...") - # remove all directories checkboxes count = self.syncDirectoriesLayout.count() - for i in range(1, count): - item = self.syncDirectoriesLayout.itemAt(1).widget() + + for i in range(2, count): + item = self.syncDirectoriesLayout.itemAt(2).widget() item.setParent(None) syncDirectories = ServerSettings.getSyncDirectories() self.addSyncDirectoryRecursive(syncDirectories) - self.refresh_button.setText("↻") def addSyncDirectoryRecursive(self, directory, level=0): perLevelPadding = 7 for dir in directory: - checkboxLabel = dir["name"] + checkboxLabel = dir["name"] + \ + " (Files: " + str(dir["childCount"]) + ")" checkbox = QtWidgets.QCheckBox(checkboxLabel, self) checkbox.setObjectName(dir["id"]) @@ -139,6 +138,7 @@ def createSettingsArea(self): self.createLocalSyncPathInput() settingsBox.setLayout(self.settingsBoxLayout) self.contentLayout.addWidget(settingsBox) + self.createChangeMode() def createAboutArea(self): aboutBox = QtWidgets.QGroupBox("About") @@ -146,7 +146,7 @@ def createAboutArea(self): aboutLine1 = QtWidgets.QLabel("Thunderklaud Desktop-Client") self.aboutBoxLayout.addWidget(aboutLine1) - aboutLine2 = QtWidgets.QLabel("Version 1.1.5") + aboutLine2 = QtWidgets.QLabel("Version 1.2.0") self.aboutBoxLayout.addWidget(aboutLine2) aboutLine3 = QtWidgets.QLabel( @@ -172,6 +172,24 @@ def createLocalSyncPathInput(self): self.settingsBoxLayout.addLayout(rowLayout) + def createChangeMode(self): + rowLayout = QtWidgets.QHBoxLayout() + + syncMode = QtWidgets.QLabel("Sync Mode") + + self.comboBox = QtWidgets.QComboBox(self) + self.comboBox.addItem("Server > Client") + self.comboBox.addItem("Client > Server") + self.comboBox.addItem("Bidirectional") + self.comboBox.setCurrentText(LocalAppManager.getSetting("syncMode")) + + rowLayout.addWidget(syncMode) + rowLayout.addWidget(self.comboBox) + self.settingsBoxLayout.addLayout(rowLayout) + + def getSyncMode(self): + return self.comboBox.currentText() + def getLocalSyncPathInput(self): return self.localSyncPathInput.text() @@ -187,6 +205,7 @@ def clickedSave(self): settings["syncFolderPath"] = self.getLocalSyncPathInput() settings["notToSyncFolders"] = self.getFoldersNotToSync() + settings["syncMode"] = self.getSyncMode() LocalAppManager.saveSettings(settings)