create repo

This commit is contained in:
sleptworld 2023-03-28 00:55:40 +08:00
commit ba41a9f95b
17 changed files with 342 additions and 0 deletions

21
LICENSE Normal file
View File

@ -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.

Binary file not shown.

1
log.txt Normal file
View File

@ -0,0 +1 @@
lixiang@192.168.1.70's password:

4
rs.egg-info/PKG-INFO Normal file
View File

@ -0,0 +1,4 @@
Metadata-Version: 2.1
Name: rs
Version: 0.1
License-File: LICENSE

9
rs.egg-info/SOURCES.txt Normal file
View File

@ -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

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,2 @@
[console_scripts]
rs = sync:app

3
rs.egg-info/requires.txt Normal file
View File

@ -0,0 +1,3 @@
Click
pyyaml
pyinotify

View File

@ -0,0 +1 @@
sync

16
setup.py Normal file
View File

@ -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
'''
)

4
sync.egg-info/PKG-INFO Normal file
View File

@ -0,0 +1,4 @@
Metadata-Version: 2.1
Name: sync
Version: 0.1
License-File: LICENSE

View File

@ -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

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,2 @@
[console_scripts]
sync = sync:app

View File

@ -0,0 +1,3 @@
Click
pyyaml
pyinotify

View File

@ -0,0 +1 @@
sync

264
sync.py Normal file
View File

@ -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):