/// <summary> /// Metoda Close, jen pro lepší přehlednost - prostě zavolá Dispose /// </summary> public void Close() { FilesIn.Clear(); FilesToAdd.Clear(); FilesToRemove.Clear(); Dispose(); }
protected override bool ReplaceLink(ref IProcessLinkParameter parameters, LinkType linkType) { FoundLink link = ((ConceptualItemParameter)parameters).Link; string href = ((ConceptualItemParameter)parameters).Href; string baseUrl = null; switch (linkType) { case LinkType.GeneralLink: baseUrl = BaseUrl; break; case LinkType.EnUsLink: baseUrl = BaseEnUsUrl; break; case LinkType.RelativeWoExt: baseUrl = BaseWoExtUrl; break; } if (baseUrl == null) { return(false); } if (Uri.TryCreate(new Uri(baseUrl), href.TrimStart('\\').TrimEnd(".md"), out Uri uri)) { string query = GetQueryFromLink(link.Link); if (linkType == LinkType.EnUsLink) { ReplacedEnUsLinks.AppendLine($"{SourceFilePath},{link.Link},\"{link.Title}\",{uri.AbsoluteUri},\"{link.Title + ExternalText}\""); } else { ReplacedLinks.AppendLine($"{SourceFilePath},{link.Link},\"{link.Title}\",{uri.AbsoluteUri},\"{link.Title + ExternalText}\""); string fileToRemove = href.TrimStart('\\'); if (!String.IsNullOrEmpty(query)) { fileToRemove = fileToRemove.Replace(query, ""); } if (Path.GetExtension(fileToRemove).Equals(".md", StringComparison.InvariantCultureIgnoreCase) && !FilesToRemove.Contains(fileToRemove)) { FilesToRemove.Add(fileToRemove); } } _newContent.Replace(link.FullMatch, link.FullMatch.Replace(link.Title, link.Title + ExternalText).Replace(link.Link, uri.AbsoluteUri)); return(true); } Logger.LogWarning($"URI could not be created: {BaseUrl} {href}"); return(false); }
/// <summary> /// Odstraní soubor - rozliší jestli je odstraňovaný soubor už fyzicky v archívu nebo má být teprve přidán po uložení /// </summary> /// <param name="Name">Název souboru k odstranění</param> public void RemoveFile(string Name) { // Zkus soubor prvně najít a odstranit ze seznamu nových souborů k přidání do archívu for (int i = 0; i < FilesToAdd.Count; i++) { // V kolekci FilesToAdd jsou absolutní cesty k souborů, my chceme však jen jejich názvy s příponou k porovnání if (Path.GetFileName(FilesToAdd[i].ToString()) == Name) { FilesToAdd.RemoveAt(i); // Odstraň a skonči metodu return; } } // Když tam nebyl, zkus najít a označit ke smazání takový soubor v seznamu souborů v archívu - když tam nebude metoda skončí vyjímkou (protože pomocní metoda FindFileByName skončí vyjímkou) FilesToRemove.Add(FindFileByName(Name)); }
/// <summary> /// Nejsložitější metoda v celé třídě. Má na starost uložení změn provedených v archívu. /// </summary> public void SaveArchieve() { // Metoda nemá smysl, pokud není otevřen nějaký soubor if (!IsOpened) { throw new ArchieverException("Nepracujete s žádným souborem!"); } // Do tohoto MemoryStreamu načteme všechny staré věci které mají v archívu zůstat (nebyly odstraněny) a zároveň do něj přidáme nové věci. using (MemoryStream MyMemory = new MemoryStream()) { // Musíme tedy projít všechny soubory které chceme zachovat a "přeuložit" je do streamu MyMemory int ToJump = 0, CurrentSize = 0; for (int i = 0; i < FilesIn.Count; i++) { // CurrentSize používám jen jako pomocnou proměnnou abych pořád nemusel dělat toto nepěkné přetypování CurrentSize = ((FileHeader)FilesIn[i]).Size; // Jesliže je momentální index v poli indexů pro odstranění z archívu, vynecháme jej, ale musíme s ním počítat i při přeskoku pomocí Seek if (FilesToRemove.IndexOf(i) > -1) { ToJump += CurrentSize; continue; // Jak jistě víte, příkaz continue ukončí momentální iteraci, ale ne samotný cyklus. } // Přeskoč na daný soubor MyArchieve.Seek((long)(FilesBegin + ToJump), SeekOrigin.Begin); // Opět se zde vědomě dopouštím stejné chyby jako v indexeru, viz. komentáře v kódu indexeru byte[] buffer = new byte[CurrentSize]; MyArchieve.Read(buffer, 0, CurrentSize); MyMemory.Write(buffer, 0, CurrentSize); ToJump += CurrentSize; } // Teď už můžeme z FilesIn skutečně bezpečně odstranit všechny ty indexy, které jsou v poli FilesToRemove foreach (int index in FilesToRemove) { FilesIn.RemoveAt(index); } // Další krok bude přidání nových souborů do archívu... byte[] buf = new byte[1024]; // V BytesRead bude počet bajtů který se přečtl při jedné blokové operaci.V TotalSize nasčítáme počet všech přečtěných bajtů, což je totéž jako velikost souboru int BytesRead = 0, TotalSize = 0; foreach (string PathToFile in FilesToAdd) { try // Zde by mohla nastat spousta špatností, protože pracujeme s filesystémem { // Toto je již velmi známá konstrukce čtení dat po pevných blocích - netřeba dál komentovat. using (FileStream MyNewFile = File.OpenRead(PathToFile)) { TotalSize = 0; while ((BytesRead = MyNewFile.Read(buf, 0, 1024)) > 0) { MyMemory.Write(buf, 0, BytesRead); TotalSize += BytesRead; } } } catch (Exception e) // Chytej jakoukoliv vyjímku { // Vyhoď vyjímku ArchieverException s textem původní vyjímky která nastala throw new ArchieverException(e.Message); } // Všimněte si, že díky bloku using() nemusíme používat finally a v něm stream zavírat! // Když všechno prošlo bez chyby, tak vytvoříme pro nový soubor hlavičku a přidáme ji do pole FilesIn FileHeader MyNewHeader = new FileHeader(); MyNewHeader.Name = Path.GetFileName(PathToFile); MyNewHeader.Size = TotalSize; FilesIn.Add(MyNewHeader); } // Vyčistíme kolekce FilesToAdd a FilesToRemove FilesToAdd.Clear(); FilesToRemove.Clear(); // V posledním kroku prostě přepíšeme původní stream MyArchieve novou hlavičkou a tím co máme v MyMemory streamu MyArchieve.Seek(0, SeekOrigin.Begin); MyArchieve.SetLength(0); // Toto zničí všechny data ve streamu (zkrátí délku souboru na 0) // První čtyři bajty archívu je počet souborů v něm (opět používáme BitConverter) MyArchieve.Write(BitConverter.GetBytes(FilesIn.Count), 0, 4); // Teď zapíšeme názvy souborů (s ohledem na 150 bajtový limit jeho délky) a hned za něj jeho velikost, celkem tedy 154 bajtů (v případě že někdo nezmění NameMaxLength na jinou hodnotu než 150) foreach (FileHeader item in FilesIn) { // Toto pole bajtů má přesně takovou velikost jako je délka názvu souboru v kódování systému byte[] bytesOfName = Encoding.Default.GetBytes(item.Name); // Musíme velikost pole bytesOfName zarovnat na NameMaxLength, což je v našem případě 150 byte[] normalizedName = new byte[NameMaxLength]; // Pozorně prosím prostudujte jak pracuje tento cyklus (kopíruje z pole s názvem souboru do normalizovaného pole od délce NameMaxLength, když je delší - oseká se) for (int i = 0; i < (bytesOfName.Length > NameMaxLength ? NameMaxLength : bytesOfName.Length); i++) { normalizedName[i] = bytesOfName[i]; } MyArchieve.Write(normalizedName, 0, NameMaxLength); MyArchieve.Write(BitConverter.GetBytes(item.Size), 0, 4); } // ... a úplně nakonec zapíšeme všechny soubory, které již máme připraveny v MyMemory - tam jsou ve stejném pořadí tak jako v hlavičce. MyArchieve.Write(MyMemory.ToArray(), 0, (int)MyMemory.Length); } // A HOTOVO! (Všimněte si, že celý kód metody byl v postatě "zabalen" v bloku od using(MemoryStream MyMemory ...) takže ať se stalo cokoliv bude vždycky zavřen! }
public override bool ProcessContentLinks() { string fname = Path.GetFileName(SourceFilePath); bool yml = string.Equals(fname, "toc.yml", StringComparison.InvariantCultureIgnoreCase); bool result = string.Equals(fname, "toc.md", StringComparison.InvariantCultureIgnoreCase) || yml || ContentHasAudienceApplicationUser(_content); if (result) { FoundLink[] links = FindAllLinks(yml, _content); Links.AddRange(links.Select(l => l.FullMatch)); foreach (FoundLink link in links.GroupBy(k => k).Select(k => k.Key)) { if (String.IsNullOrEmpty(link.Link.Trim())) { continue; } string linkClear = CleanLinkOfQueryAndHash(link.Link); if (linkClear.StartsWith("http", StringComparison.InvariantCultureIgnoreCase) || linkClear.EndsWith("toc.md", StringComparison.InvariantCultureIgnoreCase) || linkClear.EndsWith("toc.yml", StringComparison.InvariantCultureIgnoreCase) || linkClear.StartsWith("mailto", StringComparison.InvariantCultureIgnoreCase)) { continue; } try { string href = BuildFullUrl("/" + SourceFilePath, linkClear); ProcessLink(href, link.Link, new ConceptualItemParameter(ref _content, link, href)); } catch (Exception ex) { Logger.LogWarning($"Message: \"{ex.Message}\", File: \"{fname}\", Href: \"{link.Link}\""); } } FoundPicture[] pictures = FindAllPictures(_content); Pictures.AddRange(pictures.Select(p => p.FullMatch)); foreach (FoundPicture picture in pictures) { try { string href = BuildFullUrl("/" + SourceFilePath, GetOnlyLink(picture.Link1)); ProcessLink(href, picture.Link1, null); if (!String.IsNullOrEmpty(picture.Link2) && !picture.Link1.Equals(picture.Link2)) { string link2 = CleanLinkOfQueryAndHash(GetOnlyLink(picture.Link2)); href = BuildFullUrl("/" + SourceFilePath, link2); ProcessLink(href, picture.Link2, null); } } catch (Exception ex) { Logger.LogWarning($"Message: \"{ex.Message}\", File: \"{fname}\", Link1: \"{picture.Link1}\", Link2: \"{picture.Link2}\""); } } FoundLink[] includes = FindIncludedLinks(_content); foreach (FoundLink link in includes.GroupBy(k => k).Select(k => k.Key)) { if (String.IsNullOrEmpty(link.Link.Trim())) { continue; } string linkClear = CleanLinkOfQueryAndHash(link.Link); if (linkClear.StartsWith("http", StringComparison.InvariantCultureIgnoreCase) || linkClear.EndsWith("toc.md", StringComparison.InvariantCultureIgnoreCase) || linkClear.EndsWith("toc.yml", StringComparison.InvariantCultureIgnoreCase)) { continue; } try { string href = BuildFullUrl("/" + SourceFilePath, linkClear); if (!FilesToIgnore.Contains(href.TrimStart('\\'))) { FilesToIgnore.Add(href.TrimStart('\\')); } } catch (Exception ex) { Logger.LogWarning($"Message: \"{ex.Message}\", File: \"{fname}\", Href: \"{link.Link}\""); } } return(HasModified); } if (!(fname != null && fname.Equals("toc.md", StringComparison.InvariantCultureIgnoreCase))) { FilesToRemove.Add(SourceFilePath.TrimStart('\\')); } return(false); }