Alors, en espérant que vous avez donnés le meilleur de vous même.
Commençons par mon interface et par les fonctionnalités que mon programme contient.
Comme vous le voyez, j'ai une
listbox qui me permet de lister les répertoires dont je veux la sauvegarde.
Viens ensuite un bouton d'ajout et de suppression des répertoires, l'ajout se fait par
FolderBrowserDialog, la suppression par lignes sélectionnées dans la
listbox.
Un petit menu de configuration dans lequel on spécifie le dossier où placer les sauvegardes. Lors du clic sur la
textbox un
folderbrowserdialog s'ouvre et c'est sa sélection qui remplira la
textbox.
Des
NumericUpDown (un contrôle) permettant de spécifier un nombre avec le clavier ou grâce à des boutons haut et bas.
Puis un bouton pour enregistrer la configuration et un second pour effectuer une sauvegarde manuelle.
Bon je vais vous montrer le code tout de suite, on va développer section par section.
Secret (cliquez pour afficher)
Code : VB.NET 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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256 | Imports System.IO
Public Class ZBackup
'Définit les constantes
Const FichierIni As String = "Zbackup.ini"
Const LignesFichierIni As Integer = 6
Const CleSavePath As String = "SavePath"
Const CleTempSave As String = "TempSave"
Const CleNbSaves As String = "NbSaves"
Const ClePaths As String = "Paths"
Private Sub ZBackup_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Configure le timer
Me.TIM_SAVE.Interval = Me.NUM_SAVETIME.Value * 3600000 'Convertir une heure une milisecondes
Me.TIM_SAVE.Enabled = True
'Recupère la configuration enregistrée
If RecupereInfosFichierIni() Then
'Effectue d'office une sauvegarde
Sauvegarde()
End If
End Sub
#Region "Interface"
Private Sub TXT_SAVEPATH_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TXT_SAVEPATH.Click
'Ajoute la ligne seulement si un dossier a été selectioné dans le dialogue
If Me.BD_DOSSIER.ShowDialog() Then
Me.TXT_SAVEPATH.Text = Me.BD_DOSSIER.SelectedPath
End If
End Sub
Private Sub BT_AJOUT_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BT_AJOUT.Click
'Ajoute la ligne seulement si un dossier a été selectionné dans le dialogue
If Me.BD_DOSSIER.ShowDialog Then
Me.LB_PATHSASAVE.Items.Add(Me.BD_DOSSIER.SelectedPath)
End If
End Sub
Private Sub BT_SUPPR_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BT_SUPPR.Click
'Vérifie si une ligne est sélectionnée dans la listbox
If Not Me.LB_PATHSASAVE.SelectedItem Is Nothing Then
Me.LB_PATHSASAVE.Items.Remove(Me.LB_PATHSASAVE.SelectedItem)
Else
MsgBox("Selectionnez un path à supprimer")
End If
End Sub
Private Sub BT_SAVECFG_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BT_SAVECFG.Click
SauvegardeFichierIni()
End Sub
Private Sub TIM_SAVE_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TIM_SAVE.Tick
'Sauvegarde avec le timer
Sauvegarde()
End Sub
Private Sub BT_MANUSAVE_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BT_MANUSAVE.Click
'Sauvegarde manuelle
Sauvegarde()
End Sub
#End Region
#Region "FichierIni"
Sub SauvegardeFichierIni()
'Verification sur le path de sauvegarde
If Me.TXT_SAVEPATH.Text = "" Then
MsgBox("Veuillez selectionner un path de sauvegarde")
ElseIf Not Directory.Exists(Me.TXT_SAVEPATH.Text) Then
MsgBox("Path de sauvegarde invalide")
Else
'La fonction recrée le fichier quoi qu'il arrive
File.WriteAllLines(FichierIni, CreeStructureFichierIni(Me.TXT_SAVEPATH.Text, Me.NUM_SAVETIME.Value, Me.NUM_NBSAVE.Value, Me.LB_PATHSASAVE.Items))
End If
End Sub
Function CreeStructureFichierIni(ByVal SavePath As String, ByVal TempsSave As Integer, ByVal Nbsaves As Integer, ByVal PathsASave As ListBox.ObjectCollection) As String()
'Crée un tableau du nombre de lignes requises
Dim FichierIni(LignesFichierIni + PathsASave.Count) As String
'Replit la structure du fichier ini
FichierIni(0) = ";Fichier de configuration du Zbackup"
FichierIni(1) = "[configuration]"
FichierIni(2) = CleSavePath & "=" & SavePath
FichierIni(3) = CleTempSave & "=" & TempsSave
FichierIni(4) = CleNbSaves & "=" & Nbsaves
FichierIni(5) = ""
FichierIni(6) = "[paths]"
'Rempli dynamiquement les paths souhaités
Dim Compteur As Integer = LignesFichierIni
For Each Path As String In PathsASave
Compteur += 1
FichierIni(Compteur) = ClePaths & Compteur - LignesFichierIni & "=" & Path
Next
Return FichierIni
End Function
Function RecupereCleFichierIni(ByVal Cle As String) As String
For Each Ligne As String In File.ReadAllLines(FichierIni)
'Découpe la ligne au niveau de "=" (s'il existe),
'compare la premiere partie de la ligne (soit la clé)
If Ligne.Split("=")(0) = Cle Then
'Recupère la seconde partie de la ligne (soit la valeur)
Return Ligne.Split("=")(1)
End If
Next
'Sinon ne retourne rien
Return ""
End Function
Function RecupereInfosFichierIni() As Boolean
'Verification de la présence du .ini
If File.Exists(FichierIni) Then
'Recuperation
Dim SavePath As String = RecupereCleFichierIni(CleSavePath)
Dim TempSAve As String = RecupereCleFichierIni(CleTempSave)
Dim NbSaves As String = RecupereCleFichierIni(CleNbSaves)
Dim Paths(100) As String
Dim i As Integer = 0 '0 car le premier path est à 1 et on incrémente avant
Do
i += 1
Paths(i - 1) = RecupereCleFichierIni(ClePaths & i)
Loop While Paths(i - 1) <> ""
'Donc nombre de paths valides : i-1
'Verification
If (SavePath <> "") And (TempSAve <> "") And (NbSaves <> "") And (i - 1 > 0) Then
'Attribution
Me.TXT_SAVEPATH.Text = SavePath
Me.NUM_NBSAVE.Value = NbSaves
Me.NUM_SAVETIME.Value = TempSAve
'Nettoie le LB puis le remplit
Me.LB_PATHSASAVE.Items.Clear()
For j As Integer = 0 To i - 1
Me.LB_PATHSASAVE.Items.Add(Paths(j))
Next
Else
'Sinon notification
MsgBox("Le fichier " & FichierIni & " est corrompu, utilisation des paramètres par défaut")
Return False
End If
Else
'Sinon notification
MsgBox("Le fichier " & FichierIni & " n'a pas été trouvé, utilisation des paramètres par défaut")
Return False
End If
Return True
End Function
#End Region
#Region "Sauvegarde"
Sub Sauvegarde()
'Vérifie les paramètres
If Directory.Exists(Me.TXT_SAVEPATH.Text) Then
'Vérifie le nombre de sauvegardes
'Supprime la plus vieille si limite atteinte
NettoieNbSaves()
'Si le dernier caractère de la chaine est un "\" on le supprime
If Me.TXT_SAVEPATH.Text.EndsWith("\") Then
Me.TXT_SAVEPATH.Text.Trim("\")
End If
'Crée le dossier de sauvegarde avec un nom spécifié
'Supprime les / et : de la date et de l'heure
Dim DossierSave As String = Me.TXT_SAVEPATH.Text & "\Sauvegarde du " & Date.Now.ToShortDateString.Replace("/", "-") & " a " & Date.Now.ToShortTimeString.Replace(":", "-")
If Not Directory.Exists(DossierSave) Then 's'il exciste : 2saves dans la même minute, on ne la fait pas
Directory.CreateDirectory(DossierSave)
'Pour chaque path demandé, copie son dossier
For Each PathASave As String In Me.LB_PATHSASAVE.Items
If Directory.Exists(PathASave) Then
CopieDossier(New DirectoryInfo(PathASave), New DirectoryInfo(DossierSave & "\" & Path.GetFileName(PathASave)))
End If
Next
End If
Else
MsgBox("Sauvegarde échouée : le path de sauvegarde est invalide, veuillez le redéfinir")
End If
End Sub
Sub NettoieNbSaves()
Dim Compteur As Integer = 1
For Each Repertoire As String In Directory.GetDirectories(Me.TXT_SAVEPATH.Text)
'Si le repertoire est un répertoire de sauvegarde
If Path.GetFileName(Repertoire).Contains("Sauvegarde") Then
'Incrementation du compteur
Compteur += 1
End If
Next
If Compteur > Me.NUM_NBSAVE.Value Then
'Détermination du plus ancien
Dim PlusAncien As DirectoryInfo
Dim DatePlusAncienne As Date = Date.Now
For Each Repertoire As String In Directory.GetDirectories(Me.TXT_SAVEPATH.Text)
'Si le repertoire est un répertoire de sauvegarde
If Path.GetFileName(Repertoire).Contains("Sauvegarde") Then
'Si le répertoire est plus ancien de que le précédent
If (Directory.GetCreationTime(Repertoire) < DatePlusAncienne) Then
'On le place en plus ancien
DatePlusAncienne = Directory.GetCreationTime(Repertoire)
PlusAncien = New DirectoryInfo(Repertoire)
End If
End If
Next
'Supprime le plus vieux
If PlusAncien.Exists Then
PlusAncien.Delete(True)
End If
End If
End Sub
Sub CopieDossier(ByVal DossierSource As DirectoryInfo, ByVal DossierDesination As DirectoryInfo)
'Crée le dossier
DossierDesination.Create()
'Copie les fichiers
For Each Fichier As FileInfo In DossierSource.GetFiles()
Fichier.CopyTo(Path.Combine(DossierDesination.FullName, Fichier.Name))
Next
'Recommence pour les sous-repertoires
For Each SousRepertoire As DirectoryInfo In DossierSource.GetDirectories()
CopieDossier(SousRepertoire, DossierDesination.CreateSubdirectory(SousRepertoire.Name))
Next
End Sub
#End Region
End Class
|
Eh bien, ça devient conséquent.
Comme vous pouvez le voir dès la première ligne, j'ai essayé de rendre le programme "flexible". Autrement dit, il me suffit de changer les constantes pour changer le nom du fichier ini par exemple, c'est cette constante qui est utilisée à chaque fois qu'une fonction demande le nom de ce fichier.
Trois grandes sections se distinguent :
- L'interface, contenant la réaction aux boutons, etc ...
- Le fichier ini, contenant tout ce qui touche à la configuration.
- Finalement la sauvegarde.
Une rapide vue d'ensemble du fonctionnement :
- Récupération de la configuration
- Si elle n'existe pas création du fichier ini
- Si elle est corrompue, recréation du fichier ini
- A chaque tick de timer (timer configuré sur le temps souhaité entre 2 sauvegardes), on effectue la sauvegarde
- Avec le bouton sauvegarde manuelle la même action est réalisée
- Le sauvegarde consiste a créer un dossier sous la forme "Sauvegarde du DD-MM-AAAA a HH-MM"
- Puis copie l'intégralité des dossiers en respectant leur arborescence.
L'interface
Voilà donc, commençons par analyse le plus simple : l'interface.
Première information : l'ouverture de la
folderbrowserdialog lors du clic sur la textbox.
Eh bien, rien de sorcier, l'évènement clic de la textbox !
Code : VB.NET | Private Sub TXT_SAVEPATH_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TXT_SAVEPATH.Click
'Ajoute la ligne seulement si un dossier a été selectioné dans le dialogue
If Me.BD_DOSSIER.ShowDialog() Then
Me.TXT_SAVEPATH.Text = Me.BD_DOSSIER.SelectedPath
End If
End Sub
|
Eh bien lors du clic on se sert du contrôle "BD_DOSSIER" qui est mon
FolserBrowserDialog. Vu que c'est une boite de dialogue, c'est la fonction
ShowDialog qui est appellée. Puis on récupère la sélection avec le propriété
SelectedPath.
Le bouton d'ajout à le même principe mais il agit sur la listbox, sur le système que le treeview que nous avons étudiés auparavant, il faut créer, non plus des nodes, mais des items.
Code : VB.NET | Me.LB_PATHSASAVE.Items.Add(Me.BD_DOSSIER.SelectedPath)
|
C'est dans le membre "
Items" que les fonctions spécifiques a ces objets sont trouvables. La méthode
Add() permet d'ajouter un item, avec comme valeur le dossier sélectionné.
Pour la suppression :
Code : VB.NET | 'Vérifie si une ligne est sélectionnée dans la listbox
If Not Me.LB_PATHSASAVE.SelectedItem Is Nothing Then
Me.LB_PATHSASAVE.Items.Remove(Me.LB_PATHSASAVE.SelectedItem)
Else
MsgBox("Selectionnez un path à supprimer")
End If
|
Vous vous apercevez qu'une vérification est faite pour voir si une ligne est sélectionnée avec
If Not Me.LB_PATHSASAVE.SelectedItem Is Nothing Then
. Vous vous apercevez que je n'utilise pas le symbole "
<>" pour dire différent mais "
not ... is nothing" c'est une autre notation plus littérale, tout dépend des goûts de chacun.
Ensuite on supprime avec
Items.Remove en passant comme paramètre la ligne selectionnée.
Pour les autres boutons, la sauvegarde des paramètres appelle la méthode
SauvegardeFichierIni() que nous allons étudier. Le timer et la sauvegarde manuelle appellent la méthode
Sauvegarde() que nous allons aussi étudier.
Sauvegarde en fichier .ini
Attaquons tout de suite la sauvegarde.
Pour ce qui est de cette sauvegarde, je vérifie la présence d'un dossier dans la textbox et si ce dossier est valide. Ensuite j'appelle la fonction
File.WriteAllLines(FichierIni, CreeStructureFichierIni(Me.TXT_SAVEPATH.Text, Me.NUM_SAVETIME.Value, Me.NUM_NBSAVE.Value, Me.LB_PATHSASAVE.Items))
qui s'occupe de créer un fichier et d'entrer dedans un tableau de string (une case de tableau pour une ligne).
En premier paramètre, le fichier de destination, c'est notre constante avec le nom du fichier ini. Le second, autrement dit le tableau de string, c'est une fonction que nous allons étudier tout de suite :
Code : VB.NET 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | Function CreeStructureFichierIni(ByVal SavePath As String, ByVal TempsSave As Integer, ByVal Nbsaves As Integer, ByVal PathsASave As ListBox.ObjectCollection) As String()
'Crée un tableau du nombre de lignes requises
Dim FichierIni(LignesFichierIni + PathsASave.Count) As String
'Replit la structure du fichier ini
FichierIni(0) = ";Fichier de configuration du Zbackup"
FichierIni(1) = "[configuration]"
FichierIni(2) = CleSavePath & "=" & SavePath
FichierIni(3) = CleTempSave & "=" & TempsSave
FichierIni(4) = CleNbSaves & "=" & Nbsaves
FichierIni(5) = ""
FichierIni(6) = "[paths]"
'Rempli dynamiquement les paths souhaités
Dim Compteur As Integer = LignesFichierIni
For Each Path As String In PathsASave
Compteur += 1
FichierIni(Compteur) = ClePaths & Compteur - LignesFichierIni & "=" & Path
Next
Return FichierIni
End Function
|
Les valeurs passées en paramètres auraient pu être remplacées par des récupérations directement à l'intérieur de la fonction. Bref. Un tableau est crée avec comme taille le nombre de lignes initiales plus le nombre de paths à insérer.
Pour les premières lignes, j'écris manuellement dedans les premières clés. Ce qui nous donne dans le fichier ini une fois crée :
Code : Autre1
2
3
4
5
6
7
8
| ;Fichier de configuration du Zbackup
[configuration]
SavePath=C:\Save
TempSave=1
NbSaves=3
[paths]
Paths1=C:\ASave |
Deux sections donc : une configuration, une autre paths.
La section configuration contient le répertoire où sauvegarder, le temps entre 2 saves et le nombre de saves max.
La section paths contient les différents paths, tous avec un numéro différent. Les techniques peuvent différer, il aurait été possible de mettre tous les paths sur la même ligne, séparés par des ";".
Bref. La création du fichier n'est pas sorcier, le tableau de variables "FichierIni()" est renvoyée et est écrite dans le fichier.
Maintenant que vous avez vus comment le remplir, voyons comment récupérer les valeurs.
Donc pour cela une petite fonction à laquelle on passe en paramètre la clé à récupérer.
Code : VB.NET 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | Function RecupereCleFichierIni(ByVal Cle As String) As String
For Each Ligne As String In File.ReadAllLines(FichierIni)
'Découpe la ligne au niveau de "=" (s'il existe),
'compare la premiere partie de la ligne (soit la clé)
If Ligne.Split("=")(0) = Cle Then
'Recupère la seconde partie de la ligne (soit la valeur)
Return Ligne.Split("=")(1)
End If
Next
'Sinon ne retourne rien
Return ""
End Function
|
Principe de cette fonction : on parcours toutes les lignes du ini, à chaque ligne on la découpe grâce à la fonction
Split().
La fonction split s'applique sur une chaine de caractères, elle permet de "découper" cette chaine à chaque occurrence du caractère ou de la chaine passée en argument. Donc exemple :
Pour une chaine de caractère sous la forme "Cle1=Valeur1" un
Split("=")
donnera un tableau de string sous la forme.
Tableau(0) = Cle1
Tableau(1) = Valeur1
Donc un bête et méchant test sur le tableau(0) qui est retourné avec la clé souhaitée nous indique la ligne contenant la clé voulue. Une fois cette ligne atteinte, le tableau(1), celui contenant la valeur est retourné.
Si la clé n'est pas trouvée, on retourne "".
Il fallait y penser.
Sauvegarde
Attaquons tout de suite le principe de la sauvegarde.
La méthode
Sauvegarde() effectue diverses vérification sur la présence des dossiers, elle crée le dossier dans lequel la sauvegarde va être placée et lance la méthode
CopieDossier() que voici :
Code : VB.NET 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | Sub CopieDossier(ByVal DossierSource As DirectoryInfo, ByVal DossierDesination As DirectoryInfo)
'Crée le dossier
DossierDesination.Create()
'Copie les fichiers
For Each Fichier As FileInfo In DossierSource.GetFiles()
Fichier.CopyTo(Path.Combine(DossierDesination.FullName, Fichier.Name))
Next
'Recommence pour les sous-repertoires
For Each SousRepertoire As DirectoryInfo In DossierSource.GetDirectories()
CopieDossier(SousRepertoire, DossierDesination.CreateSubdirectory(SousRepertoire.Name))
Next
End Sub
|
Cette méthode prend comme arguments des variables de type DirectoryInfo. Ce n'est pas des variables communes : ce sont des objets. Il faut donc les instancier avec un
new.
Lors de l'appel de la ligne avec
CopieDossier(New DirectoryInfo(PathASave), New DirectoryInfo(DossierSave & "\" & Path.GetFileName(PathASave)))
, j'instance deux objets avec comme paramètre les chemins des dossiers voulus.
Une fois dans la méthode de copie, un dossier est tout d'abord crée, les fichiers contenus y sont copiés également puis cette action est répétée pour tous ses sous répertoires. De la même manière que le treeview du chapitre précédent.
Il y a finalement la méthode de nettoyage des sauvegardes (si on demande qu'un certain nombre de sauvegardes). Elle parcourt les répertoires crées, récupère la date de création de chacun, identifie le plus vieux et le supprime en utilisant la récursivité de la méthode Directory.Delete.
Eh bien voilà. Le code décortiqué.
Allons un peu plus loin.