Archiwista [cz. 9 – Kopie zapasowe]
Nadszedł ten długo oczekiwany moment — w końcu zacznę pracować nad tworzeniem kopii zapasowych. Mniej więcej wiem jak to zrobić, ale nie ukrywam, że będę też improwizował.
Pliki źródłowe
Pierwszym krokiem do zrobienia kopii jest zlokalizowanie pliku, który chcemy zarchiwizować. W tym celu trzeba znaleźć plik zawierający wszystkie ścieżki do plików źródłowych. Na całe szczęście Visual Studio robi to w miarę przystępny sposób.
Podczas dodawania projektów do listy jednym z wymogów jest podanie ścieżki do pliku „.sln” jest to plik zawierający informacje na temat podprojektów znajdujących się w głównym projekcie. Wewnątrz znajdziemy informacje na temat projektów oraz ścieżkę do plików zawierających ich ustawienia wraz z wszystkimi plikami źródłowymi. Przykładowy plik sln:
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 |
Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26430.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Archivist", "Archivist\Archivist.csproj", "{077F0975-1DA3-44CC-BFC4-C497C7CFFD88}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchivistTests", "ArchivistTests\ArchivistTests.csproj", "{B9B519DA-9E6C-42B3-A9C2-2F7E383B4256}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {077F0975-1DA3-44CC-BFC4-C497C7CFFD88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {077F0975-1DA3-44CC-BFC4-C497C7CFFD88}.Debug|Any CPU.Build.0 = Debug|Any CPU {077F0975-1DA3-44CC-BFC4-C497C7CFFD88}.Release|Any CPU.ActiveCfg = Release|Any CPU {077F0975-1DA3-44CC-BFC4-C497C7CFFD88}.Release|Any CPU.Build.0 = Release|Any CPU {B9B519DA-9E6C-42B3-A9C2-2F7E383B4256}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9B519DA-9E6C-42B3-A9C2-2F7E383B4256}.Debug|Any CPU.Build.0 = Debug|Any CPU {B9B519DA-9E6C-42B3-A9C2-2F7E383B4256}.Release|Any CPU.ActiveCfg = Release|Any CPU {B9B519DA-9E6C-42B3-A9C2-2F7E383B4256}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal |
Aby wydobyć informacje z tego pliku skonstruowałem wyrażenie regularne, które zwróci nazwę projektu oraz ścieżkę do pliku z ustawieniami. Metoda odpowiedzialna za zwrócenie listy dostępnych projektów w Solution prezentuje się następująco:
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 |
/// /// Returns information about projects found in .sln file /// /// /// private static List { var ProjectList = new List var SolutionFileContents = IOHelper.GetFileContents(solutionFilePath); string BaseSolutionPath = Path.GetDirectoryName(solutionFilePath); // Get Project Name and Project file path var output = Regex.Matches(SolutionFileContents, "(?:\\s\")(.*?)(?:\",\\s\")(.*?)(?:\")"); foreach (Match match in output) { ProjectList.Add(new ProjectInfo() { ProjectName = match.Groups[1].Value, ProjectPath = Path.GetFileName(match.Groups[2].Value), BasePath = $"{BaseSolutionPath}\\{match.Groups[1].Value}", }); } return ProjectList; } |
Zasada działania jest prosta — otwierany jest plik, następnie wykonywane jest wyrażenie regularne i tworzone są odpowiednie obiekty reprezentujące poszczególne projekty. Struktura przedstawiająca projekt posiada trzy informacje: Nazwę projektu, ścieżkę do pliku konfiguracyjnego oraz ścieżkę do projektu.
W przypadku języka C# plik konfiguracyjny nosi rozszerzenie „.csproj” i cała zawartość jest zapisana w postaci XML. Dlatego skorzystam z dostępnych narzędzi służących do poruszania się po drzewach. Wybrałem LINQ to XML. Wszystkie informacje znajdują się w środku znacznika Project. Dane na temat plików źródłowych znajdują się w kilku znacznikach ItemGroup, poza tym są one przechowywane jako atrybut bądź też wartość znacznika. Dlatego stworzyłem zapytanie LINQ, które zwraca wszystkie elementy znajdujące się wewnątrz Project->ItemGroup nie nazywające się Reference, Resource bądź Import. Dzięki temu będę posiadał tylko te pola, które posiadają potencjalne pliki źródłowe. Kolejnym krokiem jest wyciągnięcie wszystkich informacji na temat plików czy to z atrybutu, czy wartości. Wstępnie sprawdzam również, czy wartość posiada co najmniej jedną kropkę, co będzie sugerowało, że jest to plik.
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 |
private static List { XNamespace msbuild = "http://schemas.microsoft.com/developer/msbuild/2003"; XDocument proj = XDocument.Load($"{project.BasePath}\\{project.ProjectConfigPath}"); var SourceFiles = proj.Element(msbuild + "Project") .Elements(msbuild + "ItemGroup") .Elements() .Where(s => s.Name.LocalName != "Reference" && s.Name.LocalName != "Resource" && s.Name.LocalName != "Import"); List { // Add Projectfile for backup Path.GetFileName(project.ProjectConfigPath) }; foreach (var data in SourceFiles) { // For all compile files that var attribute = data.Attribute("Include"); if (attribute.Value != String.Empty) { SourceFileNames.Add(attribute.Value); } // If value contains '.' that mean it is a file if (data.Value.Contains('.')) { SourceFileNames.Add(data.Value); } } return SourceFileNames; } |
Tworzenie kopii
Mając listę plików źródłowych można przystąpić do tworzenia ich kopii. Zanim jednak program zacznie kopiować pliki musi się upewnić, że jest wystarczająco dużo miejsca na kopię zapasową. W tym celu zakładam, że jeżeli jest mniej wolnego miejsca niż 10MB to program ma przystąpić do procedury czyszczenia. Najpierw spróbuje wyczyścić kopie zapasowe, które najprawdopodobniej nie będą już użyteczne zostawiając po jednej kopii na dany dzień. Natomiast jeżeli nie ma kopii do zwolnienia, bądź zwolnione miejsce nie wystarczy to zostanie wyświetlony komunikat wraz z anulowaniem procesu tworzenia kopii.
Następnym etapem jest kopiowanie plików źródłowych do tymczasowego folderu, który posłuży do tworzenia archiwum. Po skopiowaniu wszystkich plików program jeszcze raz upewni się, czy posiada wystarczającą ilość miejsca na dysku, które użytkownik wskazał jako miejsce docelowe kopii zapasowych. Występuje tutaj identyczny proces jak wcześniej. Jeżeli istnieje taka możliwość, to pamięć jest zwalniana a w innym wypadku jest wyświetlany komunikat i następuje sprzątanie po procesie kopiowania.
Na tym etapie program stworzył nowy folder tymczasowy zawierający wszystkie pliki źródłowe z danego projektu. Następną rzeczą jest wygenerowanie pliku archiwum. Posłużę się w tym przypadku klasą ZipFile dostępną po dodaniu referencji System.IO.Compression.FileSystem. W tym celu stworzyłem metodę CreateZipFile, która pobiera ścieżkę do tymczasowego folderu, ścieżkę do folderu w którym mają pojawić się kopie zapasowe, a także informacje odnośnie do projektu z którego one są.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
private static void CreateZipFile(string temporaryDirectoryPath, string archivePath, ProjectInfo project) { var TodaysDateAsString = $"{ DateTime.Now.ToShortDateString()}".Replace('-', '_'); var TimeAsString = $"{DateTime.Now.ToLongTimeString()}".Replace(':', '_'); string NewFilePath = $"{archivePath}\\Archivist\\{TodaysDateAsString}\\{project.ProjectName}"; if (!Directory.Exists(NewFilePath)) { Directory.CreateDirectory(NewFilePath); } ZipFile.CreateFromDirectory(temporaryDirectoryPath, $"{NewFilePath}\\Archivist_{TimeAsString}.zip"); } |
Po stworzeniu Pliku archiwum następuje usunięcie folderu tymczasowego. Program może teraz przystąpić do tworzenia kopii następnego projektu.
Kończąc
Jedną z ważnych rzeczy do zauważenia jest „Co się stanie gdy użytkownik będzie naciskał bardzo szybko skrót klawiszowy?”. Jedną z możliwości jest blokada tworzenia kopii, ponieważ proces tworzenia jeszcze się nie zakończył — co będzie skutkowało pewnymi odstępami między kopiami, które akurat mogą być potrzebne. Inną możliwością jest stworzenie kolejki, która tworzyłaby kolejno kopie zapasowe. Jeżeli tak się stanie trzeba też pamiętać o tym, że użytkownik ciągle będzie zmieniał pliki więc trzeba by podzielić proces tworzenia kopii na tworzenie folderów tymczasowych oraz archiwizację. Tym sposobem mamy pewność, że za każdym razem kiedy użytkownik użyje skrótu klawiszowego odpowiednia kopia zostanie stworzona i do kolejki zostanie dodana konieczność archiwizowania utworzonego wcześniej folderu tymczasowego. Oczywiście ciężko przewidzieć, który proces zajmuje więcej czasu — kopiowanie pliku czy tworzenie archiwum. Na to pytanie postaram się znaleźć odpowiedź w następnym tygodniu.
[KodNaGit]