66Les répertoires source/destination peuvent être fournis en ligne de commande,
77sinon 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+
913Usage:
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
1721import os
1822import sys
1923import json
2024import shutil
2125import signal
26+ import fnmatch
2227import logging
2328from pathlib import Path
2429from 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+
4862class 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
182208USAGE = (
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