Skip to content

Commit 0bb7bbf

Browse files
committed
selecteur de fichiers
1 parent ebef212 commit 0bb7bbf

2 files changed

Lines changed: 47 additions & 16 deletions

File tree

‎config.json.exemple‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"source_dir": "/home/source",
3-
"dest_dir": "/mnt/destination"
3+
"dest_dir": "/mnt/destination",
4+
"patterns": ["*.pdf"]
45
}

‎watch_and_copy.py‎

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,24 @@
66
Les répertoires source/destination peuvent être fournis en ligne de commande,
77
sinon ils sont lus dans config.json (à côté de ce script).
88
9+
Des motifs de fichiers optionnels (ex. "*.pdf") permettent de ne copier que les
10+
fichiers dont le nom correspond (insensible à la casse). Sans motif, tous les
11+
fichiers sont copiés.
12+
913
Usage:
10-
python watch_and_copy.py run [<source_dir> <dest_dir>] # foreground
11-
python watch_and_copy.py start [<source_dir> <dest_dir>] # daemon
14+
python watch_and_copy.py run [<source_dir> <dest_dir> [<motif>...]] # foreground
15+
python watch_and_copy.py start [<source_dir> <dest_dir> [<motif>...]] # daemon
1216
python watch_and_copy.py stop
1317
python watch_and_copy.py status
14-
python watch_and_copy.py restart [<source_dir> <dest_dir>] # daemon
18+
python watch_and_copy.py restart [<source_dir> <dest_dir> [<motif>...]] # daemon
1519
"""
1620

1721
import os
1822
import sys
1923
import json
2024
import shutil
2125
import signal
26+
import fnmatch
2227
import logging
2328
from pathlib import Path
2429
from watchdog.observers import Observer
@@ -45,14 +50,32 @@ def setup_logging(foreground: bool = False):
4550
)
4651

4752

53+
def parse_patterns(raw) -> list[str]:
54+
"""Accepte une chaîne ("*.pdf" ou "*.pdf,*.docx") ou une liste de motifs."""
55+
if not raw:
56+
return []
57+
if isinstance(raw, str):
58+
raw = raw.split(",")
59+
return [p.strip() for p in raw if p and p.strip()]
60+
61+
4862
class CopyOnCreate(FileSystemEventHandler):
49-
def __init__(self, dest: Path):
63+
def __init__(self, dest: Path, patterns: list[str] | None = None):
5064
self.dest = dest
65+
self.patterns = [p.lower() for p in (patterns or [])]
66+
67+
def matches(self, name: str) -> bool:
68+
if not self.patterns:
69+
return True
70+
name = name.lower()
71+
return any(fnmatch.fnmatchcase(name, p) for p in self.patterns)
5172

5273
def on_created(self, event):
5374
if event.is_directory:
5475
return
5576
src = Path(event.src_path if isinstance(event.src_path, str) else event.src_path.decode())
77+
if not self.matches(src.name):
78+
return
5679
dst = self.dest / src.name
5780
if dst.exists():
5881
logging.info(f"Ignoré {src} : {dst} existe déjà")
@@ -87,7 +110,7 @@ def get_pid() -> int | None:
87110
return None
88111

89112

90-
def do_run(source: Path, dest: Path):
113+
def do_run(source: Path, dest: Path, patterns: list[str]):
91114
"""Lance la surveillance en foreground (Ctrl+C pour arrêter)."""
92115
if not source.is_dir():
93116
print(f"Erreur : répertoire source introuvable : {source}")
@@ -98,10 +121,11 @@ def do_run(source: Path, dest: Path):
98121

99122
logging.info(f"Surveillance de : {source}")
100123
logging.info(f"Destination : {dest}")
124+
logging.info(f"Filtre : {', '.join(patterns) or 'tous les fichiers'}")
101125
logging.info("Ctrl+C pour arrêter.")
102126

103127
observer = Observer()
104-
observer.schedule(CopyOnCreate(dest), str(source), recursive=True)
128+
observer.schedule(CopyOnCreate(dest, patterns), str(source), recursive=True)
105129
observer.start()
106130

107131
try:
@@ -112,7 +136,7 @@ def do_run(source: Path, dest: Path):
112136
logging.info("Arrêté.")
113137

114138

115-
def do_start(source: Path, dest: Path):
139+
def do_start(source: Path, dest: Path, patterns: list[str]):
116140
if get_pid() is not None:
117141
print("Le daemon tourne déjà. Utilisez 'restart' pour le relancer.")
118142
sys.exit(1)
@@ -126,6 +150,7 @@ def do_start(source: Path, dest: Path):
126150
print(f"Démarrage du daemon…")
127151
print(f" Source : {source}")
128152
print(f" Destination : {dest}")
153+
print(f" Filtre : {', '.join(patterns) or 'tous les fichiers'}")
129154
print(f" Logs : {LOG_FILE}")
130155
print(f" PID : {PID_FILE}")
131156

@@ -135,9 +160,10 @@ def do_start(source: Path, dest: Path):
135160
logging.info(f"Daemon démarré (PID {os.getpid()})")
136161
logging.info(f"Surveillance de : {source}")
137162
logging.info(f"Destination : {dest}")
163+
logging.info(f"Filtre : {', '.join(patterns) or 'tous les fichiers'}")
138164

139165
observer = Observer()
140-
observer.schedule(CopyOnCreate(dest), str(source), recursive=True)
166+
observer.schedule(CopyOnCreate(dest, patterns), str(source), recursive=True)
141167
observer.start()
142168

143169
def on_signal(signum, frame):
@@ -180,13 +206,15 @@ def do_status():
180206

181207

182208
USAGE = (
183-
f"Usage: {sys.argv[0]} run [<source> <dest>] # foreground\n"
184-
f" {sys.argv[0]} start [<source> <dest>] # daemon\n"
209+
f"Usage: {sys.argv[0]} run [<source> <dest> [<motif>...]] # foreground\n"
210+
f" {sys.argv[0]} start [<source> <dest> [<motif>...]] # daemon\n"
185211
f" {sys.argv[0]} stop\n"
186212
f" {sys.argv[0]} status\n"
187-
f" {sys.argv[0]} restart [<source> <dest>] # daemon\n"
213+
f" {sys.argv[0]} restart [<source> <dest> [<motif>...]] # daemon\n"
188214
f"\n"
189-
f"Si <source>/<dest> sont omis, ils sont lus dans {CONFIG_FILE}."
215+
f"<motif> : filtre sur le nom de fichier, ex. '*.pdf' (à mettre entre guillemets).\n"
216+
f"Si <source>/<dest> sont omis, ils sont lus dans {CONFIG_FILE}\n"
217+
f"(clés source_dir, dest_dir et patterns optionnel)."
190218
)
191219

192220

@@ -202,11 +230,13 @@ def main():
202230
elif cmd == "status":
203231
do_status()
204232
elif cmd in ("run", "start", "restart"):
205-
if len(sys.argv) == 4:
233+
if len(sys.argv) >= 4:
206234
source_raw, dest_raw = sys.argv[2], sys.argv[3]
235+
patterns = parse_patterns(sys.argv[4:])
207236
elif len(sys.argv) == 2:
208237
config = load_config()
209238
source_raw, dest_raw = config.get("source_dir"), config.get("dest_dir")
239+
patterns = parse_patterns(config.get("patterns"))
210240
if not source_raw or not dest_raw:
211241
print(f"Erreur : source_dir/dest_dir manquants dans {CONFIG_FILE}")
212242
sys.exit(1)
@@ -217,11 +247,11 @@ def main():
217247
source = Path(source_raw).resolve()
218248
dest = Path(dest_raw).resolve()
219249
if cmd == "run":
220-
do_run(source, dest)
250+
do_run(source, dest, patterns)
221251
else:
222252
if cmd == "restart":
223253
do_stop()
224-
do_start(source, dest)
254+
do_start(source, dest, patterns)
225255
else:
226256
print(USAGE)
227257
sys.exit(1)

0 commit comments

Comments
 (0)