LiveSync 0.2.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- LiveSync-0.3.0/LiveSync.egg-info/PKG-INFO +97 -0
- {LiveSync-0.2.1 → LiveSync-0.3.0}/LiveSync.egg-info/requires.txt +2 -1
- LiveSync-0.3.0/PKG-INFO +97 -0
- LiveSync-0.3.0/README.md +84 -0
- LiveSync-0.3.0/livesync/__init__.py +2 -0
- LiveSync-0.3.0/livesync/folder.py +92 -0
- LiveSync-0.3.0/livesync/livesync.py +75 -0
- {LiveSync-0.2.1 → LiveSync-0.3.0}/livesync/mutex.py +13 -8
- {LiveSync-0.2.1 → LiveSync-0.3.0}/setup.py +5 -3
- LiveSync-0.2.1/LiveSync.egg-info/PKG-INFO +0 -65
- LiveSync-0.2.1/PKG-INFO +0 -65
- LiveSync-0.2.1/README.md +0 -52
- LiveSync-0.2.1/livesync/__init__.py +0 -2
- LiveSync-0.2.1/livesync/folder.py +0 -78
- LiveSync-0.2.1/livesync/livesync.py +0 -65
- {LiveSync-0.2.1 → LiveSync-0.3.0}/LICENSE +0 -0
- {LiveSync-0.2.1 → LiveSync-0.3.0}/LiveSync.egg-info/SOURCES.txt +0 -0
- {LiveSync-0.2.1 → LiveSync-0.3.0}/LiveSync.egg-info/dependency_links.txt +0 -0
- {LiveSync-0.2.1 → LiveSync-0.3.0}/LiveSync.egg-info/entry_points.txt +0 -0
- {LiveSync-0.2.1 → LiveSync-0.3.0}/LiveSync.egg-info/top_level.txt +0 -0
- {LiveSync-0.2.1 → LiveSync-0.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: LiveSync
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Repeatedly synchronize local workspace with a (slow) remote machine
|
|
5
|
+
Home-page: https://github.com/zauberzeug/livesync
|
|
6
|
+
Author: Zauberzeug GmbH
|
|
7
|
+
Author-email: info@zauberzeug.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: sync remote watch filesystem development deploy live hot reload
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
|
|
14
|
+
# LiveSync
|
|
15
|
+
|
|
16
|
+
Repeatedly synchronize local workspace with a (slow) remote machine.
|
|
17
|
+
It is available as [PyPI package](https://pypi.org/project/livesync/) and hosted on [GitHub](https://github.com/zauberzeug/livesync).
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/livesync/)
|
|
20
|
+
[](https://pypi.org/project/livesync/)
|
|
21
|
+
[](https://github.com/zauberzeug/livesync/graphs/commit-activity)
|
|
22
|
+
[](https://github.com/zauberzeug/livesync/issues)
|
|
23
|
+
[](https://github.com/zauberzeug/livesync/blob/main/LICENSE)
|
|
24
|
+
|
|
25
|
+
## Use Case
|
|
26
|
+
|
|
27
|
+
[VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) and similar tools are great as long as your remote machine is powerful enough.
|
|
28
|
+
But if your target is a Raspberry Pi, Jetson Nano/Xavier/Orin, Beagle Board or similar, it feels like coding in jelly.
|
|
29
|
+
Especially if you run powerful extensions like Pylance.
|
|
30
|
+
LiveSync solves this by watching your code for changes and just copying the modifications to the slow remote machine.
|
|
31
|
+
It works best if you have some kind of reload mechanism in place on the target ([NiceGUI](https://nicegui.io), [FastAPI](https://fastapi.tiangolo.com/) or [Flask](https://flask.palletsprojects.com/) for example).
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
livesync <source> <username>@<host>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
LiveSync uses rsync (SSH) to copy the files, so the `<username>@<host>` must be accessible via SSH (ideally by key, not password or passphrase, because it will be called over and over).
|
|
40
|
+
|
|
41
|
+
Press `CTRL-C` to abort the synchronization.
|
|
42
|
+
|
|
43
|
+
Positional arguments:
|
|
44
|
+
|
|
45
|
+
- `<source>`
|
|
46
|
+
local folder or VSCode workspace file
|
|
47
|
+
- `<username>@<host>`
|
|
48
|
+
target user and host (e.g. username@hostname)
|
|
49
|
+
|
|
50
|
+
Options:
|
|
51
|
+
|
|
52
|
+
- `--target-root TARGET_ROOT`
|
|
53
|
+
subfolder on target to synchronize to (default: "")
|
|
54
|
+
- `--target-port TARGET_PORT`
|
|
55
|
+
SSH port on target (default: 22)
|
|
56
|
+
- `--on-change ON_CHANGE`
|
|
57
|
+
command to be executed on remote host after any file change (default: None)
|
|
58
|
+
- `--mutex-interval MUTEX_INTERVAL`
|
|
59
|
+
interval in which mutex is updated (default: 10 seconds)
|
|
60
|
+
|
|
61
|
+
### Notes
|
|
62
|
+
|
|
63
|
+
- We suggest you have some auto-reloading in place on the (slow) target machine, like [NiceGUI](https://nicegui.io).
|
|
64
|
+
- Only one user per target host should run LiveSync at a time. Therefore LiveSync provides a mutex mechanism.
|
|
65
|
+
- You can create a `.syncignore` file in any source directory to skip additional files and directories from syncing.
|
|
66
|
+
- If a `.syncignore` file doesn't exist, it is automatically created containing `.git/`, `__pycache__/`, `.DS_Store`, `*.tmp`, and `.env`.
|
|
67
|
+
- If you pass a VSCode workspace file as `source`, LiveSync will synchronize each directory listed in the `folders` section.
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python3 -m pip install livesync
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Development
|
|
76
|
+
|
|
77
|
+
For development we suggest to use the following instructions instead of the normal pip installation:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git clone git@github.com:zauberzeug/livesync.git
|
|
81
|
+
cd livesync
|
|
82
|
+
python3 -m pip uninstall livesync # remove previous installed version
|
|
83
|
+
python3 -m pip install -e .
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Now you can change the code and call the `livesync` command from your `$PATH` variable with the modified code.
|
|
87
|
+
|
|
88
|
+
## Testing
|
|
89
|
+
|
|
90
|
+
We have build a small testing infrastructure with two docker containers.
|
|
91
|
+
See [tests/README.md](https://github.com/zauberzeug/livesync/blob/main/tests/README.md) for details.
|
|
92
|
+
|
|
93
|
+
## Releases
|
|
94
|
+
|
|
95
|
+
Just create and push a new tag with the new version name (v0.2.1 for example).
|
|
96
|
+
After a successful build a new release will be created.
|
|
97
|
+
This should be edited to describe the changes in the release notes.
|
LiveSync-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: LiveSync
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Repeatedly synchronize local workspace with a (slow) remote machine
|
|
5
|
+
Home-page: https://github.com/zauberzeug/livesync
|
|
6
|
+
Author: Zauberzeug GmbH
|
|
7
|
+
Author-email: info@zauberzeug.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: sync remote watch filesystem development deploy live hot reload
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
|
|
14
|
+
# LiveSync
|
|
15
|
+
|
|
16
|
+
Repeatedly synchronize local workspace with a (slow) remote machine.
|
|
17
|
+
It is available as [PyPI package](https://pypi.org/project/livesync/) and hosted on [GitHub](https://github.com/zauberzeug/livesync).
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/livesync/)
|
|
20
|
+
[](https://pypi.org/project/livesync/)
|
|
21
|
+
[](https://github.com/zauberzeug/livesync/graphs/commit-activity)
|
|
22
|
+
[](https://github.com/zauberzeug/livesync/issues)
|
|
23
|
+
[](https://github.com/zauberzeug/livesync/blob/main/LICENSE)
|
|
24
|
+
|
|
25
|
+
## Use Case
|
|
26
|
+
|
|
27
|
+
[VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) and similar tools are great as long as your remote machine is powerful enough.
|
|
28
|
+
But if your target is a Raspberry Pi, Jetson Nano/Xavier/Orin, Beagle Board or similar, it feels like coding in jelly.
|
|
29
|
+
Especially if you run powerful extensions like Pylance.
|
|
30
|
+
LiveSync solves this by watching your code for changes and just copying the modifications to the slow remote machine.
|
|
31
|
+
It works best if you have some kind of reload mechanism in place on the target ([NiceGUI](https://nicegui.io), [FastAPI](https://fastapi.tiangolo.com/) or [Flask](https://flask.palletsprojects.com/) for example).
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
livesync <source> <username>@<host>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
LiveSync uses rsync (SSH) to copy the files, so the `<username>@<host>` must be accessible via SSH (ideally by key, not password or passphrase, because it will be called over and over).
|
|
40
|
+
|
|
41
|
+
Press `CTRL-C` to abort the synchronization.
|
|
42
|
+
|
|
43
|
+
Positional arguments:
|
|
44
|
+
|
|
45
|
+
- `<source>`
|
|
46
|
+
local folder or VSCode workspace file
|
|
47
|
+
- `<username>@<host>`
|
|
48
|
+
target user and host (e.g. username@hostname)
|
|
49
|
+
|
|
50
|
+
Options:
|
|
51
|
+
|
|
52
|
+
- `--target-root TARGET_ROOT`
|
|
53
|
+
subfolder on target to synchronize to (default: "")
|
|
54
|
+
- `--target-port TARGET_PORT`
|
|
55
|
+
SSH port on target (default: 22)
|
|
56
|
+
- `--on-change ON_CHANGE`
|
|
57
|
+
command to be executed on remote host after any file change (default: None)
|
|
58
|
+
- `--mutex-interval MUTEX_INTERVAL`
|
|
59
|
+
interval in which mutex is updated (default: 10 seconds)
|
|
60
|
+
|
|
61
|
+
### Notes
|
|
62
|
+
|
|
63
|
+
- We suggest you have some auto-reloading in place on the (slow) target machine, like [NiceGUI](https://nicegui.io).
|
|
64
|
+
- Only one user per target host should run LiveSync at a time. Therefore LiveSync provides a mutex mechanism.
|
|
65
|
+
- You can create a `.syncignore` file in any source directory to skip additional files and directories from syncing.
|
|
66
|
+
- If a `.syncignore` file doesn't exist, it is automatically created containing `.git/`, `__pycache__/`, `.DS_Store`, `*.tmp`, and `.env`.
|
|
67
|
+
- If you pass a VSCode workspace file as `source`, LiveSync will synchronize each directory listed in the `folders` section.
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python3 -m pip install livesync
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Development
|
|
76
|
+
|
|
77
|
+
For development we suggest to use the following instructions instead of the normal pip installation:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git clone git@github.com:zauberzeug/livesync.git
|
|
81
|
+
cd livesync
|
|
82
|
+
python3 -m pip uninstall livesync # remove previous installed version
|
|
83
|
+
python3 -m pip install -e .
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Now you can change the code and call the `livesync` command from your `$PATH` variable with the modified code.
|
|
87
|
+
|
|
88
|
+
## Testing
|
|
89
|
+
|
|
90
|
+
We have build a small testing infrastructure with two docker containers.
|
|
91
|
+
See [tests/README.md](https://github.com/zauberzeug/livesync/blob/main/tests/README.md) for details.
|
|
92
|
+
|
|
93
|
+
## Releases
|
|
94
|
+
|
|
95
|
+
Just create and push a new tag with the new version name (v0.2.1 for example).
|
|
96
|
+
After a successful build a new release will be created.
|
|
97
|
+
This should be edited to describe the changes in the release notes.
|
LiveSync-0.3.0/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# LiveSync
|
|
2
|
+
|
|
3
|
+
Repeatedly synchronize local workspace with a (slow) remote machine.
|
|
4
|
+
It is available as [PyPI package](https://pypi.org/project/livesync/) and hosted on [GitHub](https://github.com/zauberzeug/livesync).
|
|
5
|
+
|
|
6
|
+
[](https://pypi.org/project/livesync/)
|
|
7
|
+
[](https://pypi.org/project/livesync/)
|
|
8
|
+
[](https://github.com/zauberzeug/livesync/graphs/commit-activity)
|
|
9
|
+
[](https://github.com/zauberzeug/livesync/issues)
|
|
10
|
+
[](https://github.com/zauberzeug/livesync/blob/main/LICENSE)
|
|
11
|
+
|
|
12
|
+
## Use Case
|
|
13
|
+
|
|
14
|
+
[VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) and similar tools are great as long as your remote machine is powerful enough.
|
|
15
|
+
But if your target is a Raspberry Pi, Jetson Nano/Xavier/Orin, Beagle Board or similar, it feels like coding in jelly.
|
|
16
|
+
Especially if you run powerful extensions like Pylance.
|
|
17
|
+
LiveSync solves this by watching your code for changes and just copying the modifications to the slow remote machine.
|
|
18
|
+
It works best if you have some kind of reload mechanism in place on the target ([NiceGUI](https://nicegui.io), [FastAPI](https://fastapi.tiangolo.com/) or [Flask](https://flask.palletsprojects.com/) for example).
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
livesync <source> <username>@<host>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
LiveSync uses rsync (SSH) to copy the files, so the `<username>@<host>` must be accessible via SSH (ideally by key, not password or passphrase, because it will be called over and over).
|
|
27
|
+
|
|
28
|
+
Press `CTRL-C` to abort the synchronization.
|
|
29
|
+
|
|
30
|
+
Positional arguments:
|
|
31
|
+
|
|
32
|
+
- `<source>`
|
|
33
|
+
local folder or VSCode workspace file
|
|
34
|
+
- `<username>@<host>`
|
|
35
|
+
target user and host (e.g. username@hostname)
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
|
|
39
|
+
- `--target-root TARGET_ROOT`
|
|
40
|
+
subfolder on target to synchronize to (default: "")
|
|
41
|
+
- `--target-port TARGET_PORT`
|
|
42
|
+
SSH port on target (default: 22)
|
|
43
|
+
- `--on-change ON_CHANGE`
|
|
44
|
+
command to be executed on remote host after any file change (default: None)
|
|
45
|
+
- `--mutex-interval MUTEX_INTERVAL`
|
|
46
|
+
interval in which mutex is updated (default: 10 seconds)
|
|
47
|
+
|
|
48
|
+
### Notes
|
|
49
|
+
|
|
50
|
+
- We suggest you have some auto-reloading in place on the (slow) target machine, like [NiceGUI](https://nicegui.io).
|
|
51
|
+
- Only one user per target host should run LiveSync at a time. Therefore LiveSync provides a mutex mechanism.
|
|
52
|
+
- You can create a `.syncignore` file in any source directory to skip additional files and directories from syncing.
|
|
53
|
+
- If a `.syncignore` file doesn't exist, it is automatically created containing `.git/`, `__pycache__/`, `.DS_Store`, `*.tmp`, and `.env`.
|
|
54
|
+
- If you pass a VSCode workspace file as `source`, LiveSync will synchronize each directory listed in the `folders` section.
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
python3 -m pip install livesync
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
For development we suggest to use the following instructions instead of the normal pip installation:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
git clone git@github.com:zauberzeug/livesync.git
|
|
68
|
+
cd livesync
|
|
69
|
+
python3 -m pip uninstall livesync # remove previous installed version
|
|
70
|
+
python3 -m pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Now you can change the code and call the `livesync` command from your `$PATH` variable with the modified code.
|
|
74
|
+
|
|
75
|
+
## Testing
|
|
76
|
+
|
|
77
|
+
We have build a small testing infrastructure with two docker containers.
|
|
78
|
+
See [tests/README.md](https://github.com/zauberzeug/livesync/blob/main/tests/README.md) for details.
|
|
79
|
+
|
|
80
|
+
## Releases
|
|
81
|
+
|
|
82
|
+
Just create and push a new tag with the new version name (v0.2.1 for example).
|
|
83
|
+
After a successful build a new release will be created.
|
|
84
|
+
This should be edited to describe the changes in the release notes.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
import pathspec
|
|
9
|
+
import watchfiles
|
|
10
|
+
|
|
11
|
+
KWONLY_SLOTS = {'kw_only': True, 'slots': True} if sys.version_info >= (3, 10) else {}
|
|
12
|
+
DEFAULT_IGNORES = ['.git/', '__pycache__/', '.DS_Store', '*.tmp', '.env']
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_subprocess(command: str, *, quiet: bool = False) -> None:
|
|
16
|
+
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True)
|
|
17
|
+
if not quiet:
|
|
18
|
+
print(result.stdout.decode())
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(**KWONLY_SLOTS)
|
|
22
|
+
class Target:
|
|
23
|
+
host: str
|
|
24
|
+
port: int
|
|
25
|
+
root: Path
|
|
26
|
+
|
|
27
|
+
def make_target_root_directory(self) -> None:
|
|
28
|
+
print(f'make target root directory {self.root}')
|
|
29
|
+
run_subprocess(f'ssh {self.host} -p {self.port} "mkdir -p {self.root}"')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Folder:
|
|
33
|
+
|
|
34
|
+
def __init__(self, local_dir: Path, target: Target) -> None:
|
|
35
|
+
self.local_path = local_dir.resolve() # one should avoid `absolute` if Python < 3.11
|
|
36
|
+
self.target = target
|
|
37
|
+
|
|
38
|
+
# from https://stackoverflow.com/a/22090594/3419103
|
|
39
|
+
match_pattern = pathspec.patterns.gitwildmatch.GitWildMatchPattern
|
|
40
|
+
self._ignore_spec = pathspec.PathSpec.from_lines(match_pattern, self.get_ignores())
|
|
41
|
+
|
|
42
|
+
self._stop_watching = asyncio.Event()
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def target_path(self) -> Path:
|
|
46
|
+
return self.target.root / self.local_path.stem
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def ssh_path(self) -> str:
|
|
50
|
+
return f'{self.target.host}:{self.target_path}'
|
|
51
|
+
|
|
52
|
+
def get_ignores(self) -> List[str]:
|
|
53
|
+
path = self.local_path / '.syncignore'
|
|
54
|
+
if not path.is_file():
|
|
55
|
+
path.write_text('\n'.join(DEFAULT_IGNORES))
|
|
56
|
+
return [line.strip() for line in path.read_text().splitlines() if not line.startswith('#')]
|
|
57
|
+
|
|
58
|
+
def get_summary(self) -> str:
|
|
59
|
+
summary = f'{self.local_path} --> {self.ssh_path}\n'
|
|
60
|
+
if not (self.local_path / '.git').exists():
|
|
61
|
+
return summary
|
|
62
|
+
try:
|
|
63
|
+
cmd = ['git', 'log', '--pretty=format:[%h]\n', '-n', '1']
|
|
64
|
+
summary += subprocess.check_output(cmd, cwd=self.local_path).decode()
|
|
65
|
+
cmd = ['git', 'status', '--short', '--branch']
|
|
66
|
+
summary += subprocess.check_output(cmd, cwd=self.local_path).decode().strip() + '\n'
|
|
67
|
+
except Exception:
|
|
68
|
+
pass # maybe git is not installed
|
|
69
|
+
return summary
|
|
70
|
+
|
|
71
|
+
async def watch(self, on_change_command: Optional[str]) -> None:
|
|
72
|
+
try:
|
|
73
|
+
async for changes in watchfiles.awatch(self.local_path, stop_event=self._stop_watching,
|
|
74
|
+
watch_filter=lambda _, filepath: not self._ignore_spec.match_file(filepath)):
|
|
75
|
+
for change, filepath in changes:
|
|
76
|
+
print('?+U-'[change], filepath)
|
|
77
|
+
self.sync(on_change_command)
|
|
78
|
+
except RuntimeError as e:
|
|
79
|
+
if 'Already borrowed' not in str(e):
|
|
80
|
+
raise
|
|
81
|
+
|
|
82
|
+
def stop_watching(self) -> None:
|
|
83
|
+
self._stop_watching.set()
|
|
84
|
+
|
|
85
|
+
def sync(self, post_sync_command: Optional[str] = None) -> None:
|
|
86
|
+
args = '--prune-empty-dirs --delete -avz --checksum --no-t'
|
|
87
|
+
# args += ' --mkdirs' # INFO: this option is not available in rsync < 3.2.3
|
|
88
|
+
args += ''.join(f' --exclude="{e}"' for e in self.get_ignores())
|
|
89
|
+
args += f' -e "ssh -p {self.target.port}"'
|
|
90
|
+
run_subprocess(f'rsync {args} {self.local_path}/ {self.ssh_path}/', quiet=True)
|
|
91
|
+
if post_sync_command:
|
|
92
|
+
run_subprocess(f'ssh {self.target.host} -p {self.target.port} "cd {self.target_path}; {post_sync_command}"')
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import asyncio
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
import pyjson5
|
|
9
|
+
|
|
10
|
+
from livesync import Folder, Mutex, Target
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def git_summary(folders: List[Folder]) -> str:
|
|
14
|
+
return '\n'.join(f.get_summary() for f in folders).replace('"', '\'')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def async_main() -> None:
|
|
18
|
+
parser = argparse.ArgumentParser(
|
|
19
|
+
description='Repeatedly synchronize local directories with remote machine',
|
|
20
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
21
|
+
parser.add_argument('source', type=str, help='local source folder or VSCode workspace file')
|
|
22
|
+
parser.add_argument('--target-root', type=str, default='', help='subfolder on target to synchronize to')
|
|
23
|
+
parser.add_argument('--target-port', type=int, default=22, help='SSH port on target')
|
|
24
|
+
parser.add_argument('--on-change', type=str, help='command to be executed on remote host after any file change')
|
|
25
|
+
parser.add_argument('--mutex-interval', type=int, default=10, help='interval in which mutex is updated')
|
|
26
|
+
parser.add_argument('host', type=str, help='the target host (e.g. username@hostname)')
|
|
27
|
+
args = parser.parse_args()
|
|
28
|
+
source = Path(args.source)
|
|
29
|
+
target = Target(host=args.host, port=args.target_port, root=Path(args.target_root))
|
|
30
|
+
|
|
31
|
+
folders: List[Folder] = []
|
|
32
|
+
if source.is_file():
|
|
33
|
+
workspace = pyjson5.decode(source.read_text())
|
|
34
|
+
paths = [Path(f['path']) for f in workspace['folders']]
|
|
35
|
+
folders = [Folder(p, target) for p in paths if p.is_dir()]
|
|
36
|
+
else:
|
|
37
|
+
folders = [Folder(source, target)]
|
|
38
|
+
|
|
39
|
+
for folder in folders:
|
|
40
|
+
if not folder.local_path.is_dir():
|
|
41
|
+
print(f'Invalid path: {folder.local_path}')
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
print('Checking mutex...')
|
|
45
|
+
mutex = Mutex(target)
|
|
46
|
+
if not mutex.set(git_summary(folders)):
|
|
47
|
+
print(f'Target is in use by {mutex.occupant}')
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
|
|
50
|
+
if args.target_root:
|
|
51
|
+
print('Creating target root directory...')
|
|
52
|
+
target.make_target_root_directory()
|
|
53
|
+
|
|
54
|
+
print('Initial sync...')
|
|
55
|
+
for folder in folders:
|
|
56
|
+
print(f' {folder.local_path} --> {folder.ssh_path}')
|
|
57
|
+
folder.sync(post_sync_command=args.on_change)
|
|
58
|
+
|
|
59
|
+
print('Watching for file changes...')
|
|
60
|
+
for folder in folders:
|
|
61
|
+
asyncio.create_task(folder.watch(on_change_command=args.on_change))
|
|
62
|
+
|
|
63
|
+
while mutex.set(git_summary(folders)):
|
|
64
|
+
await asyncio.sleep(args.mutex_interval)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def main():
|
|
68
|
+
try:
|
|
69
|
+
asyncio.run(async_main())
|
|
70
|
+
except KeyboardInterrupt:
|
|
71
|
+
print('Bye!')
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == '__main__':
|
|
75
|
+
main()
|
|
@@ -2,28 +2,30 @@ import logging
|
|
|
2
2
|
import socket
|
|
3
3
|
import subprocess
|
|
4
4
|
from datetime import datetime, timedelta
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from .folder import Target
|
|
5
8
|
|
|
6
9
|
MUTEX_FILEPATH = '~/.livesync_mutex'
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class Mutex:
|
|
10
13
|
|
|
11
|
-
def __init__(self,
|
|
12
|
-
self.
|
|
13
|
-
self.occupant: str = None
|
|
14
|
+
def __init__(self, target: Target) -> None:
|
|
15
|
+
self.target = target
|
|
16
|
+
self.occupant: Optional[str] = None
|
|
14
17
|
self.user_id = socket.gethostname()
|
|
15
18
|
|
|
16
19
|
def is_free(self, info: str) -> bool:
|
|
17
20
|
try:
|
|
18
|
-
|
|
19
|
-
output = subprocess.check_output(command, stderr=subprocess.DEVNULL).decode().splitlines()[0]
|
|
21
|
+
output = self._run_ssh_command(f'cat {MUTEX_FILEPATH} || echo "{self.tag}\n{info}"').splitlines()[0]
|
|
20
22
|
words = output.strip().split()
|
|
21
23
|
self.occupant = words[0]
|
|
22
24
|
occupant_ok = self.occupant == self.user_id
|
|
23
25
|
mutex_datetime = datetime.fromisoformat(words[1])
|
|
24
26
|
mutex_expired = datetime.now() - mutex_datetime > timedelta(seconds=15)
|
|
25
27
|
return occupant_ok or mutex_expired
|
|
26
|
-
except:
|
|
28
|
+
except Exception:
|
|
27
29
|
logging.exception('Could not access target system')
|
|
28
30
|
return False
|
|
29
31
|
|
|
@@ -31,8 +33,7 @@ class Mutex:
|
|
|
31
33
|
if not self.is_free(info):
|
|
32
34
|
return False
|
|
33
35
|
try:
|
|
34
|
-
|
|
35
|
-
subprocess.check_output(command, stderr=subprocess.DEVNULL)
|
|
36
|
+
self._run_ssh_command(f'echo "{self.tag}\n{info}" > {MUTEX_FILEPATH}')
|
|
36
37
|
return True
|
|
37
38
|
except subprocess.CalledProcessError:
|
|
38
39
|
print('Could not write mutex file')
|
|
@@ -41,3 +42,7 @@ class Mutex:
|
|
|
41
42
|
@property
|
|
42
43
|
def tag(self) -> str:
|
|
43
44
|
return f'{self.user_id} {datetime.now().isoformat()}'
|
|
45
|
+
|
|
46
|
+
def _run_ssh_command(self, command: str) -> str:
|
|
47
|
+
ssh_command = ['ssh', self.target.host, '-p', str(self.target.port), command]
|
|
48
|
+
return subprocess.check_output(ssh_command, stderr=subprocess.DEVNULL).decode()
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
import os
|
|
3
|
-
from distutils.core import setup
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
from setuptools import setup
|
|
6
|
+
|
|
7
|
+
long_description = (Path(__file__).parent / 'README.md').read_text()
|
|
8
|
+
requirements = (Path(__file__).parent / 'requirements.txt').read_text()
|
|
7
9
|
VERSION = os.getenv('VERSION', '0.0.1')
|
|
8
10
|
|
|
9
11
|
setup(
|
|
@@ -19,7 +21,7 @@ setup(
|
|
|
19
21
|
keywords='sync remote watch filesystem development deploy live hot reload',
|
|
20
22
|
python_requires='>=3.7',
|
|
21
23
|
packages=['livesync'],
|
|
22
|
-
install_requires=
|
|
24
|
+
install_requires=requirements.splitlines(),
|
|
23
25
|
entry_points={
|
|
24
26
|
'console_scripts': [
|
|
25
27
|
'livesync=livesync.livesync:main',
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: LiveSync
|
|
3
|
-
Version: 0.2.1
|
|
4
|
-
Summary: Repeatedly synchronize local workspace with a (slow) remote machine
|
|
5
|
-
Home-page: https://github.com/zauberzeug/livesync
|
|
6
|
-
Author: Zauberzeug GmbH
|
|
7
|
-
Author-email: info@zauberzeug.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Keywords: sync remote watch filesystem development deploy live hot reload
|
|
10
|
-
Requires-Python: >=3.7
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
License-File: LICENSE
|
|
13
|
-
|
|
14
|
-
# LiveSync
|
|
15
|
-
|
|
16
|
-
Repeatedly synchronize local workspace with a (slow) remote machine.
|
|
17
|
-
|
|
18
|
-
## Use Case
|
|
19
|
-
|
|
20
|
-
[VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) and similar tools are great as long as your remote machine is powerful enough.
|
|
21
|
-
But if your target is a Raspberry Pi, Jetson Nano/Xavier/Orin, Beagle Board or similar, it feels like coding in yelly.
|
|
22
|
-
Especially if you run powerful extensions like Pylance.
|
|
23
|
-
LiveSync solves this by watching your code and just copying the changed files to the slow remote machine.
|
|
24
|
-
It works best if you have some kind of reloading mechanism in place on the target.
|
|
25
|
-
We obviously recommend [NiceGUI](https://nicegui.io).
|
|
26
|
-
|
|
27
|
-
## Install
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
python3 -m pip install livesync
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Usage
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
cd <my_folder_with_vscode_workspace>
|
|
37
|
-
livesync <username>@<host>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
LiveSync uses rsync (SSH) to copy the files, so the `<username>@<host>` must be accessible via SSH (ideally by key, not password or passphrase, because it will be called over and over).
|
|
41
|
-
|
|
42
|
-
Press `CTRL-C` to abort the synchronization.
|
|
43
|
-
|
|
44
|
-
### Notes
|
|
45
|
-
|
|
46
|
-
- We suggest you have some auto-reloading in place on the (slow) target machine, like [NiceGUI](https://nicegui.io).
|
|
47
|
-
- Only one user per target host can run LiveSync at a time.
|
|
48
|
-
- By default `.git/` folders are not synchronized.
|
|
49
|
-
- All files and directories from the `.gitignore` of any source directory are also excluded from synchronization.
|
|
50
|
-
- You can create a `.syncignore` file in any source directory to skip additional files and directories from syncing.
|
|
51
|
-
|
|
52
|
-
## Development
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
git clone git@github.com:zauberzeug/livesync.git
|
|
56
|
-
cd livesync
|
|
57
|
-
python3 -m pip uninstall livesync # remove previous installed version
|
|
58
|
-
python3 -m pip install -e .
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Now you can change the code and still use the `livesync` command from your `$PATH` variable.
|
|
62
|
-
|
|
63
|
-
## Releases
|
|
64
|
-
|
|
65
|
-
Just create and push a new tag with the new version name.
|
LiveSync-0.2.1/PKG-INFO
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: LiveSync
|
|
3
|
-
Version: 0.2.1
|
|
4
|
-
Summary: Repeatedly synchronize local workspace with a (slow) remote machine
|
|
5
|
-
Home-page: https://github.com/zauberzeug/livesync
|
|
6
|
-
Author: Zauberzeug GmbH
|
|
7
|
-
Author-email: info@zauberzeug.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Keywords: sync remote watch filesystem development deploy live hot reload
|
|
10
|
-
Requires-Python: >=3.7
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
License-File: LICENSE
|
|
13
|
-
|
|
14
|
-
# LiveSync
|
|
15
|
-
|
|
16
|
-
Repeatedly synchronize local workspace with a (slow) remote machine.
|
|
17
|
-
|
|
18
|
-
## Use Case
|
|
19
|
-
|
|
20
|
-
[VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) and similar tools are great as long as your remote machine is powerful enough.
|
|
21
|
-
But if your target is a Raspberry Pi, Jetson Nano/Xavier/Orin, Beagle Board or similar, it feels like coding in yelly.
|
|
22
|
-
Especially if you run powerful extensions like Pylance.
|
|
23
|
-
LiveSync solves this by watching your code and just copying the changed files to the slow remote machine.
|
|
24
|
-
It works best if you have some kind of reloading mechanism in place on the target.
|
|
25
|
-
We obviously recommend [NiceGUI](https://nicegui.io).
|
|
26
|
-
|
|
27
|
-
## Install
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
python3 -m pip install livesync
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Usage
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
cd <my_folder_with_vscode_workspace>
|
|
37
|
-
livesync <username>@<host>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
LiveSync uses rsync (SSH) to copy the files, so the `<username>@<host>` must be accessible via SSH (ideally by key, not password or passphrase, because it will be called over and over).
|
|
41
|
-
|
|
42
|
-
Press `CTRL-C` to abort the synchronization.
|
|
43
|
-
|
|
44
|
-
### Notes
|
|
45
|
-
|
|
46
|
-
- We suggest you have some auto-reloading in place on the (slow) target machine, like [NiceGUI](https://nicegui.io).
|
|
47
|
-
- Only one user per target host can run LiveSync at a time.
|
|
48
|
-
- By default `.git/` folders are not synchronized.
|
|
49
|
-
- All files and directories from the `.gitignore` of any source directory are also excluded from synchronization.
|
|
50
|
-
- You can create a `.syncignore` file in any source directory to skip additional files and directories from syncing.
|
|
51
|
-
|
|
52
|
-
## Development
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
git clone git@github.com:zauberzeug/livesync.git
|
|
56
|
-
cd livesync
|
|
57
|
-
python3 -m pip uninstall livesync # remove previous installed version
|
|
58
|
-
python3 -m pip install -e .
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Now you can change the code and still use the `livesync` command from your `$PATH` variable.
|
|
62
|
-
|
|
63
|
-
## Releases
|
|
64
|
-
|
|
65
|
-
Just create and push a new tag with the new version name.
|
LiveSync-0.2.1/README.md
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# LiveSync
|
|
2
|
-
|
|
3
|
-
Repeatedly synchronize local workspace with a (slow) remote machine.
|
|
4
|
-
|
|
5
|
-
## Use Case
|
|
6
|
-
|
|
7
|
-
[VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) and similar tools are great as long as your remote machine is powerful enough.
|
|
8
|
-
But if your target is a Raspberry Pi, Jetson Nano/Xavier/Orin, Beagle Board or similar, it feels like coding in yelly.
|
|
9
|
-
Especially if you run powerful extensions like Pylance.
|
|
10
|
-
LiveSync solves this by watching your code and just copying the changed files to the slow remote machine.
|
|
11
|
-
It works best if you have some kind of reloading mechanism in place on the target.
|
|
12
|
-
We obviously recommend [NiceGUI](https://nicegui.io).
|
|
13
|
-
|
|
14
|
-
## Install
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
python3 -m pip install livesync
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Usage
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
cd <my_folder_with_vscode_workspace>
|
|
24
|
-
livesync <username>@<host>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
LiveSync uses rsync (SSH) to copy the files, so the `<username>@<host>` must be accessible via SSH (ideally by key, not password or passphrase, because it will be called over and over).
|
|
28
|
-
|
|
29
|
-
Press `CTRL-C` to abort the synchronization.
|
|
30
|
-
|
|
31
|
-
### Notes
|
|
32
|
-
|
|
33
|
-
- We suggest you have some auto-reloading in place on the (slow) target machine, like [NiceGUI](https://nicegui.io).
|
|
34
|
-
- Only one user per target host can run LiveSync at a time.
|
|
35
|
-
- By default `.git/` folders are not synchronized.
|
|
36
|
-
- All files and directories from the `.gitignore` of any source directory are also excluded from synchronization.
|
|
37
|
-
- You can create a `.syncignore` file in any source directory to skip additional files and directories from syncing.
|
|
38
|
-
|
|
39
|
-
## Development
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
git clone git@github.com:zauberzeug/livesync.git
|
|
43
|
-
cd livesync
|
|
44
|
-
python3 -m pip uninstall livesync # remove previous installed version
|
|
45
|
-
python3 -m pip install -e .
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Now you can change the code and still use the `livesync` command from your `$PATH` variable.
|
|
49
|
-
|
|
50
|
-
## Releases
|
|
51
|
-
|
|
52
|
-
Just create and push a new tag with the new version name.
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import os
|
|
3
|
-
import subprocess
|
|
4
|
-
from typing import List, Optional
|
|
5
|
-
|
|
6
|
-
import pathspec
|
|
7
|
-
import watchfiles
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Folder:
|
|
11
|
-
|
|
12
|
-
def __init__(self, local_dir: str, target_host: str) -> None:
|
|
13
|
-
self.local_dir = os.path.abspath(local_dir)
|
|
14
|
-
self.target_host = target_host
|
|
15
|
-
|
|
16
|
-
# https://stackoverflow.com/a/22090594/3419103
|
|
17
|
-
self._ignore_spec = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, self.get_excludes())
|
|
18
|
-
|
|
19
|
-
self._stop_watching = asyncio.Event()
|
|
20
|
-
|
|
21
|
-
@property
|
|
22
|
-
def target_path(self) -> str:
|
|
23
|
-
return os.path.basename(os.path.realpath(self.local_dir))
|
|
24
|
-
|
|
25
|
-
@property
|
|
26
|
-
def ssh_target(self) -> str:
|
|
27
|
-
return f'{self.target_host}:{self.target_path}'
|
|
28
|
-
|
|
29
|
-
@property
|
|
30
|
-
def is_valid(self) -> bool:
|
|
31
|
-
return os.path.isdir(self.local_dir)
|
|
32
|
-
|
|
33
|
-
def get_excludes(self) -> List[str]:
|
|
34
|
-
return ['.git/', '__pycache__/', '.DS_Store'] + \
|
|
35
|
-
self._parse_ignore_file(f'{self.local_dir}/.syncignore') + \
|
|
36
|
-
self._parse_ignore_file(f'{self.local_dir}/.gitignore')
|
|
37
|
-
|
|
38
|
-
@staticmethod
|
|
39
|
-
def _parse_ignore_file(path: str) -> List[str]:
|
|
40
|
-
if not os.path.isfile(path):
|
|
41
|
-
return []
|
|
42
|
-
with open(path) as f:
|
|
43
|
-
return [line.strip() for line in f.readlines() if not line.startswith('#')]
|
|
44
|
-
|
|
45
|
-
def get_summary(self) -> str:
|
|
46
|
-
summary = f'{self.local_dir} --> {self.ssh_target}\n'
|
|
47
|
-
if not os.path.exists(os.path.join(self.local_dir, '.git')):
|
|
48
|
-
return summary
|
|
49
|
-
try:
|
|
50
|
-
cmd = ['git', 'log', '--pretty=format:[%h]\n', '-n', '1']
|
|
51
|
-
summary += subprocess.check_output(cmd, cwd=self.local_dir).decode()
|
|
52
|
-
cmd = ['git', 'status', '--short', '--branch']
|
|
53
|
-
summary += subprocess.check_output(cmd, cwd=self.local_dir).decode().strip()
|
|
54
|
-
except:
|
|
55
|
-
pass # maybe git is not installed
|
|
56
|
-
return summary
|
|
57
|
-
|
|
58
|
-
async def watch(self, on_change_command: Optional[str]) -> None:
|
|
59
|
-
try:
|
|
60
|
-
async for changes in watchfiles.awatch(self.local_dir, stop_event=self._stop_watching,
|
|
61
|
-
watch_filter=lambda _, filepath: not self._ignore_spec.match_file(filepath)):
|
|
62
|
-
for change, filepath in changes:
|
|
63
|
-
print('?+U-'[change], filepath)
|
|
64
|
-
self.sync(on_change_command)
|
|
65
|
-
except RuntimeError as e:
|
|
66
|
-
if 'Already borrowed' not in str(e):
|
|
67
|
-
raise
|
|
68
|
-
|
|
69
|
-
def stop_watching(self) -> None:
|
|
70
|
-
self._stop_watching.set()
|
|
71
|
-
|
|
72
|
-
def sync(self, post_sync_command: Optional[str] = None) -> None:
|
|
73
|
-
args = '--prune-empty-dirs --delete -avz --checksum --no-t'
|
|
74
|
-
args += ''.join(f' --exclude="{e}"' for e in self.get_excludes())
|
|
75
|
-
command = f'rsync {args} {self.local_dir}/ {self.ssh_target}'
|
|
76
|
-
if post_sync_command:
|
|
77
|
-
command += f'; ssh {self.target_host} "cd {self.target_path}; {post_sync_command}"'
|
|
78
|
-
subprocess.run(command, shell=True, stdout=subprocess.DEVNULL)
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
import argparse
|
|
3
|
-
import asyncio
|
|
4
|
-
import json
|
|
5
|
-
import sys
|
|
6
|
-
from glob import glob
|
|
7
|
-
from typing import List
|
|
8
|
-
|
|
9
|
-
from livesync import Folder, Mutex
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def git_summary(folders: List[Folder]) -> str:
|
|
13
|
-
return '\n'.join(f.get_summary() for f in folders).replace('"', '\'')
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
async def async_main() -> None:
|
|
17
|
-
parser = argparse.ArgumentParser(description='Repeatedly synchronize local workspace with remote machine')
|
|
18
|
-
parser.add_argument('--on-change', type=str, help='command to be executed on remote host after any file change')
|
|
19
|
-
parser.add_argument('--source', type=str, help='source folder on local host instead of vscode workspace file')
|
|
20
|
-
parser.add_argument('--mutex-interval', type=int, nargs='?', default=10, help='interval in which mutex is updated')
|
|
21
|
-
parser.add_argument('host', type=str, help='the target host (e.g. username@hostname)')
|
|
22
|
-
args = parser.parse_args()
|
|
23
|
-
|
|
24
|
-
folders: List[Folder] = []
|
|
25
|
-
workspaces = glob('*.code-workspace')
|
|
26
|
-
if args.source is None and workspaces:
|
|
27
|
-
print(f'Reading vscode workspace file {workspaces[0]} ...')
|
|
28
|
-
try:
|
|
29
|
-
with open(workspaces[0]) as f:
|
|
30
|
-
workspace = json.load(f)
|
|
31
|
-
folders = [Folder(folder['path'], args.host) for folder in workspace['folders']]
|
|
32
|
-
except IndexError:
|
|
33
|
-
print('No vscode workspace file found; provide --source parameter or start in dir with *.code-workspace file')
|
|
34
|
-
sys.exit(1)
|
|
35
|
-
else:
|
|
36
|
-
folders = [Folder(args.source or '.', args.host)]
|
|
37
|
-
|
|
38
|
-
print('Checking mutex...')
|
|
39
|
-
mutex = Mutex(args.host)
|
|
40
|
-
if not mutex.set(git_summary(folders)):
|
|
41
|
-
print(f'Target is in use by {mutex.occupant}')
|
|
42
|
-
sys.exit(1)
|
|
43
|
-
|
|
44
|
-
print('Initial sync...')
|
|
45
|
-
for folder in folders:
|
|
46
|
-
print(f' {folder.local_dir} --> {folder.ssh_target}')
|
|
47
|
-
folder.sync(post_sync_command=args.on_change)
|
|
48
|
-
|
|
49
|
-
print('Watching for file changes...')
|
|
50
|
-
for folder in folders:
|
|
51
|
-
asyncio.create_task(folder.watch(on_change_command=args.on_change))
|
|
52
|
-
|
|
53
|
-
while mutex.set(git_summary(folders)):
|
|
54
|
-
await asyncio.sleep(args.mutex_interval)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def main():
|
|
58
|
-
try:
|
|
59
|
-
asyncio.run(async_main())
|
|
60
|
-
except KeyboardInterrupt:
|
|
61
|
-
print('Bye!')
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if __name__ == '__main__':
|
|
65
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|