diff --git a/.gitignore b/.gitignore index e2ae38d70..53881cb78 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ IoTuring/Configurator/configurations.json* IoTuring/Configurator/dontmoveconf.itg* .venv build -*.egg-info \ No newline at end of file +*.egg-info +UserFiles/ \ No newline at end of file diff --git a/IoTuring/Configurator/ConfiguratorIO.py b/IoTuring/Configurator/ConfiguratorIO.py index dccffde59..c9005cdf8 100644 --- a/IoTuring/Configurator/ConfiguratorIO.py +++ b/IoTuring/Configurator/ConfiguratorIO.py @@ -6,6 +6,7 @@ from IoTuring.Logger.LogObject import LogObject from IoTuring.MyApp.App import App # App name +from IoTuring.MyApp.Directory import ConfigurationsDirectory # macOS dep (in PyObjC) try: @@ -25,6 +26,7 @@ class ConfiguratorIO(LogObject): def __init__(self): self.directoryName = App.getName() + self.ConfigurationsDirectory = ConfigurationsDirectory() def readConfigurations(self): """ Returns configurations dictionary. If does not exist the file where it should be stored, return None. """ @@ -35,7 +37,7 @@ def readConfigurations(self): self.Log(self.LOG_MESSAGE, "Loaded \"" + self.getFilePath() + "\"") except: self.Log(self.LOG_WARNING, "It seems you don't have a configuration yet. Use configuration mode (-c) to enable your favourite entities and warehouses.\ - \nConfigurations will be saved in \"" + self.getFolderPath() + "\"") + \nConfigurations will be saved in \"" + self.ConfigurationsDirectory.getFolderPath() + "\"") return config def writeConfigurations(self, data): @@ -51,64 +53,15 @@ def checkConfigurationFileExists(self): def getFilePath(self): """ Returns the path to the configurations file. """ - return os.path.join(self.getFolderPath(), CONFIGURATION_FILE_NAME) + return os.path.join(self.ConfigurationsDirectory.getFolderPath(), CONFIGURATION_FILE_NAME) def createFolderPathIfDoesNotExist(self): """ Check if file exists, if not check if path exists: if not create both folder and file otherwise just the file """ - if not os.path.exists(self.getFolderPath()): - if not os.path.exists(self.getFolderPath()): - os.makedirs(self.getFolderPath()) + if not os.path.exists(self.ConfigurationsDirectory.getFolderPath()): + if not os.path.exists(self.ConfigurationsDirectory.getFolderPath()): + os.makedirs(self.ConfigurationsDirectory.getFolderPath()) - def getFolderPath(self): - """ Returns the path to the configurations file. If the directory where the file - will be stored doesn't exist, it will be created. """ - - folderPath = self.defaultFolderPath() - try: - # Use path from environment variable if present, otherwise os specific folders, otherwise use default path - envvarPath = self.envvarFolderPath() - if envvarPath is not None: - folderPath = envvarPath - else: - _os = platform.system() - if _os == 'Darwin' and macos_support: - folderPath = self.macOSFolderPath() - folderPath = os.path.join(folderPath, self.directoryName) - elif _os == "Windows": - folderPath = self.windowsFolderPath() - folderPath = os.path.join(folderPath, self.directoryName) - elif _os == "Linux": - folderPath = self.linuxFolderPath() - folderPath = os.path.join(folderPath, self.directoryName) - except: - pass # default folder path will be used - - # add slash if missing (for log reasons) - if not folderPath.endswith(os.sep): - folderPath += os.sep - - return folderPath - def defaultFolderPath(self): - return os.path.dirname(inspect.getfile(ConfiguratorIO)) - - # https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html - def macOSFolderPath(self): - paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,NSUserDomainMask,True) - basePath = (len(paths) > 0 and paths[0]) or NSTemporaryDirectory() - return basePath - - # https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid - def windowsFolderPath(self): - return os.environ["APPDATA"] - - # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - def linuxFolderPath(self): - return os.environ["XDG_CONFIG_HOME"] if "XDG_CONFIG_HOME" in os.environ else os.path.join(os.environ["HOME"], ".config") - - def envvarFolderPath(self): - return os.getenv(CONFIG_PATH_ENV_VAR) - # In versions prior to 2022.12.2, the configurations file was stored in the same folder as this file def oldFolderPath(self): return os.path.dirname(inspect.getfile(ConfiguratorIO)) @@ -123,7 +76,7 @@ def checkConfigurationFileInOldLocation(self): """ # This check is not done if old path = current chosen path (also check if ending slash is present) - if self.oldFolderPath() == self.getFolderPath() or self.oldFolderPath() == self.getFolderPath()[:-1]: + if self.oldFolderPath() == self.ConfigurationsDirectory.getFolderPath() or self.oldFolderPath() == self.ConfigurationsDirectory.getFolderPath()[:-1]: return # Exit check if no config. in old directory or if there is also the dont move file with it. @@ -139,8 +92,8 @@ def checkConfigurationFileInOldLocation(self): # create folder if not exists self.createFolderPathIfDoesNotExist() # copy file from old to new location - os.rename(os.path.join(self.oldFolderPath(), CONFIGURATION_FILE_NAME), os.path.join(self.getFolderPath(), CONFIGURATION_FILE_NAME)) - self.Log(self.LOG_MESSAGE, "Copied into \"" + os.path.join(self.getFolderPath(), CONFIGURATION_FILE_NAME) + "\"") + os.rename(os.path.join(self.oldFolderPath(), CONFIGURATION_FILE_NAME), os.path.join(self.ConfigurationsDirectory.getFolderPath(), CONFIGURATION_FILE_NAME)) + self.Log(self.LOG_MESSAGE, "Copied into \"" + os.path.join(self.ConfigurationsDirectory.getFolderPath(), CONFIGURATION_FILE_NAME) + "\"") else: # create dont move file with open(os.path.join(self.oldFolderPath(), DONT_MOVE_FILE_FILENAME), "w") as f: diff --git a/IoTuring/Logger/Logger.py b/IoTuring/Logger/Logger.py index 066f764fc..d267b1f51 100644 --- a/IoTuring/Logger/Logger.py +++ b/IoTuring/Logger/Logger.py @@ -2,11 +2,12 @@ import IoTuring.Logger.consts as consts from IoTuring.Logger.Colors import Colors import sys -import os # to access directory functions -import inspect # to get this file path +import os from datetime import datetime # for logging purpose and filename import json # to print a dict easily import threading # to lock the file descriptor +from IoTuring.MyApp.Directory import LogsDirectory +from IoTuring.MyApp.App import App # Singleton pattern used @@ -17,7 +18,7 @@ class Logger(): lock = threading.Lock() - log_filename = "" + log_path = "" log_file_descriptor = None def __init__(self) -> None: @@ -31,25 +32,26 @@ def __init__(self) -> None: self.terminalSupportsColors = Logger.checkTerminalSupportsColors() # Prepare the log - self.SetLogFilename() + self.SetLogPath() # Open the file descriptor self.GetLogFileDescriptor() - def SetLogFilename(self) -> str: - """ Set filename with timestamp and also call setup folder """ + def SetLogPath(self) -> str: + """ Set filename with timestamp and also call setup folder to create log folder if does not exist (and get folder for the file)""" dateTimeObj = datetime.now() - self.log_filename = os.path.join( - self.SetupFolder(), dateTimeObj.strftime(consts.LOG_FILENAME_FORMAT).replace(":", "_")) - return self.log_filename + self.log_path = os.path.join( + self.SetupFolder(), dateTimeObj.strftime(consts.LOG_FILENAME_FORMAT.format(App.getName())).replace(":", "_")) + print(self.log_path) + return self.log_path def SetupFolder(self) -> str: - """ Check if exists (or create) the folder of logs inside this file's folder """ - thisFolder = os.path.dirname(inspect.getfile(Logger)) - newFolder = os.path.join(thisFolder, consts.LOGS_FOLDER) - if not os.path.exists(newFolder): - os.mkdir(newFolder) - - return newFolder + """ Check if exists (or create) the folder of logs using LogsDirectory class """ + print("here") + logsFolder = LogsDirectory().getFolderPath() + if not os.path.exists(logsFolder): + os.mkdir(logsFolder) + print("return",logsFolder) + return logsFolder def GetMessageDatetimeString(self) -> str: now = datetime.now() @@ -155,7 +157,8 @@ def ColoredPrint(self, string, level) -> None: def GetLogFileDescriptor(self) -> TextIOWrapper: if self.log_file_descriptor is None: - self.log_file_descriptor = open(self.log_filename, "a") + self.log_file_descriptor = open(self.log_path, "a") + self.Log(self.LOG_MESSAGE, "Logger", "Log saved in " + self.log_path, printToConsole=True, writeToFile=False) return self.log_file_descriptor diff --git a/IoTuring/Logger/consts.py b/IoTuring/Logger/consts.py index f73e1cf80..9fd51f3df 100644 --- a/IoTuring/Logger/consts.py +++ b/IoTuring/Logger/consts.py @@ -1,5 +1,5 @@ LOGS_FOLDER = "Logs" -LOG_FILENAME_FORMAT = "Log_%Y-%m-%d_%H:%M:%S.log" +LOG_FILENAME_FORMAT = "{}_%Y-%m-%d_%H:%M:%S.log" # first place for App Name MESSAGE_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' diff --git a/IoTuring/MyApp/App.py b/IoTuring/MyApp/App.py index 743de742b..6abc41149 100644 --- a/IoTuring/MyApp/App.py +++ b/IoTuring/MyApp/App.py @@ -1,5 +1,4 @@ import inspect -from IoTuring.Logger.Logger import Logger from importlib_metadata import metadata import os diff --git a/IoTuring/MyApp/Directory/ConfigurationsDirectory.py b/IoTuring/MyApp/Directory/ConfigurationsDirectory.py new file mode 100644 index 000000000..ffd7302f1 --- /dev/null +++ b/IoTuring/MyApp/Directory/ConfigurationsDirectory.py @@ -0,0 +1,29 @@ +import os +from .GetDirectory import GetDirectory + +# macOS dep (in PyObjC) +try: + from AppKit import * + from Foundation import * + macos_support = True +except: + macos_support = False + +class ConfigurationsDirectory(GetDirectory): + PATH_ENVVAR_NAME = "IOTURING_CONFIG_DIR" # Environment variable name + SUBFOLDER_NAME = "Configurations" # Subfolder name for configurations in default folder + + # https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html + def _macOSFolderPath(self): + paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,NSUserDomainMask,True) + basePath = (len(paths) > 0 and paths[0]) or NSTemporaryDirectory() + return basePath + + # https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid + def _windowsFolderPath(self): + return os.environ["APPDATA"] + + # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + def _linuxFolderPath(self): + return os.environ["XDG_CONFIG_HOME"] if "XDG_CONFIG_HOME" in os.environ else os.path.join(os.environ["HOME"], ".config") + \ No newline at end of file diff --git a/IoTuring/MyApp/Directory/GetDirectory.py b/IoTuring/MyApp/Directory/GetDirectory.py new file mode 100644 index 000000000..d99f4780d --- /dev/null +++ b/IoTuring/MyApp/Directory/GetDirectory.py @@ -0,0 +1,94 @@ +import platform +import os +import sys +from importlib_metadata import metadata + + +class GetDirectory(): + """ Class to get the path to the folder where things are stored in the system. + It will try to get the path from the environment variable IOTURING_PATH, if it is not set + it will use the default path for the OS. + + There are 3 os-specific methods to get the default path. + + Path selection is done in the following order: + 1. Environment variable + 2. OS-specific method + 3. Default path + + Once the path is obtained, it will be joined with the directory name of the application. + + e.g.: + - by ENVVAR: CHOSEN_PATH + - Windows: C:\\Users\\username\\AppData\\Roaming\\ + IoTuring + - macOS: /Users/username/Library/Application Support/ + IoTuring/ + - Linux: /home/username/.config/ + IoTuring/ + - if error: default path + + In addition, if default path is used and SUBFOLDER_NAME is set, it will be joined with the path. + + e.g.: + - by ENVVAR: CHOSEN_PATH - like above + - Windows: C:\\Users\\username\\AppData\\Roaming\\ + IoTuring - like above + - if error: default path + SUBFOLDER_NAME + """ + + PATH_ENVVAR_NAME = "IOTURING_PATH" # Environment variable name, default one + SUBFOLDER_NAME = None + + APP_NAME = metadata('IoTuring')['Name'] + + def getFolderPath(self): + """ Returns the path to the folder where the application will store its data. """ + folderPath = self._defaultFolderPath() + try: + # Use path from environment variable if present, otherwise os specific folders, otherwise use default path + envvarPath = self._envvarFolderPath() + if envvarPath is not None: + folderPath = envvarPath + else: + _os = platform.system() + if _os == 'Darwin': + folderPath = self._macOSFolderPath() + folderPath = os.path.join(folderPath, self.APP_NAME) + elif _os == "Windows": + folderPath = self._windowsFolderPath() + folderPath = os.path.join(folderPath, self.APP_NAME) + elif _os == "Linux": + folderPath = self._linuxFolderPath() + folderPath = os.path.join(folderPath, self.APP_NAME) + except: + folderPath = self._defaultFolderPath() # default folder path will be used + + # add slash if missing (for log reasons) + if not folderPath.endswith(os.sep): + folderPath += os.sep + + return folderPath + + + def _macOSFolderPath(self): + """ Returns the path to the folder where the application will store its data in macOS """ + raise NotImplementedError("_macOSFolderPath() is not implemented yet") + + def _windowsFolderPath(self): + """ Returns the path to the folder where the application will store its data in Windows """ + raise NotImplementedError("_windowsFolderPath() is not implemented yet") + + def _linuxFolderPath(self): + """ Returns the path to the folder where the application will store its data in Linux """ + raise NotImplementedError("_linuxFolderPath() is not implemented yet") + + def _envvarFolderPath(self): + """ Returns the path to the folder where the application will store its data from the environment variable; None if not set. + The environment variable name must be stored in self.PATH_ENVVAR_NAME """ + # Get path from environment variable + return os.environ.get(self.PATH_ENVVAR_NAME) + + def _defaultFolderPath(self): + """ Returns the path to the folder where the application will store its data in the default path. + If not overriden, it will return IoTuring install directory + "UserFiles/" + [SUBFOLDER_NAME if not None]""" + if self.SUBFOLDER_NAME is None: + return os.path.join(os.path.dirname(os.path.realpath(sys.modules['__main__'].__file__)), "UserFiles") + else: + return os.path.join(os.path.dirname(os.path.realpath(sys.modules['__main__'].__file__)), "UserFiles", self.SUBFOLDER_NAME) \ No newline at end of file diff --git a/IoTuring/MyApp/Directory/LogsDirectory.py b/IoTuring/MyApp/Directory/LogsDirectory.py new file mode 100644 index 000000000..c010f8782 --- /dev/null +++ b/IoTuring/MyApp/Directory/LogsDirectory.py @@ -0,0 +1,32 @@ +import os +from .GetDirectory import GetDirectory + +# macOS dep (in PyObjC) +try: + from AppKit import * + from Foundation import * + macos_support = True +except: + macos_support = False + +class LogsDirectory(GetDirectory): + PATH_ENVVAR_NAME = "IOTURING_LOG_DIR" # Environment variable name + SUBFOLDER_NAME = "Logs" # Subfolder name for configurations in default folder + + # get the folder where macos stores all the application log files + def _macOSFolderPath(self): + return os.path.join(NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, True)[0], "Logs") + + # get the folder on windows where to store application log file + def _windowsFolderPath(self): + # return joined local app data folder and "Logs" subfolder + return os.path.join(os.environ["LOCALAPPDATA"], "Logs") + + # get the folder where linux stores all the application log files + def _linuxFolderPath(self): + # There is no standard in the XDG spec for logs, so place it in $XDG_CACHE_HOME, and fallback to $HOME/.cache + if "XDG_CACHE_HOME" in os.environ: + return os.path.join(os.environ["XDG_CACHE_HOME"], "Logs") + else: + return os.path.join(os.environ["HOME"], ".cache", "Logs") + \ No newline at end of file diff --git a/IoTuring/MyApp/Directory/__init__.py b/IoTuring/MyApp/Directory/__init__.py new file mode 100644 index 000000000..4377c6464 --- /dev/null +++ b/IoTuring/MyApp/Directory/__init__.py @@ -0,0 +1,2 @@ +from .ConfigurationsDirectory import ConfigurationsDirectory +from .LogsDirectory import LogsDirectory \ No newline at end of file