Bonjour,
Le principe d'une sauvegarde est simple: disposer de 2 copies des fichiers importants de sorte qu'une panne ou une maladresse, un virus, un piratage (ransonware?), etc... qui ferait disparaître l'une des copies nous laisserait l'autre intacte. Il est bien sûr recommandé que les 2 copies ne soient pas sur le même support (disque dur)! Le mieux est même que la sauvegarde soit sur un disque amovible (USB par exemple) débranché en dehors des sauvegardes (dans un coffre ignifugée?), ou même hors du bâtiment (liaison FTP sur Internet?). Tout dépend, bien sûr, de l'importance des fichiers, de celle des risques et des moyens disponibles.
On va ici se restreindre à un contexte simple de PC et de fichiers utilisateurs courants: courriers, images, vidéos, sources de programmes développés, etc... Les sauvegardes sont importantes, mais souvent négligées par les amateurs: je me rappelle que dans mon photo-club, certains de ceux qui s'intéressaient aux sauvegardes avaient déjà tout perdu!
Le problème qu'on va résoudre ici est celui-ci: faire une sauvegarde d'un gros répertoire en copiant tout est très long! Un répertoire de plusieurs centaines de Go peut demander plusieurs heures. Pour résoudre cela, il suffit à chaque sauvegarde de se contenter de ne copier que les fichiers absents ou plus récents de la sauvegarde précédente! Cela fera passer la sauvegarde de plusieurs heures à quelques minutes ou quelques secondes. C'est ce qu'on appellera ici: sauvegarde incrémentale (ou incrémentielle).
Il existe déjà de nombreuses solutions existantes pour faire ça. Sous Windows, par exemple, il existe en console "xcopy" et "robocopy" qui marchent très bien mais qui ne sont pas toujours facile à configurer. Toujours sous Windows, un logiciel gratuit comme "SyncBackFree" (https://www.2brightsparks.com/freewa...eware-hub.html) fait ça très bien!
Mais comme c'est amusant à développer soi-même, on ne va pas s'en priver! Le grand avantage, c'est qu'on pourra le personnaliser comme on veut. Quand à la rapidité de la sauvegarde en Python, ce n'est pas un problème, puisque le gros du délai sera consommé par les opérations physiques de lecture-écriture qui utilisent les fonctions de base de l'OS.
Le principe du logiciel proposé est celui-ci:
- on lit le répertoire source, on prend tous ses fichiers un par un, on recalcule pour chacun d'eux son chemin sur le répertoire destination, et on teste: si le fichier destination n'existe pas ou s'il est antérieur au fichier source, on le recopie. il faudra, bien sûr, créer les sous-répertoires nécessaires sur la destination.
- Il y a aussi une option "miroir": on lit le répertoire destination, on prend tous ses fichiers un par un, on recalcule pour chacun d'eux son chemin sur le répertoire source, et on teste: si le fichier source n'existe pas, on le supprime du répertoire destination. Pareil pour les sous-répertoires.
Pour explorer le contenu d'un répertoire, il existe sous Python plusieurs solutions: os.listdir, os.scandir, glob.glob, os.walk. Après plusieurs essais, j'ai choisi ici: os.walk, parce qu’il a au moins deux gros avantages:
- gérer convenablement les erreurs. Il est en effet désagréable de voir un logiciel comme ça se planter sur un des fichiers en erreur en ne traitant pas les suivants, ou pire, ne pas traiter les fichiers en erreur sans rien dire.
- permettre à certains sous-répertoires d'être éliminés des explorations suivantes, ce qui évite d'avoir à traiter ses fichiers (ici pour l'option miroir).
Il existe un autre avantage d'os.walk qu'on n'utilisera pas ici: pouvoir explorer les fichiers de l'arborescence d'un répertoire en commençant par les feuilles et en remontant jusqu'au tronc (option "topdown=False", donc un parcours "bottom up").
Voilà le code principal (largement commenté):
Pour exploiter cette fonction dans une console, on va utiliser le module "argparse", qui permettra de recevoir les arguments passés en ligne de commande sans se tromper. Voilà la partie "argparse":
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 ############################################################################# def copieincrementale(rep1, rep2, miroir=False, fnerreur=None, recursion=True, suiviliens=False): """Fait une sauvegarde incrémentale de rep1 sur rep2, c'est à dire ne copie que les fichiers de rep1, nouveaux pour rep2 ou plus récents. - rep1: le répertoire à sauvegarder - rep2: le répertoire qui recevra les copies de rep1 - miroir: si True: supprime fichiers et répert. de rep2 absents de rep1 - fnerreur: si != None: fonction callback pour le message d'erreur - recursion: si True, traite aussi les sous-répertoires - suiviliens: si True, suit les liens symboliques """ #========================================================================= # calcule les adresses absolues des répertoires donnés rep1 = os.path.abspath(os.path.expanduser(rep1)) rep2 = os.path.abspath(os.path.expanduser(rep2)) #========================================================================= # vérifie l'accès à rep1 if not os.access(rep1, os.X_OK): if fnerreur is not None: fnerreur("Erreur accès à rep1") return # impossible de rentrer dans rep1 #========================================================================= # vérifie l'existence de rep2 if not os.path.exists(rep2): # rep2 n'existe pas: on le crée try: os.mkdir(rep2) except Exception as msgerr: if fnerreur is not None: fnerreur(msgerr) return # impossible de créer le répertoire destination rep2 else: # rep2 existe déjà: on vérifie l'accès if not os.access(rep2, os.X_OK): if fnerreur is not None: fnerreur("Erreur accès à rep2") return # impossible de rentrer dans rep2 #========================================================================= # fait la copie incrémentale lgrep1, lgrep2 = len(rep1), len(rep2) # nb de caractères de rep1 et rep2 for repert, reps, fics in os.walk(rep1, onerror=fnerreur, followlinks=suiviliens): for fic in fics: nfc1 = os.path.join(repert, fic) # fic avec son chemin sur rep1 nfc2 = os.path.join(rep2, repert[lgrep1+1:], fic)# avec chemin sur rep2 if not os.path.exists(nfc2): # crée le chemin nécessaire pour copier le nouveau fichier chemin = os.path.dirname(nfc2) if not os.path.exists(chemin): try: os.makedirs(chemin) except Exception as msgerr: if fnerreur is not None: fnerreur(msgerr) continue # échec => passer au fic suivant # copie le nouveau fichier try: copy2(nfc1, nfc2) print("Copie le nouveau fichier: {}".format(nfc1)) except Exception as msgerr: if fnerreur is not None: fnerreur(msgerr) else: # le fichier fic existe déjà sur rep2: on compare les dates try: if os.path.getmtime(nfc1) > os.path.getmtime(nfc2): # copie le fichier fic de rep1 plus récent que celui de rep2 copy2(nfc1, nfc2) print("Copie le fichier plus récent: {}".format(nfc1)) except Exception as msgerr: # erreur possible avec getmtime ou copy2 if fnerreur is not None: fnerreur(msgerr) if not recursion: break # interrompt la boucle os.walk pour empêcher la récursion #========================================================================= # traite l'effet miroir if miroir: # lecture de rep2 for repert, reps, fics in os.walk(rep2, onerror=fnerreur, followlinks=suiviliens): # supprime les fichiers de rep2 absents de rep1 for fic in fics: nfc2 = os.path.join(repert, fic) nfc1 = os.path.join(rep1, repert[lgrep2+1:], fic) if not os.path.exists(nfc1): # le fichier fic de rep2 absent de rep1 => on le supprime try: os.remove(nfc2) print("Miroir => supprime le fichier: {}".format(nfc2)) except Exception as msgerr: if fnerreur is not None: fnerreur(msgerr) # supprime les répertoires de rep2 absents de rep1 for i in range(len(reps[:])-1, -1, -1): # parcours à l'envers de reps nrc2 = os.path.join(repert, reps[i]) # rep avec chemin sur rep2 nrc1 = os.path.join(rep1, repert[lgrep2+1:], reps[i]) # rep avec chemin sur rep1 if not os.path.exists(nrc1): # répertoire rep de rep2 absent de rep1 => on le supprime try: rmtree(nrc2) print("Miroir => supprime le répertoire: {}".format(nrc2)) reps.pop(i) # on retire rep[i] de l'exploration suivante except Exception as msgerr: if fnerreur is not None: fnerreur(msgerr)
Un des avantages de cette partie, c'est qu'on peut rappeler la syntaxe à utiliser en faisant simplement dans la console:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 ############################################################################# if __name__ == "__main__": #======================================================================== # récupération des données de traitement de la ligne de commande import argparse def str2bool(v): """Traduit les chaines de caractères en booléens """ if v.lower() in ["o", "oui", "y", "yes", "t", "true"]: return True elif v.lower() in ["n", "non", "not", "f", "false"]: return False else: raise Exception ("Erreur: valeur booléenne attendue pour {}".format(v)) # création du parse des arguments parser = argparse.ArgumentParser(description="Sauvegarde incrémentale d'un répertoire") # déclaration et configuration des arguments parser.add_argument('-s', '--source', dest='source', type=str, required=True, action="store", help="Répertoire source") parser.add_argument('-d', '--destination', dest='destination', type=str, required=True, action="store", help="Répertoire destination") parser.add_argument('-m', '--miroir', dest="miroir", type=str2bool, choices=[True, False], default=False, help="Si True, supprime les fichiers et répertoires destination absents de la source") parser.add_argument('-r', '--recursion', dest="recursion", type=str2bool, choices=[True, False], default=True, help="Si True, explore les sous-répertoires") parser.add_argument('-l', '--suiviliens', dest="suiviliens", type=str2bool, choices=[True, False], default=False, help="Si True, suit les liens symboliques") # dictionnaire des arguments passés au lancement dicoargs = vars(parser.parse_args()) # Récupére les données de traitement source = dicoargs["source"] destination = dicoargs["destination"] miroir = dicoargs["miroir"] recursion = dicoargs["recursion"] suiviliens = dicoargs["suiviliens"]
Ce qui affichera:
Code : Sélectionner tout - Visualiser dans une fenêtre à part sauvincrem.py --help
Un exemple de ligne de commande pour un répertoire de courrier (rappel: les chemins des fichiers doivent avoir des guillemets quand il y a des espaces dans ces chemins):
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 usage: sauvincrem.py [-h] -s SOURCE -d DESTINATION [-m {True,False}] [-r {True,False}] [-l {True,False}] Sauvegarde incrémentale d'un répertoire optional arguments: -h, --help show this help message and exit -s SOURCE, --source SOURCE Répertoire source -d DESTINATION, --destination DESTINATION Répertoire destination -m {True,False}, --miroir {True,False} Si True, supprime les fichiers et répertoires destination absents de la source -r {True,False}, --recursion {True,False} Si True, explore les sous-répertoires -l {True,False}, --suiviliens {True,False} Si True, suit les liens symboliques
Bien sûr, la 1ère sauvegarde qu'on fait doit tout copier!
Code : Sélectionner tout - Visualiser dans une fenêtre à part sauvincrem.py -s "E:\Documents_2\Courrier Privé 2" -d "E:\Documents_2\Courrier Privé 2 2" -m True -r True -l False
En cas d'erreur de syntaxe dans la ligne de commande, le logiciel le signale en rappelant la syntaxe à utiliser.
On peut aussi faire que ce qui devrait être affiché sera enregistré dans un fichier "log" en ajoutant à la ligne de commande: "> fichier.log", ou même faire que le traitement soit "silencieux" (=sans affichage) avec "> nul" (toujours sous Windows, mais il y a l’équivalent sous Linux et MacOS).
Après la récupération des données de traitement par argparse, voilà comment on peut lancer la fonction avec ses arguments et récupérer ses résultats:
On voit que, pour cette dernière partie, il faut ajouter 2 fonctions de temps: une fonction qui donne la date et l'heure de l'ordinateur pour donner le moment d'exécution de la sauvegarde, et une fonction donnant le délai du traitement:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 # Sauvegarde erreurs = [] secs = perf_counter() copieincrementale(source, destination, miroir, erreurs.append, recursion, suiviliens) secs = perf_counter()-secs #======================================================================== # Affichage des erreurs s'il y en a if erreurs!=[]: print("Erreurs: {}".format(len(erreurs))) print() for erreur in erreurs: print(erreur) print() #======================================================================== _, _, _, _, msgtps = secs2jhms(secs) print("Sauvegarde terminée en {}".format(msgtps))
On obtient ainsi, si on a utilisé l'option "miroir", un répertoire destination identique au répertoire source.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 ############################################################################# def cejour(): """Retourne la date et l'heure de l'ordinateur: jj/mm/aaaa hh:mm:ss """ dt = datetime.today() return "{}/{}/{} {}:{}:{}".format(dt.day, dt.month, dt.year, dt.hour, dt.minute, int(dt.second)) ############################################################################# def secs2jhms(secs): """Convertit le délai secs (secondes) => jours, heures, minutes, secondes En retour, j, h et m sont des entiers. s est de type float et msgtps (présentation du délai pour affichage) est un str """ m, s = divmod(float(secs), 60) m = int(m) h, m = divmod(m, 60) j, h = divmod(h, 24) if j>0: msgtps = "{:d}j {:d}h {:d}m {:.6f}s".format(j, h, m, s) elif h>0: msgtps = "{:d}h {:d}m {:.6f}s".format(h, m, s) elif m>0: msgtps = "{:d}m {:.6f}s".format(m, s) else: msgtps = "{:.6f}s".format(s) return j, h, m, s, msgtps
Évolution du logiciel: sur cette base simplifiée, on peut ajouter des options comme (c'est ce que j'utilise en fait):
- une sélection des fichiers à sauvegarder: il suffit de mettre des motifs "wildcard" en argument. Par exemple: "*.py;*.pyw".
- une liste de sous-répertoires à exclure de la sauvegarde. Par exemple: "__pycache__".
Mais ça complique un peu le traitement de l'option miroir!
Rien ne vous empêche aussi de faire une version "FTP" si vous pouvez faire cette sauvegarde su un site web à vous!
Au lieu de lancer la sauvegarde par ligne de commande, et dans la mesure où ça sera fréquent et concernera les mêmes répertoires, on peut lancer un script "batch" sous Windows (shell sous Linux et MacOS):
Ce qui lancera la même ligne de commande que plus haut. On n'a plus qu'à créer autant de fichiers comme ça qu'il y a de répertoires à sauvegarder:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 ECHO OFF CHCP 65001 REM Sauvegarde incrémentale REM ========================================================================== SET scriptpy=sauvincrem.py SET source="E:\Documents_2\Courrier Privé 2" SET destination="E:\Documents_2\Courrier Privé 2 2" SET miroir=True SET recursion=True SET suiviliens=False REM ========================================================================== START "" /B /WAIT Python.exe ^ %scriptpy% ^ --source %source% ^ --destination %destination% ^ --miroir %miroir% ^ --recursion %recursion% ^ --suiviliens %suiviliens% PAUSE
Rien ne vous empêche, bien sûr, de faire une version exécutable "autonome" (*.exe sous Windows) avec pyinstaller ou cx_freeze, ce qui permettra d'exécuter ce logiciel de sauvegarde sur des PC sans Python.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 Sauve_Courrier.bat Sauve_PythonDev.bat Sauve_Images.bat Sauve_Videos.bat Etc...
Je n'ai pas testé sur Linux ni sur MacOS, mais comme je n'ai pas utilisé d'instruction spécifique à Windows, ça ne devrait pas poser de problème: n'hésitez pas à me le confirmer, ou à me signaler les difficultés rencontrées!
Voilà la récap de la source complète:
===> Bonnes sauvegardes!
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 #!/usr/bin/python3 # -*- coding: utf-8 -*- """ Sauvegarde incrémentale Fait une sauvegarde incrémentale de rep1 sur rep2, c'est à dire ne copie que les fichiers de rep1, nouveaux pour rep2 ou plus récents. Plus option miroir: efface de rep2 les fichiers et sous-répertoires absents de rep1 """ import os from shutil import copy2, rmtree from datetime import datetime # pour la date/heure de l'ordinateur from time import perf_counter # pour le calcul du temps de traitement ############################################################################# def cejour(): """Retourne la date et l'heure de l'ordinateur: jj/mm/aaaa hh:mm:ss """ dt = datetime.today() return "{}/{}/{} {}:{}:{}".format(dt.day, dt.month, dt.year, dt.hour, dt.minute, int(dt.second)) ############################################################################# def secs2jhms(secs): """Convertit le délai secs (secondes) => jours, heures, minutes, secondes En retour, j, h et m sont des entiers. s est de type float et msgtps (présentation du délai pour affichage) est un str """ m, s = divmod(float(secs), 60) m = int(m) h, m = divmod(m, 60) j, h = divmod(h, 24) if j>0: msgtps = "{:d}j {:d}h {:d}m {:.6f}s".format(j, h, m, s) elif h>0: msgtps = "{:d}h {:d}m {:.6f}s".format(h, m, s) elif m>0: msgtps = "{:d}m {:.6f}s".format(m, s) else: msgtps = "{:.6f}s".format(s) return j, h, m, s, msgtps ############################################################################# def copieincrementale(rep1, rep2, miroir=False, fnerreur=None, recursion=True, suiviliens=False): """Fait une sauvegarde incrémentale de rep1 sur rep2, c'est à dire ne copie que les fichiers de rep1, nouveaux pour rep2 ou plus récents. - rep1: le répertoire à sauvegarder - rep2: le répertoire qui recevra les copies de rep1 - miroir: si True: supprime fichiers et répert. de rep2 absents de rep1 - fnerreur: si != None: fonction callback pour le message d'erreur - recursion: si True, traite aussi les sous-répertoires - suiviliens: si True, suit les liens symboliques """ #========================================================================= # calcule les adresses absolues des répertoires donnés rep1 = os.path.abspath(os.path.expanduser(rep1)) rep2 = os.path.abspath(os.path.expanduser(rep2)) #========================================================================= # vérifie l'accès à rep1 if not os.access(rep1, os.X_OK): if fnerreur is not None: fnerreur("Erreur accès à rep1") return # impossible de rentrer dans rep1 #========================================================================= # vérifie l'existence de rep2 if not os.path.exists(rep2): # rep2 n'existe pas: on le crée try: os.mkdir(rep2) except Exception as msgerr: if fnerreur is not None: fnerreur(msgerr) return # impossible de créer le répertoire destination rep2 else: # rep2 existe déjà: on vérifie l'accès if not os.access(rep2, os.X_OK): if fnerreur is not None: fnerreur("Erreur accès à rep2") return # impossible de rentrer dans rep2 #========================================================================= # fait la copie incrémentale lgrep1, lgrep2 = len(rep1), len(rep2) # nb de caractères de rep1 et rep2 for repert, reps, fics in os.walk(rep1, onerror=fnerreur, followlinks=suiviliens): for fic in fics: nfc1 = os.path.join(repert, fic) # fic avec son chemin sur rep1 nfc2 = os.path.join(rep2, repert[lgrep1+1:], fic)# avec chemin sur rep2 if not os.path.exists(nfc2): # crée le chemin nécessaire pour copier le nouveau fichier chemin = os.path.dirname(nfc2) if not os.path.exists(chemin): try: os.makedirs(chemin) except Exception as msgerr: if fnerreur is not None: fnerreur(msgerr) continue # échec => passer au fic suivant # copie le nouveau fichier try: copy2(nfc1, nfc2) print("Copie le nouveau fichier: {}".format(nfc1)) except Exception as msgerr: if fnerreur is not None: fnerreur(msgerr) else: # le fichier fic existe déjà sur rep2: on compare les dates try: if os.path.getmtime(nfc1) > os.path.getmtime(nfc2): # copie le fichier fic de rep1 plus récent que celui de rep2 copy2(nfc1, nfc2) print("Copie le fichier plus récent: {}".format(nfc1)) except Exception as msgerr: # erreur possible avec getmtime ou copy2 if fnerreur is not None: fnerreur(msgerr) if not recursion: break # interrompt la boucle os.walk pour empêcher la récursion #========================================================================= # traite l'effet miroir if miroir: # lecture de rep2 for repert, reps, fics in os.walk(rep2, onerror=fnerreur, followlinks=suiviliens): # supprime les fichiers de rep2 absents de rep1 for fic in fics: nfc2 = os.path.join(repert, fic) nfc1 = os.path.join(rep1, repert[lgrep2+1:], fic) if not os.path.exists(nfc1): # le fichier fic de rep2 absent de rep1 => on le supprime try: os.remove(nfc2) print("Miroir => supprime le fichier: {}".format(nfc2)) except Exception as msgerr: if fnerreur is not None: fnerreur(msgerr) # supprime les répertoires de rep2 absents de rep1 for i in range(len(reps[:])-1, -1, -1): # parcours à l'envers de reps nrc2 = os.path.join(repert, reps[i]) # rep avec chemin sur rep2 nrc1 = os.path.join(rep1, repert[lgrep2+1:], reps[i]) # rep avec chemin sur rep1 if not os.path.exists(nrc1): # répertoire rep de rep2 absent de rep1 => on le supprime try: rmtree(nrc2) print("Miroir => supprime le répertoire: {}".format(nrc2)) reps.pop(i) # on retire rep[i] de l'exploration suivante except Exception as msgerr: if fnerreur is not None: fnerreur(msgerr) ############################################################################# if __name__ == "__main__": #======================================================================== # récupération des données de traitement de la ligne de commande import argparse def str2bool(v): """Traduit les chaines de caractères en booléens """ if v.lower() in ["o", "oui", "y", "yes", "t", "true"]: return True elif v.lower() in ["n", "non", "not", "f", "false"]: return False else: raise Exception ("Erreur: valeur booléenne attendue pour {}".format(v)) # création du parse des arguments parser = argparse.ArgumentParser(description="Sauvegarde incrémentale d'un répertoire") # déclaration et configuration des arguments parser.add_argument('-s', '--source', dest='source', type=str, required=True, action="store", help="Répertoire source") parser.add_argument('-d', '--destination', dest='destination', type=str, required=True, action="store", help="Répertoire destination") parser.add_argument('-m', '--miroir', dest="miroir", type=str2bool, choices=[True, False], default=False, help="Si True, supprime les fichiers et répertoires destination absents de la source") parser.add_argument('-r', '--recursion', dest="recursion", type=str2bool, choices=[True, False], default=True, help="Si True, explore les sous-répertoires") parser.add_argument('-l', '--suiviliens', dest="suiviliens", type=str2bool, choices=[True, False], default=False, help="Si True, suit les liens symboliques") # dictionnaire des arguments passés au lancement dicoargs = vars(parser.parse_args()) # Récupére les données de traitement source = dicoargs["source"] destination = dicoargs["destination"] miroir = dicoargs["miroir"] recursion = dicoargs["recursion"] suiviliens = dicoargs["suiviliens"] #======================================================================== # en-tête d'affichage print("SAUVEGARDE INCREMENTALE ({})".format(cejour())) print() print("Répertoire source: {}".format(source)) print("Répertoire destination: {}".format(destination)) print("Option miroir: {}".format(miroir)) print("Recursion: {}".format(recursion)) print("Suivi des liens symboliques: {}".format(suiviliens)) print() #======================================================================== # Sauvegarde erreurs = [] secs = perf_counter() copieincrementale(source, destination, miroir, erreurs.append, recursion, suiviliens) secs = perf_counter()-secs #======================================================================== # Affichage des erreurs s'il y en a if erreurs!=[]: print("Erreurs: {}".format(len(erreurs))) print() for erreur in erreurs: print(erreur) print() #======================================================================== _, _, _, _, msgtps = secs2jhms(secs) print("Sauvegarde terminée en {}".format(msgtps))
Partager