From ba41a9f95ba2e29837442d461b9fa62d17f60c1f Mon Sep 17 00:00:00 2001 From: sleptworld Date: Tue, 28 Mar 2023 00:55:40 +0800 Subject: [PATCH] create repo --- LICENSE | 21 +++ __pycache__/sync.cpython-310.pyc | Bin 0 -> 7890 bytes log.txt | 1 + rs.egg-info/PKG-INFO | 4 + rs.egg-info/SOURCES.txt | 9 + rs.egg-info/dependency_links.txt | 1 + rs.egg-info/entry_points.txt | 2 + rs.egg-info/requires.txt | 3 + rs.egg-info/top_level.txt | 1 + setup.py | 16 ++ sync.egg-info/PKG-INFO | 4 + sync.egg-info/SOURCES.txt | 9 + sync.egg-info/dependency_links.txt | 1 + sync.egg-info/entry_points.txt | 2 + sync.egg-info/requires.txt | 3 + sync.egg-info/top_level.txt | 1 + sync.py | 264 +++++++++++++++++++++++++++++ 17 files changed, 342 insertions(+) create mode 100644 LICENSE create mode 100644 __pycache__/sync.cpython-310.pyc create mode 100644 log.txt create mode 100644 rs.egg-info/PKG-INFO create mode 100644 rs.egg-info/SOURCES.txt create mode 100644 rs.egg-info/dependency_links.txt create mode 100644 rs.egg-info/entry_points.txt create mode 100644 rs.egg-info/requires.txt create mode 100644 rs.egg-info/top_level.txt create mode 100644 setup.py create mode 100644 sync.egg-info/PKG-INFO create mode 100644 sync.egg-info/SOURCES.txt create mode 100644 sync.egg-info/dependency_links.txt create mode 100644 sync.egg-info/entry_points.txt create mode 100644 sync.egg-info/requires.txt create mode 100644 sync.egg-info/top_level.txt create mode 100644 sync.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5e04ff2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 sleptworld + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/__pycache__/sync.cpython-310.pyc b/__pycache__/sync.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df341fee1a993796cd3fb2c256943284e9fa981e GIT binary patch literal 7890 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 Ti_E|lME0nSciHi*m@@w#Nu|@h literal 0 HcmV?d00001 diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..8b9bccc --- /dev/null +++ b/log.txt @@ -0,0 +1 @@ + lixiang@192.168.1.70's password: \ No newline at end of file diff --git a/rs.egg-info/PKG-INFO b/rs.egg-info/PKG-INFO new file mode 100644 index 0000000..30bacde --- /dev/null +++ b/rs.egg-info/PKG-INFO @@ -0,0 +1,4 @@ +Metadata-Version: 2.1 +Name: rs +Version: 0.1 +License-File: LICENSE diff --git a/rs.egg-info/SOURCES.txt b/rs.egg-info/SOURCES.txt new file mode 100644 index 0000000..45a57b1 --- /dev/null +++ b/rs.egg-info/SOURCES.txt @@ -0,0 +1,9 @@ +LICENSE +setup.py +sync.py +rs.egg-info/PKG-INFO +rs.egg-info/SOURCES.txt +rs.egg-info/dependency_links.txt +rs.egg-info/entry_points.txt +rs.egg-info/requires.txt +rs.egg-info/top_level.txt \ No newline at end of file diff --git a/rs.egg-info/dependency_links.txt b/rs.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rs.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/rs.egg-info/entry_points.txt b/rs.egg-info/entry_points.txt new file mode 100644 index 0000000..5d152b8 --- /dev/null +++ b/rs.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +rs = sync:app diff --git a/rs.egg-info/requires.txt b/rs.egg-info/requires.txt new file mode 100644 index 0000000..a69708b --- /dev/null +++ b/rs.egg-info/requires.txt @@ -0,0 +1,3 @@ +Click +pyyaml +pyinotify diff --git a/rs.egg-info/top_level.txt b/rs.egg-info/top_level.txt new file mode 100644 index 0000000..def1d9c --- /dev/null +++ b/rs.egg-info/top_level.txt @@ -0,0 +1 @@ +sync diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8e20830 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup, find_packages + +setup( + name='rs', + version='0.1', + py_modules=['sync'], + install_requires=[ + 'Click', + 'pyyaml', + 'pyinotify' + ], + entry_points=''' + [console_scripts] + rs=sync:app + ''' +) diff --git a/sync.egg-info/PKG-INFO b/sync.egg-info/PKG-INFO new file mode 100644 index 0000000..87bcc13 --- /dev/null +++ b/sync.egg-info/PKG-INFO @@ -0,0 +1,4 @@ +Metadata-Version: 2.1 +Name: sync +Version: 0.1 +License-File: LICENSE diff --git a/sync.egg-info/SOURCES.txt b/sync.egg-info/SOURCES.txt new file mode 100644 index 0000000..4b4d9c0 --- /dev/null +++ b/sync.egg-info/SOURCES.txt @@ -0,0 +1,9 @@ +LICENSE +setup.py +sync.py +sync.egg-info/PKG-INFO +sync.egg-info/SOURCES.txt +sync.egg-info/dependency_links.txt +sync.egg-info/entry_points.txt +sync.egg-info/requires.txt +sync.egg-info/top_level.txt \ No newline at end of file diff --git a/sync.egg-info/dependency_links.txt b/sync.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/sync.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/sync.egg-info/entry_points.txt b/sync.egg-info/entry_points.txt new file mode 100644 index 0000000..f3cf998 --- /dev/null +++ b/sync.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +sync = sync:app diff --git a/sync.egg-info/requires.txt b/sync.egg-info/requires.txt new file mode 100644 index 0000000..a69708b --- /dev/null +++ b/sync.egg-info/requires.txt @@ -0,0 +1,3 @@ +Click +pyyaml +pyinotify diff --git a/sync.egg-info/top_level.txt b/sync.egg-info/top_level.txt new file mode 100644 index 0000000..def1d9c --- /dev/null +++ b/sync.egg-info/top_level.txt @@ -0,0 +1 @@ +sync diff --git a/sync.py b/sync.py new file mode 100644 index 0000000..6d3bba6 --- /dev/null +++ b/sync.py @@ -0,0 +1,264 @@ +import os +import time +import asyncio +import platform +import re +from typing import Dict, List, Optional +import sys +import click +import pexpect as pe +import pyinotify +import yaml +from rich import print +from rich.console import Console + +cwd = os.getcwd() +wm = pyinotify.WatchManager() +console = Console() + +DEFAULT_CONFIG_FILE_NAME = "sync.yaml" + +DEFAULT_CONFIG_PATH = os.path.join(cwd, DEFAULT_CONFIG_FILE_NAME) +DEFAULT_CONFIG_PATHS = [cwd,os.path.expanduser("~"),r"/etc/conf.d/"] + +EVENTS = None + +CONFIG_ERROR = 1 + +class Config: + DEFAULT_CONFIG = { + "ssh": { + "host": "", + "port": 22, + "user": "", + "password": "", + }, + "workspace": [], + "all":{ + "events":[], + "ignore":[] + }, + "log":{ + "path":"", + "level":"INFO" + } + } + + def __init__(self, level: int): + self.__config = {} + self.__changed_part = {} + self.level = level + + @property + def config(self): + return self.__config + + @classmethod + def default(cls): + tmp = cls() + tmp.change(cls.DEFAULT_CONFIG) + return tmp + + @staticmethod + def get_from_ctx(ctx): + assert ctx.obj is None + error = ctx.obj.get('error') + config = ctx.obj.get('config') + return (config, error) + + @classmethod + def from_yaml(cls,path,level): + with open(path, "r", encoding="utf-8") as f: + try: + __config = yaml.safe_load(f) + tmp = cls(level) + tmp.change(__config) + return tmp + except yaml.YAMLError as e: + raise ConfigError(1, "config file error") + + def save(self,save_path:str): + with open(save_path, 'w', encoding='utf-8') as f: + yaml.safe_dump(self.config, f) + + def change(self, new_value): + self.__config.update(new_value) + self.__changed_part.update(new_value) + + def add_workspace(self, new_value:Dict): + if self.__config.get("workspace") is None: + self.__config["workspace"] = [] + self.__config['workspace'].append(new_value) + + def __lt__(self, other): + self.change(new_value = other.config) + return self + + def __gt__(self, other): + other.change(new_value = self.config) + return other + + @property + def ssh_info(self): + return self.config['ssh'] + + @property + def log_info(self): + return self.config['log'] + + @property + def workspace_info(self): + return self.config['workspace'] + + + +class ConfigError(Exception): + def __init__(self, code, message): + super().__init__(code, message) + self.code = code + self.message = message + def __str__(self): + return (self.message) + +def main(events:Optional[List[str]]): + if Optional is None: + events = pyinotify.ALL_EVENTS + else: + events = [pyinotify.EventsCodes.ALL_EVENTS.get(event) for event in events] + wm.add_watch(cwd, events) + notifer = pyinotify.Notifier(wm, MyEventHandler()) + notifer.loop() + +def get_cfg() -> str: + for index,p in enumerate( [os.path.join(i,DEFAULT_CONFIG_FILE_NAME) for i in DEFAULT_CONFIG_PATHS ]): + if os.path.exists(p): + return (index, p) + raise ConfigError(0,"config file not exists") + +# @click.argument('src') +@click.command() +@click.argument('dst') +@click.option('--src', '-s', default='') +@click.option('--events', '-e', multiple=True) +@click.option('--port', default=22) +@click.option('--ignore', '-i', default=None) +@click.option('--config_path', '-c', default=DEFAULT_CONFIG_PATH) +@click.pass_context +def init(ctx,dst,src,events, port, ignore, config_path) -> Config: + # init Config Class + config,error = Config.get_from_ctx(ctx) + try: + if index == 0: + click.echo("have init") + return None + except ConfigError as e: + if e.code != 0: + click.echo(e.message) + exit(CONFIG_ERROR) + + finally: + termux_config = Config() + _ssh = dst.replace(" ", "") + ssh_info = re.compile(r"(\w+)@([\w,.]+):/([\w,/]+)").match(_ssh) + abs_path = (os.path.abspath(src)) + dst = "/"+ssh_info.group(3) + + if not os.path.exists(abs_path): + click.echo("ssh key not exists") + exit(CONFIG_ERROR) + + if ssh_info is None: + click.echo("ssh info error") + exit(CONFIG_ERROR) + + pwd = click.prompt("password", hide_input=True,type=str) + + termux_config.change({ + "ssh": { + "host": ssh_info.group(2), + "port":port, + "user": ssh_info.group(1), + "password": pwd + }, + "log": { + "path": abs_path, + "level": 0, + }, + }) + + + +@click.command() +@click.option('--config_path', '-c', default=DEFAULT_CONFIG_PATH) +@click.pass_context +def sync(ctx,config_path): + + error = ctx.obj.get('error') + config = ctx.obj.get('config') + + if error is not None or config is None: + click.echo(error if not error else "" + "config") + exit(CONFIG_ERROR) + + loop = asyncio.get_event_loop() + password_re = re.compile(r"[pP]assword:") + + children = config.workspace_info.map(lambda workspace: pe.spawn("/bin/bash",[ + "-c", + "rsync -av -e ssh {} {}".format( + workspace['path'], + config.ssh_info['user'] + "@" + config.ssh_info['host'] + ":" + config.ssh_info['dir'] + ), + ],encoding='utf-8')) + + for child in children: + child.logfile = sys.stdout.buffer + + async def answer_password(child): + v = await child.expect_list([password_re],async_=True) + if v == 0: + v.sendline(config.ssh_info['password']) + + loop.run_until_complete(asyncio.gather( + [answer_password(child) for child in children] + )) + + +@click.group(chain=True) +@click.pass_context +def app(ctx): + ctx.ensure_object(dict) + + if platform.system() != "Linux": + click.echo("only support linux") + exit(1) + + try: + index, file_config_path = get_cfg() + config_from_file = Config.from_yaml(file_config_path) + ctx.obj['config'] = config_from_file + except ConfigError as e: + ctx.obj['error'] = e + + +app.add_command(init) +app.add_command(sync) + +class MyEventHandler(pyinotify.ProcessEvent): + def process_default(self, event): + print(event.pathname) + # def process_IN_ATTRIB(self, event): + # pass + # def process_IN_DELETE(self, event): + # pass + # def process_IN_OPEN(self, event): + # pass + # def process_IN_CLOSE_NOWRITE(self, event): + # pass + # def process_IN_CLOSE_WRITE(self, event): + # pass + # def process_IN_ACCESS(self, event): + # pass + # def process_IN_CREATE(self, event): + # pass + # def process_IN_MODIFY(self,event):