dfsync 0.4.9__tar.gz → 0.4.10__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: dfsync
3
- Version: 0.4.9
3
+ Version: 0.4.10
4
4
  Summary:
5
5
  Author: Mihai Balint
6
6
  Author-email: balint.mihai@gmail.com
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
14
15
  Requires-Dist: black (==24.3.0)
15
16
  Requires-Dist: click (>=8,<9)
16
17
  Requires-Dist: click-default-group (>=1.2.2,<2.0.0)
@@ -0,0 +1,101 @@
1
+ from enum import Enum
2
+ import platform
3
+ import subprocess
4
+ import time
5
+ import threading
6
+
7
+
8
+ class ThreadedOperationsManager:
9
+ def __init__(self):
10
+ self.threads = []
11
+
12
+ def stop(self, *args, **kwargs):
13
+ for thread in self.threads:
14
+ thread.stop(*args, **kwargs)
15
+
16
+ def register_threaded_operation(self, op):
17
+ self.threads.append(op)
18
+
19
+
20
+ thread_manager = ThreadedOperationsManager()
21
+
22
+
23
+ class ControlledThreadedOperation:
24
+ def __init__(self):
25
+ self._running = False
26
+ self._thread = threading.Thread(target=self.run)
27
+ thread_manager.register_threaded_operation(self)
28
+
29
+ def start(self):
30
+ self._running = True
31
+ self._thread.start()
32
+
33
+ def stop(self, *args, **kwargs):
34
+ self._running = False
35
+
36
+ @property
37
+ def is_running(self):
38
+ return self._running
39
+
40
+ @property
41
+ def is_completed(self):
42
+ return self._running is False
43
+
44
+ def run(self):
45
+ while self._running:
46
+ try:
47
+ self._run_once()
48
+ except Exception:
49
+ time.sleep(0.001)
50
+
51
+ def _run_once(self):
52
+ time.sleep(0.001)
53
+
54
+
55
+ class RsyncFlavour(Enum):
56
+ SAMBA_RSYNC = "SAMBA_RSYNC"
57
+ OPEN_RSYNC = "OPENRSYNC"
58
+
59
+ @property
60
+ def executable(self):
61
+ if platform.system() == "Darwin": # Check if running on macOS
62
+ if self == RsyncFlavour.SAMBA_RSYNC:
63
+ return "/opt/homebrew/bin/rsync"
64
+ elif self == RsyncFlavour.OPEN_RSYNC:
65
+ return "/usr/bin/rsync"
66
+
67
+ # Default fallback for other platforms or if paths don't exist
68
+ return "rsync"
69
+
70
+ @property
71
+ def exists_installed(self):
72
+ try:
73
+ # Check if the executable exists and is executable
74
+ result = subprocess.run(
75
+ ["which", self.executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
76
+ )
77
+ if result.returncode != 0:
78
+ return False
79
+
80
+ # Additionally verify it can be executed
81
+ result = subprocess.run(
82
+ [self.executable, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
83
+ )
84
+ return result.returncode == 0
85
+ except Exception:
86
+ return False
87
+
88
+
89
+ def check_rsync() -> RsyncFlavour:
90
+ try:
91
+ result = subprocess.run(["rsync", "--version"], capture_output=True, text=True)
92
+ version_text = result.stdout
93
+
94
+ if "rsync.samba.org/" in version_text:
95
+ return RsyncFlavour.SAMBA_RSYNC
96
+ else:
97
+ # Default to OpenRsync if we can't positively identify Samba rsync
98
+ return RsyncFlavour.OPEN_RSYNC
99
+ except (subprocess.SubprocessError, FileNotFoundError):
100
+ # If rsync isn't available or fails, default to Samba rsync
101
+ return RsyncFlavour.SAMBA_RSYNC
@@ -13,6 +13,7 @@ from watchdog.observers import Observer
13
13
  from watchdog.events import FileSystemEventHandler
14
14
 
15
15
  import dfsync.filters as filters
16
+ import dfsync.lib as lib
16
17
  from dfsync.backends import rsync_backend, kube_backend
17
18
  from dfsync.distribution import (
18
19
  get_installed_version,
@@ -25,7 +26,6 @@ from dfsync.distribution import (
25
26
  from dfsync.config import read_config
26
27
  from dfsync.char_ui import KeyController
27
28
  from dfsync.kube_credentials import contextualize_kube_credentials, update_local_kube_config, normalized_k8s_url
28
- from dfsync.lib import ControlledThreadedOperation, thread_manager
29
29
 
30
30
  logging.basicConfig(level=logging.WARN)
31
31
 
@@ -44,7 +44,11 @@ class RelatedLocationsError(ValueError):
44
44
  pass
45
45
 
46
46
 
47
- class FileChangedEventHandler(ControlledThreadedOperation, FileSystemEventHandler):
47
+ class UnsupportedRsyncError(ValueError):
48
+ pass
49
+
50
+
51
+ class FileChangedEventHandler(lib.ControlledThreadedOperation, FileSystemEventHandler):
48
52
  def __init__(
49
53
  self,
50
54
  backend: str = "log",
@@ -258,7 +262,7 @@ def main():
258
262
  pass
259
263
 
260
264
 
261
- @main.command()
265
+ @main.command(add_help_option=False)
262
266
  def version():
263
267
  """
264
268
  Print the currently installed version
@@ -279,17 +283,16 @@ def _version():
279
283
  click.echo(f"Latest version is {latest_version}, please update using `dfsync self-update`")
280
284
 
281
285
 
282
- @main.command(name="help", context_settings=dict(ignore_unknown_options=True))
283
- @click.option("--help", is_flag=True, default=False, hidden=True)
284
- def help(help):
286
+ @main.command(name="help", add_help_option=False)
287
+ def help():
285
288
  """
286
289
  Print the usage/help for dfsync
287
290
  """
288
- with click.Context(sync, info_name="dfsync") as ctx:
291
+ with click.Context(sync, info_name="dfsync", max_content_width=999) as ctx:
289
292
  click.echo(sync.get_help(ctx))
290
293
 
291
294
 
292
- @main.command()
295
+ @main.command(add_help_option=False)
293
296
  def self_update():
294
297
  """
295
298
  Update dfsync to the latest version published on pypi
@@ -395,6 +398,11 @@ def sync(source, destination, supervisor, kube_host, pod_timeout, full_sync, ver
395
398
  elif len(missing) > 0:
396
399
  click.echo(f"Using source file/dirs {', '.join(paths)}")
397
400
 
401
+ installed_rsync = lib.check_rsync()
402
+ click.echo(f"Installed: {installed_rsync.executable}")
403
+ if installed_rsync != lib.RsyncFlavour.SAMBA_RSYNC:
404
+ raise UnsupportedRsyncError()
405
+
398
406
  backend_options = dict(
399
407
  destination_dir=destination_dir,
400
408
  supervisor=supervisor,
@@ -423,6 +431,14 @@ def sync(source, destination, supervisor, kube_host, pod_timeout, full_sync, ver
423
431
  click.echo(str(e))
424
432
  return -1
425
433
 
434
+ except UnsupportedRsyncError:
435
+ click.echo(
436
+ "\nStarting from Mac OSX Sequoia 15.4, Apple are shipping openrsync instead of the GPL-licensed rsync from Samba"
437
+ "\nThe Apple version of rsync has less features and is currently not supported by dfsync"
438
+ "\nPlease use homebrew to install the GPL-licensed rsync from Samba with this command `brew install rsync`."
439
+ )
440
+ return -1
441
+
426
442
  except ValueError as e:
427
443
  click.echo(
428
444
  "Trying {} from source: {} to destination: '{}'\n".format(
@@ -455,7 +471,7 @@ def sync(source, destination, supervisor, kube_host, pod_timeout, full_sync, ver
455
471
  controller.on_key(
456
472
  "x",
457
473
  description="to exit",
458
- action=partial(thread_manager.stop, "Exiting."),
474
+ action=partial(lib.thread_manager.stop, "Exiting."),
459
475
  )
460
476
 
461
477
  try:
@@ -481,16 +497,16 @@ def sync(source, destination, supervisor, kube_host, pod_timeout, full_sync, ver
481
497
  controller.raise_exceptions()
482
498
 
483
499
  except KeyboardInterrupt:
484
- thread_manager.stop()
500
+ lib.thread_manager.stop()
485
501
  click.echo("Received [Ctrl-C], exiting.")
486
502
 
487
503
  except:
488
- thread_manager.stop()
504
+ lib.thread_manager.stop()
489
505
  raise
490
506
 
491
507
  finally:
492
508
  observer.stop()
493
- thread_manager.stop()
509
+ lib.thread_manager.stop()
494
510
  backend_engine.on_monitor_exit(**backend_options)
495
511
  if observer.ident is not None:
496
512
  # only join the observer if it was previously started
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dfsync"
3
- version = "0.4.9"
3
+ version = "0.4.10"
4
4
  description = ""
5
5
  authors = ["Mihai Balint <balint.mihai@gmail.com>"]
6
6
 
@@ -1,49 +0,0 @@
1
- import time
2
- import threading
3
-
4
-
5
- class ThreadedOperationsManager:
6
- def __init__(self):
7
- self.threads = []
8
-
9
- def stop(self, *args, **kwargs):
10
- for thread in self.threads:
11
- thread.stop(*args, **kwargs)
12
-
13
- def register_threaded_operation(self, op):
14
- self.threads.append(op)
15
-
16
-
17
- thread_manager = ThreadedOperationsManager()
18
-
19
-
20
- class ControlledThreadedOperation:
21
- def __init__(self):
22
- self._running = False
23
- self._thread = threading.Thread(target=self.run)
24
- thread_manager.register_threaded_operation(self)
25
-
26
- def start(self):
27
- self._running = True
28
- self._thread.start()
29
-
30
- def stop(self, *args, **kwargs):
31
- self._running = False
32
-
33
- @property
34
- def is_running(self):
35
- return self._running
36
-
37
- @property
38
- def is_completed(self):
39
- return self._running is False
40
-
41
- def run(self):
42
- while self._running:
43
- try:
44
- self._run_once()
45
- except Exception:
46
- time.sleep(0.001)
47
-
48
- def _run_once(self):
49
- time.sleep(0.001)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes