diff --git a/cctv-scheduler.conf b/cctv-scheduler.conf index d3c813c..2ad50ab 100644 --- a/cctv-scheduler.conf +++ b/cctv-scheduler.conf @@ -93,7 +93,7 @@ userpass = pass # # If a record directory on a remote host is used, a username and password must be specified. # Supported protocols: -# FTP, SFTP, SMB. +# FTP, SFTP, SMB or local path. records_root_path = ftp://user:pass@192.168.254.254:21/Records/camera.test.local # # One line parameters string has lower priority and parameters are overwritten by @@ -155,7 +155,7 @@ step999 = rebootcamera, -, -, -, -, -, -, image_find_names = step071, image-01, image-02 # If image root or destination video directories on a remote host is used, username and password must be specified. # Supported protocols: -# FTP, SFTP, SMB. +# FTP, SFTP, SMB or local path. image_root_path = sftp://user:pass@192.168.254.254/Records/camera.test.local # # One line parameters string has lower priority and parameters are overwritten by @@ -207,26 +207,26 @@ video_framerate = 25 video_find_names = step071, image-01, image-02 # If a video directory on a remote host is used, a username and password must be specified. # Supported protocols: -# FTP, SFTP, SMB. -video_root_path = ftp://user:pass@192.168.254.254/Downloads +# FTP, SFTP, SMB or local path. +video_root_path = /home/user/Downloads # # One line parameters string has lower priority and parameters are overwritten by # separated parameter variables if you use both. # -#video_dest_path = /Downloads +#video_dest_path = /home/user/Downloads # -#video_dest_host = 192.168.254.254 +#video_dest_host = # # Optionality you can set custom connection port: -#video_dest_port = 21 +#video_dest_port = # # You must set connection type (ftp is faster than sftp, sftp is faster than smb): # ftp, sftp, smb. -#video_dest_type = ftp +#video_dest_type = # -#video_dest_user = user +#video_dest_user = # -#video_dest_pass = pass +#video_dest_pass = # # Optionality you can enable or disable publishing by Wordpress: # true - Wordpress enabled, false - Wordpress disbaled. diff --git a/cctv-scheduler.py b/cctv-scheduler.py index 0b76382..2cd7f11 100644 --- a/cctv-scheduler.py +++ b/cctv-scheduler.py @@ -17,6 +17,7 @@ from argparse import ArgumentParser from ftplib import FTP from multiprocessing import Process, Queue from os import environ, makedirs, path, remove, rmdir, sep, stat, walk +from shutil import copyfile from string import ascii_letters, digits from subprocess import Popen, PIPE, STDOUT from sys import modules, platform @@ -689,52 +690,6 @@ class Connect: local_logger.debug(msg='error: ' + '\n' + str(error)) return {"success": False, "result": "ERROR"} - @staticmethod - # pylint: disable=W0718 - def ssh_commands( - command: str, - hostname: str, - username: str, - password: str, - port: int = 22, - logger_alias: str = inspect.stack()[0].function - ) -> str: - """Handling SSH command executing. - - Args: - command (str): command for executing. - hostname (str): remote hostname or ip address. - username (str): remote host username. - password (str): remote host password. - port (int, optional): remote host connection port. Defaults to 22. - logger_alias (str, optional): sublogger name. Defaults to function or method name. - - Returns: - str: terminal response or 'ERROR'. - """ - local_logger = logging.getLogger(logger_alias) - client = SSHClient() - client.set_missing_host_key_policy(AutoAddPolicy()) - local_logger.debug(msg='' - + '\n' + 'host: ' + hostname + ':' + str(port) - + '\n' + 'user: ' + username - + '\n' + 'pass: ' + password - + '\n' + 'command: ' + command - ) - try: - client.connect(hostname=hostname, username=username, password=password, port=port) - stdin, stdout, stderr = client.exec_command(command=command, get_pty=True) - if 'sudo' in command: - stdin.write(password + '\n') - stdin.flush() - stdout.flush() - data = stdout.read() + stderr.read() - client.close() - return data.decode('utf-8') - except Exception as error: - local_logger.debug(msg='error: ' + '\n' + str(error)) - return 'ERROR' - @staticmethod # pylint: disable=W0718 def ftp_file_search( @@ -915,7 +870,54 @@ class Connect: return {"success": False, "result": "ERROR"} @staticmethod - # pylint: disable=W0718 + # pylint: disable=W0718 + def ssh_commands( + command: str, + hostname: str, + username: str, + password: str, + port: int = 22, + logger_alias: str = inspect.stack()[0].function + ) -> str: + """Handling SSH command executing. + + Args: + command (str): command for executing. + hostname (str): remote hostname or ip address. + username (str): remote host username. + password (str): remote host password. + port (int, optional): remote host connection port. Defaults to 22. + logger_alias (str, optional): sublogger name. Defaults to function or method name. + + Returns: + str: terminal response or 'ERROR'. + """ + local_logger = logging.getLogger(logger_alias) + if Do.args_valid(locals(), Connect.ssh_commands.__annotations__): + client = SSHClient() + client.set_missing_host_key_policy(AutoAddPolicy()) + local_logger.debug(msg='' + + '\n' + 'host: ' + hostname + ':' + str(port) + + '\n' + 'user: ' + username + + '\n' + 'pass: ' + password + + '\n' + 'command: ' + command + ) + try: + client.connect(hostname=hostname, username=username, password=password, port=port) + stdin, stdout, stderr = client.exec_command(command=command, get_pty=True) + if 'sudo' in command: + stdin.write(password + '\n') + stdin.flush() + stdout.flush() + data = stdout.read() + stderr.read() + client.close() + return data.decode('utf-8') + except Exception as error: + local_logger.debug(msg='error: ' + '\n' + str(error)) + return 'ERROR' + + @staticmethod + # pylint: disable=W0718 def ssh_file_search( root_path: str, search_name: (str, type(None)) = None, @@ -995,7 +997,7 @@ class Connect: return result @staticmethod - # pylint: disable=W0718 + # pylint: disable=W0718 def ssh_get_file( src_file: str, dst_file: str, @@ -1096,7 +1098,7 @@ class Connect: return {"success": False, "result": "ERROR"} @staticmethod - # pylint: disable=W0718 + # pylint: disable=W0718 def smb_file_search( root_path: str, search_name: (str, type(None)) = None, @@ -1169,7 +1171,7 @@ class Connect: return result @staticmethod - # pylint: disable=W0718 + # pylint: disable=W0718 def smb_get_file( src_file: str, dst_file: str, @@ -1216,7 +1218,7 @@ class Connect: return {"success": False, "result": "ERROR"} @staticmethod - # pylint: disable=W0718 + # pylint: disable=W0718 def smb_put_file( src_file: str, dst_file: str, @@ -1264,9 +1266,8 @@ class Connect: local_logger.debug(msg='error: ' + '\n' + str(error)) return {"success": False, "result": "ERROR"} - - @staticmethod + # pylint: disable=W0612 def local_file_search( root_path: str, search_name: (str, type(None)) = None, @@ -1290,9 +1291,19 @@ class Connect: + '\n' + 'root_path: ' + root_path + '\n' + 'search_name: ' + str(search_name) ) - return [] + result = [] + for root, dirs, files in walk(root_path, topdown=False): + for file in files: + if search_name: + if search_name in file: + result.append(path.join(path.realpath(root), file)) + else: + result.append(path.join(path.realpath(root), file)) + result.sort() + return result @staticmethod + # pylint: disable=W0718 def local_get_file( src_file: str, dst_file: str, @@ -1314,9 +1325,16 @@ class Connect: + '\n' + 'src_file: ' + src_file + '\n' + 'dst_file: ' + dst_file ) - return {"success": False, "result": "ERROR"} + try: + makedirs(path.dirname(dst_file), exist_ok=True) + copyfile(src=src_file, dst=dst_file) + return {"success": True, "result": dst_file} + except Exception as error: + local_logger.debug(msg='error: ' + '\n' + str(error)) + return {"success": False, "result": "ERROR"} @staticmethod + # pylint: disable=W0718 def local_put_file( src_file: str, dst_file: str, @@ -1338,9 +1356,13 @@ class Connect: + '\n' + 'src_file: ' + src_file + '\n' + 'dst_file: ' + dst_file ) - return {"success": False, "result": "ERROR"} - - + try: + makedirs(path.dirname(dst_file), exist_ok=True) + copyfile(src=src_file, dst=dst_file) + return {"success": True, "result": dst_file} + except Exception as error: + local_logger.debug(msg='error: ' + '\n' + str(error)) + return {"success": False, "result": "ERROR"} @staticmethod def parse_connect_params( @@ -3314,7 +3336,7 @@ class Convert: image_temp_list = temp_path + sep + 'convert.list' image_amount = 0 with open(image_temp_list, mode='w+', encoding='UTF-8') as converter_list: - for file in Do.file_search(root_path=temp_path, search_name=name): + for file in Connect.file_search(search_path=temp_path, search_name=name): converter_list.write("\nfile '" + file + "'") image_amount += 1 temp_files.append(image_temp_list) @@ -3360,9 +3382,10 @@ class Convert: class Publish: """Publish handling. """ - @staticmethod + @classmethod # pylint: disable=W0612 def run( + cls, video_root_path: (str, list), video_find_names: list, temp_path: str, @@ -3490,7 +3513,7 @@ class Publish: if tg_api_key: tg = Telegram(tg_api_key) - Do.tg_routine_media( + cls.tg_routine_media( tg=tg, targets_media_files=video_files, period=publish_date['period'], @@ -3504,7 +3527,7 @@ class Publish: username=wp_user_name, password=wp_user_pass ) - Do.wp_routine_media( + cls.wp_routine_media( wp=wp, targets_media_files=video_files, period=publish_date['period'], @@ -3523,160 +3546,6 @@ class Publish: except OSError as error: local_logger.debug(msg='error: ' + '\n' + str(error)) - -class Do(): - """Set of various methods (functions) for routine. - """ - - @staticmethod - def random_string(length: int) -> str: - """Generate string from lowercase letters, uppercase letters, digits. - - Args: - length (int): string lenght. - - Returns: - str: random string. - """ - return ''.join(choice(ascii_letters + digits) for i in range(length)) - - @staticmethod - def args_valid(arguments: dict, annotations: dict) -> bool: - """Arguments type validating by annotations. - - Args: - arguments (dict): 'locals()' immediately after starting the function. - annotations (dict): function.name.__annotations__. - - Raises: - TypeError: type of argument is not equal type in annotation. - - Returns: - bool: True if argument types are valid. - """ - for var_name, var_type in annotations.items(): - if not var_name == 'return': - if not isinstance(arguments[var_name], var_type): - raise TypeError("" - + "type of '" - + var_name - + "' = " - + str(arguments[var_name]) - + " is not " - + str(var_type) - ) - return True - - @staticmethod - def date_calc( - target: datetime.date = datetime.datetime.now(), - amount: int = 0, - period: (str, type(None)) = None - ) -> dict: - """Calculating start/end dates for period: day, week, month, year. - - Args: - target (datetime.date, optional): date in the calculation period. - Defaults to now. - amount (int, optional): +/- periods. - Defaults to 0. - period (str, type, optional): 'y'|'year','m'|'month','w'|'week','d'|'day'. - Defaults to None. - - Raises: - ValueError: 'period' value is wrong. - - Returns: - dict: { - 'period':day|week|month|year, - 'start':{'y':int,'m':int,'w':int,'d':int}, - 'end':{'y':int,'m':int,'w':int,'d':int} - }. - """ - if Do.args_valid(locals(), Do.date_calc.__annotations__): - date = {} - if not period: - raise ValueError("'period' value is wrong: " + "''") - elif period == 'd' or period == 'day': - delta = target + datetime.timedelta(days=amount) - target = delta - date['period'] = 'day' - elif period == 'w' or period == 'week': - delta = target + datetime.timedelta(weeks=amount) - target_week = str(delta.year) + '-W' + str(delta.isocalendar()[1]) - target = datetime.datetime.strptime(target_week + '-1', "%G-W%V-%u") - delta = target + datetime.timedelta(days=6) - date['period'] = 'week' - elif period == 'm' or period == 'month': - delta_month = (target.month + amount) % 12 - if not delta_month: - delta_month = 12 - delta_year = target.year + ((target.month) + amount - 1) // 12 - delta_days = calendar.monthrange(delta_year, delta_month)[1] - delta = target = target.replace( - year=delta_year, - month=delta_month, - day=1 - ) - delta = delta.replace( - year=delta_year, - month=delta_month, - day=delta_days - ) - date['period'] = 'month' - elif period == 'y' or period == 'year': - target = target.replace( - year=target.year + amount, - month=1, - day=1 - ) - delta = target.replace( - year=target.year, - month=12, - day=31 - ) - date['period'] = 'year' - else: - raise ValueError("'period' value is wrong: " + period) - date['start'] = { - 'y': target.year, - 'm': target.month, - 'w': target.isocalendar()[1], - 'd': target.day - } - date['end'] = { - 'y': delta.year, - 'm': delta.month, - 'w': delta.isocalendar()[1], - 'd': delta.day - } - return date - - @staticmethod - # pylint: disable=W0612 - def file_search(root_path: str, search_name: (str, type(None)) = None) -> list: - """Search files. - - Args: - root_path (str): where to search. - search_name (str, type, optional): full or partial filename for the filter. - Defaults to None. - - Returns: - list: list of found files. - """ - found_list = [] - if Do.args_valid(locals(), Do.file_search.__annotations__): - for root, dirs, files in walk(root_path, topdown=False): - for file in files: - if search_name: - if search_name in file: - found_list.append(path.join(path.realpath(root), file)) - else: - found_list.append(path.join(path.realpath(root), file)) - found_list.sort() - return found_list - @staticmethod def wp_routine_media( wp: Wordpress, @@ -3700,7 +3569,7 @@ class Do(): dict: {'media upload': bool, 'event create': bool, 'pages update': bool} """ local_logger = logging.getLogger(logger_alias) - if Do.args_valid(locals(), Do.wp_routine_media.__annotations__): + if Do.args_valid(locals(), Publish.wp_routine_media.__annotations__): default_media_links = { "day": { "point-01": ( @@ -3968,7 +3837,7 @@ class Do(): dict: {'success':bool,'result':response}. """ local_logger = logging.getLogger(logger_alias) - if Do.args_valid(locals(), Do.tg_routine_media.__annotations__): + if Do.args_valid(locals(), Publish.tg_routine_media.__annotations__): default_caption = ("" + "`period:` yyyy.mm.dd\n" + "`source:` https://www.hmp.today/media\n" @@ -4087,6 +3956,134 @@ class Do(): return response_result +class Do(): + """Set of various methods (functions) for routine. + """ + @staticmethod + def random_string(length: int) -> str: + """Generate string from lowercase letters, uppercase letters, digits. + + Args: + length (int): string lenght. + + Returns: + str: random string. + """ + return ''.join(choice(ascii_letters + digits) for i in range(length)) + + @staticmethod + def args_valid(arguments: dict, annotations: dict) -> bool: + """Arguments type validating by annotations. + + Args: + arguments (dict): 'locals()' immediately after starting the function. + annotations (dict): function.name.__annotations__. + + Raises: + TypeError: type of argument is not equal type in annotation. + + Returns: + bool: True if argument types are valid. + """ + for var_name, var_type in annotations.items(): + if not var_name == 'return': + if not isinstance(arguments[var_name], var_type): + raise TypeError("" + + "type of '" + + var_name + + "' = " + + str(arguments[var_name]) + + " is not " + + str(var_type) + ) + return True + + @staticmethod + def date_calc( + target: datetime.date = datetime.datetime.now(), + amount: int = 0, + period: (str, type(None)) = None + ) -> dict: + """Calculating start/end dates for period: day, week, month, year. + + Args: + target (datetime.date, optional): date in the calculation period. + Defaults to now. + amount (int, optional): +/- periods. + Defaults to 0. + period (str, type, optional): 'y'|'year','m'|'month','w'|'week','d'|'day'. + Defaults to None. + + Raises: + ValueError: 'period' value is wrong. + + Returns: + dict: { + 'period':day|week|month|year, + 'start':{'y':int,'m':int,'w':int,'d':int}, + 'end':{'y':int,'m':int,'w':int,'d':int} + }. + """ + if Do.args_valid(locals(), Do.date_calc.__annotations__): + date = {} + if not period: + raise ValueError("'period' value is wrong: " + "''") + elif period == 'd' or period == 'day': + delta = target + datetime.timedelta(days=amount) + target = delta + date['period'] = 'day' + elif period == 'w' or period == 'week': + delta = target + datetime.timedelta(weeks=amount) + target_week = str(delta.year) + '-W' + str(delta.isocalendar()[1]) + target = datetime.datetime.strptime(target_week + '-1', "%G-W%V-%u") + delta = target + datetime.timedelta(days=6) + date['period'] = 'week' + elif period == 'm' or period == 'month': + delta_month = (target.month + amount) % 12 + if not delta_month: + delta_month = 12 + delta_year = target.year + ((target.month) + amount - 1) // 12 + delta_days = calendar.monthrange(delta_year, delta_month)[1] + delta = target = target.replace( + year=delta_year, + month=delta_month, + day=1 + ) + delta = delta.replace( + year=delta_year, + month=delta_month, + day=delta_days + ) + date['period'] = 'month' + elif period == 'y' or period == 'year': + target = target.replace( + year=target.year + amount, + month=1, + day=1 + ) + delta = target.replace( + year=target.year, + month=12, + day=31 + ) + date['period'] = 'year' + else: + raise ValueError("'period' value is wrong: " + period) + date['start'] = { + 'y': target.year, + 'm': target.month, + 'w': target.isocalendar()[1], + 'd': target.day + } + date['end'] = { + 'y': delta.year, + 'm': delta.month, + 'w': delta.isocalendar()[1], + 'd': delta.day + } + return date + + if __name__ == "__main__": time_start = datetime.datetime.now()