Update Telegram SharpAESCrypt dep Update SystemIOWindows to split the difference # Conflicts: # BuildTools/AutoUpdateBuilder/AutoUpdateBuilder.csproj # BuildTools/AutoUpdateBuilder/packages.config # BuildTools/DependencyFinder/DependencyFinder.csproj # BuildTools/DependencyFinder/packages.config # BuildTools/GnupgSigningTool/GnupgSigningTool.csproj # BuildTools/UpdateVersionStamp/UpdateVersionStamp.csproj # BuildTools/UpdateVersionStamp/app.config # Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj # Duplicati/CommandLine/BackendTester/app.config # Duplicati/CommandLine/BackendTool/Duplicati.CommandLine.BackendTool.csproj # Duplicati/CommandLine/BackendTool/app.config # Duplicati/CommandLine/ConfigurationImporter/Duplicati.CommandLine.ConfigurationImporter.csproj # Duplicati/CommandLine/Duplicati.CommandLine.csproj # Duplicati/CommandLine/RecoveryTool/Duplicati.CommandLine.RecoveryTool.csproj # Duplicati/CommandLine/RecoveryTool/packages.config # Duplicati/CommandLine/app.config # Duplicati/GUI/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj # Duplicati/GUI/Duplicati.GUI.TrayIcon/app.config # Duplicati/GUI/Duplicati.GUI.TrayIcon/packages.config # Duplicati/Library/AutoUpdater/Duplicati.Library.AutoUpdater.csproj # Duplicati/Library/AutoUpdater/app.config # Duplicati/Library/AutoUpdater/packages.config # Duplicati/Library/Backend/AlternativeFTP/Duplicati.Library.Backend.AlternativeFTP.csproj # Duplicati/Library/Backend/AlternativeFTP/app.config # Duplicati/Library/Backend/AlternativeFTP/packages.config # Duplicati/Library/Backend/AzureBlob/Duplicati.Library.Backend.AzureBlob.csproj # Duplicati/Library/Backend/AzureBlob/packages.config # Duplicati/Library/Backend/Backblaze/Duplicati.Library.Backend.Backblaze.csproj # Duplicati/Library/Backend/Backblaze/packages.config # Duplicati/Library/Backend/Box/Duplicati.Library.Backend.Box.csproj # Duplicati/Library/Backend/Box/packages.config # Duplicati/Library/Backend/CloudFiles/Duplicati.Library.Backend.CloudFiles.csproj # Duplicati/Library/Backend/CloudFiles/app.config # Duplicati/Library/Backend/Dropbox/Duplicati.Library.Backend.Dropbox.csproj # Duplicati/Library/Backend/Dropbox/packages.config # Duplicati/Library/Backend/FTP/Duplicati.Library.Backend.FTP.csproj # Duplicati/Library/Backend/FTP/app.config # Duplicati/Library/Backend/File/Duplicati.Library.Backend.File.csproj # Duplicati/Library/Backend/File/app.config # Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj # Duplicati/Library/Backend/GoogleServices/packages.config # Duplicati/Library/Backend/HubiC/Duplicati.Library.Backend.HubiC.csproj # Duplicati/Library/Backend/Jottacloud/Duplicati.Library.Backend.Jottacloud.csproj # Duplicati/Library/Backend/Mega/Duplicati.Library.Backend.Mega.csproj # Duplicati/Library/Backend/Mega/packages.config # Duplicati/Library/Backend/OAuthHelper/Duplicati.Library.OAuthHelper.csproj # Duplicati/Library/Backend/OAuthHelper/packages.config # Duplicati/Library/Backend/OneDrive/Duplicati.Library.Backend.OneDrive.csproj # Duplicati/Library/Backend/OneDrive/packages.config # Duplicati/Library/Backend/OpenStack/Duplicati.Library.Backend.OpenStack.csproj # Duplicati/Library/Backend/OpenStack/packages.config # Duplicati/Library/Backend/Rclone/Duplicati.Library.Backend.Rclone.csproj # Duplicati/Library/Backend/Rclone/packages.config # Duplicati/Library/Backend/S3/Duplicati.Library.Backend.S3.csproj # Duplicati/Library/Backend/S3/app.config # Duplicati/Library/Backend/S3/packages.config # Duplicati/Library/Backend/SSHv2/Duplicati.Library.Backend.SSHv2.csproj # Duplicati/Library/Backend/SSHv2/packages.config # Duplicati/Library/Backend/SharePoint/Duplicati.Library.Backend.SharePoint.csproj # Duplicati/Library/Backend/SharePoint/app.config # Duplicati/Library/Backend/Sia/Duplicati.Library.Backend.Sia.csproj # Duplicati/Library/Backend/Sia/packages.config # Duplicati/Library/Backend/TahoeLAFS/Duplicati.Library.Backend.TahoeLAFS.csproj # Duplicati/Library/Backend/TahoeLAFS/packages.config # Duplicati/Library/Backend/Tardigrade/Duplicati.Library.Backend.Tardigrade.csproj # Duplicati/Library/Backend/Tardigrade/packages.config # Duplicati/Library/Backend/WEBDAV/Duplicati.Library.Backend.WEBDAV.csproj # Duplicati/Library/Backend/WEBDAV/app.config # Duplicati/Library/Common/Duplicati.Library.Common.csproj # Duplicati/Library/Common/IO/SystemIOWindows.cs # Duplicati/Library/Common/app.config # Duplicati/Library/Common/packages.config # Duplicati/Library/Compression/Duplicati.Library.Compression.csproj # Duplicati/Library/Compression/app.config # Duplicati/Library/Compression/packages.config # Duplicati/Library/DynamicLoader/Duplicati.Library.DynamicLoader.csproj # Duplicati/Library/DynamicLoader/app.config # Duplicati/Library/Encryption/Duplicati.Library.Encryption.csproj # Duplicati/Library/Encryption/packages.config # Duplicati/Library/Interface/Duplicati.Library.Interface.csproj # Duplicati/Library/Localization/Duplicati.Library.Localization.csproj # Duplicati/Library/Localization/packages.config # Duplicati/Library/Logging/Duplicati.Library.Logging.csproj # Duplicati/Library/Logging/app.config # Duplicati/Library/Main/Duplicati.Library.Main.csproj # Duplicati/Library/Main/app.config # Duplicati/Library/Main/packages.config # Duplicati/Library/Modules/Builtin/Duplicati.Library.Modules.Builtin.csproj # Duplicati/Library/Modules/Builtin/packages.config # Duplicati/Library/SQLiteHelper/Duplicati.Library.SQLiteHelper.csproj # Duplicati/Library/Snapshots/Duplicati.Library.Snapshots.csproj # Duplicati/Library/Snapshots/app.config # Duplicati/Library/Snapshots/packages.config # Duplicati/Library/UsageReporter/Duplicati.Library.UsageReporter.csproj # Duplicati/Library/UsageReporter/packages.config # Duplicati/Library/Utility/Duplicati.Library.Utility.csproj # Duplicati/Library/Utility/app.config # Duplicati/Library/Utility/packages.config # Duplicati/License/Duplicati.License.csproj # Duplicati/Server/Duplicati.Server.Serialization/Duplicati.Server.Serialization.csproj # Duplicati/Server/Duplicati.Server.Serialization/packages.config # Duplicati/Server/Duplicati.Server.csproj # Duplicati/Server/app.config # Duplicati/Server/packages.config # Duplicati/Service/Duplicati.Service.csproj # Duplicati/Service/app.config # Duplicati/Tools/Duplicati.Tools.csproj # Duplicati/UnitTest/Duplicati.UnitTest.csproj # Duplicati/UnitTest/RestoreHandlerTests.cs # Duplicati/UnitTest/app.config # Duplicati/UnitTest/packages.config # Duplicati/WindowsService/App.config # Duplicati/WindowsService/WindowsService.csproj # Tools/SQLiteTool/App.config # Tools/SQLiteTool/SQLiteTool.csproj # thirdparty/TlsTest/TlsTest.csproj # thirdparty/UnixSupport/UnixSupport.csproj # thirdparty/mozroots/mozroots.csproj
504 lines
21 KiB
C#
504 lines
21 KiB
C#
// Copyright (C) 2015, The Duplicati Team
|
|
// http://www.duplicati.com, info@duplicati.com
|
|
//
|
|
// This library is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as
|
|
// published by the Free Software Foundation; either version 2.1 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using Duplicati.Library.Common.IO;
|
|
using Duplicati.Library.Common;
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
namespace Duplicati.CommandLine.RecoveryTool
|
|
{
|
|
public static class Restore
|
|
{
|
|
private static readonly ISystemIO systemIO = SystemIO.IO_OS;
|
|
|
|
public static int Run(List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter)
|
|
{
|
|
if (args.Count != 2 && args.Count != 3)
|
|
{
|
|
Console.WriteLine("Invalid argument count ({0} expected 2 or 3): {1}{2}", args.Count, Environment.NewLine, string.Join(Environment.NewLine, args));
|
|
return 100;
|
|
}
|
|
|
|
var folder = Path.GetFullPath(args[1]);
|
|
|
|
if (!Directory.Exists(folder))
|
|
{
|
|
Console.WriteLine("Folder not found: {0}", folder);
|
|
return 100;
|
|
}
|
|
|
|
Directory.SetCurrentDirectory(folder);
|
|
|
|
string targetpath;
|
|
options.TryGetValue("targetpath", out targetpath);
|
|
|
|
string ixfile;
|
|
options.TryGetValue("indexfile", out ixfile);
|
|
if (string.IsNullOrWhiteSpace(ixfile))
|
|
ixfile = "index.txt";
|
|
|
|
ixfile = Path.GetFullPath(ixfile);
|
|
if (!File.Exists(ixfile))
|
|
{
|
|
Console.WriteLine("Index file not found, perhaps you need to run the index command?");
|
|
return 100;
|
|
}
|
|
|
|
|
|
string filelist;
|
|
if (args.Count == 2)
|
|
{
|
|
var time = List.ParseListFiles(folder).First();
|
|
filelist = time.Value;
|
|
|
|
Console.WriteLine("Using set 0 with timestamp {0}", time.Key.ToLocalTime());
|
|
}
|
|
else
|
|
{
|
|
filelist = List.SelectListFile(args[2], folder);
|
|
}
|
|
|
|
Library.Main.Volumes.VolumeReaderBase.UpdateOptionsFromManifest(Path.GetExtension(filelist).Trim('.'), filelist, new Duplicati.Library.Main.Options(options));
|
|
|
|
string blocksize_str;
|
|
options.TryGetValue("blocksize", out blocksize_str);
|
|
string blockhash_str;
|
|
options.TryGetValue("block-hash-algorithm", out blockhash_str);
|
|
string filehash_str;
|
|
options.TryGetValue("block-hash-algorithm", out filehash_str);
|
|
|
|
long blocksize = string.IsNullOrWhiteSpace(blocksize_str) ? 0 : Library.Utility.Sizeparser.ParseSize(blocksize_str);
|
|
|
|
if (blocksize <= 0)
|
|
{
|
|
Console.WriteLine("Invalid blocksize: {0}, try setting --blocksize manually", blocksize);
|
|
return 100;
|
|
}
|
|
|
|
var blockhasher = string.IsNullOrWhiteSpace(blockhash_str) ? null : HashAlgorithm.Create(blockhash_str);
|
|
var filehasher = string.IsNullOrWhiteSpace(filehash_str) ? null : HashAlgorithm.Create(filehash_str);
|
|
|
|
if (blockhasher == null)
|
|
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Block hash algorithm not valid: {0}", blockhash_str), "BlockHashAlgorithmNotSupported");
|
|
if (filehasher == null)
|
|
throw new Duplicati.Library.Interface.UserInformationException(string.Format("File hash algorithm not valid: {0}", filehash_str), "FileHashAlgorithmNotSupported");
|
|
|
|
var hashesprblock = blocksize / (blockhasher.HashSize / 8);
|
|
|
|
using (var mru = new CompressedFileMRUCache(options))
|
|
{
|
|
Console.WriteLine("Building lookup table for file hashes");
|
|
using (HashLookupHelper lookup = new HashLookupHelper(ixfile, mru, (int) blocksize, blockhasher.HashSize / 8))
|
|
{
|
|
var filecount = 0L;
|
|
string largestprefix = null;
|
|
string[] largestprefixparts = null;
|
|
|
|
if (!string.IsNullOrWhiteSpace(targetpath))
|
|
Console.WriteLine("Computing restore path");
|
|
|
|
foreach (var f in List.EnumerateFilesInDList(filelist, filter, options))
|
|
{
|
|
if (largestprefix == null)
|
|
{
|
|
largestprefix = f.Path;
|
|
largestprefixparts = largestprefix.Split(new char[] { Path.DirectorySeparatorChar });
|
|
}
|
|
else if (largestprefix.Length > 1)
|
|
{
|
|
var parts = f.Path.Split(new char[] { Path.DirectorySeparatorChar });
|
|
|
|
var ni = 0;
|
|
for (; ni < Math.Min(parts.Length, largestprefixparts.Length); ni++)
|
|
if (!Library.Utility.Utility.ClientFilenameStringComparer.Equals(parts[ni], largestprefixparts[ni]))
|
|
break;
|
|
|
|
if (ni != largestprefixparts.Length)
|
|
{
|
|
if (ni == 0)
|
|
{
|
|
largestprefixparts = new string[0];
|
|
largestprefix = string.Empty;
|
|
}
|
|
else
|
|
{
|
|
Array.Resize(ref largestprefixparts, ni - 1);
|
|
largestprefix = string.Join(Util.DirectorySeparatorString, largestprefixparts);
|
|
}
|
|
}
|
|
}
|
|
filecount++;
|
|
}
|
|
|
|
Console.WriteLine("Restoring {0} files to {1}", filecount, string.IsNullOrWhiteSpace(targetpath) ? "original position" : targetpath);
|
|
|
|
if (Platform.IsClientPosix || largestprefix.Length > 0)
|
|
largestprefix = Util.AppendDirSeparator(largestprefix);
|
|
|
|
if (!string.IsNullOrEmpty(largestprefix))
|
|
Console.WriteLine("Removing common prefix {0} from files", largestprefix);
|
|
|
|
var i = 0L;
|
|
foreach (var f in List.EnumerateFilesInDList(filelist, filter, options))
|
|
{
|
|
try
|
|
{
|
|
var targetfile = MapToRestorePath(f.Path, largestprefix, targetpath);
|
|
if (!systemIO.DirectoryExists(systemIO.PathGetDirectoryName(targetfile)))
|
|
systemIO.DirectoryCreate(systemIO.PathGetDirectoryName(targetfile));
|
|
|
|
Console.Write("{0}: {1} ({2})", i, targetfile, Library.Utility.Utility.FormatSizeString(f.Size));
|
|
|
|
using (var tf = new Library.Utility.TempFile())
|
|
{
|
|
using (var sw = File.OpenWrite(tf))
|
|
{
|
|
if (f.BlocklistHashes == null)
|
|
{
|
|
lookup.WriteHash(sw, f.Hash);
|
|
}
|
|
else
|
|
{
|
|
var blhi = 0L;
|
|
foreach (var blh in f.BlocklistHashes)
|
|
{
|
|
Console.Write(" {0}", blhi);
|
|
var blockhashoffset = blhi * hashesprblock * blocksize;
|
|
|
|
try
|
|
{
|
|
var bi = 0;
|
|
foreach (var h in lookup.ReadBlocklistHashes(blh))
|
|
{
|
|
try
|
|
{
|
|
sw.Position = blockhashoffset + (bi * blocksize);
|
|
lookup.WriteHash(sw, h);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("Failed to read hash: {0}{1}{2}", h, Environment.NewLine, ex);
|
|
}
|
|
|
|
bi++;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("Failed to read Blocklist hash: {0}{1}{2}", blh, Environment.NewLine, ex);
|
|
}
|
|
|
|
blhi++;
|
|
}
|
|
}
|
|
}
|
|
|
|
string fh;
|
|
using (var fs = File.OpenRead(tf))
|
|
fh = Convert.ToBase64String(filehasher.ComputeHash(fs));
|
|
|
|
if (fh == f.Hash)
|
|
{
|
|
Console.WriteLine(" done!");
|
|
systemIO.FileCopy(tf, targetfile, true);
|
|
}
|
|
else
|
|
{
|
|
Console.Write(" - Restored file hash mismatch");
|
|
if (systemIO.FileExists(targetfile))
|
|
Console.WriteLine(" - not overwriting existing file: {0}", targetfile);
|
|
else
|
|
Console.WriteLine(" - restoring file in damaged condition");
|
|
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(" error: {0}", ex);
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private static string MapToRestorePath(string path, string prefixpath, string restorepath)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(restorepath))
|
|
return path;
|
|
|
|
if (string.IsNullOrWhiteSpace(prefixpath))
|
|
{
|
|
//Special case, restoring to new folder, but files are from different drives
|
|
// So we use the format <restore path> / <drive letter> / <source path>
|
|
// To avoid generating paths with a colon
|
|
if (path.Substring(1, 1) == ":")
|
|
prefixpath = path.Substring(0, 1) + path.Substring(2);
|
|
|
|
return systemIO.PathCombine(restorepath, prefixpath);
|
|
}
|
|
|
|
return systemIO.PathCombine(restorepath, path.Substring(prefixpath.Length));
|
|
}
|
|
|
|
|
|
|
|
private class HashLookupHelper : IDisposable
|
|
{
|
|
private const int LOOKUP_TABLE_SIZE = 2048;
|
|
|
|
private readonly CompressedFileMRUCache m_cache;
|
|
private readonly int m_hashsize;
|
|
private readonly int m_blocksize;
|
|
private Stream m_indexfile;
|
|
private readonly List<string> m_lookup = new List<string>();
|
|
private readonly List<long> m_offsets = new List<long>();
|
|
private readonly byte[] m_linebuf = new byte[128];
|
|
private readonly byte[] m_newline = Environment.NewLine.Select(x => (byte)x).ToArray();
|
|
|
|
public HashLookupHelper(string indexfile, CompressedFileMRUCache cache, int blocksize, int hashsize)
|
|
{
|
|
m_cache = cache;
|
|
m_blocksize = blocksize;
|
|
m_hashsize = hashsize;
|
|
m_indexfile = File.OpenRead(indexfile);
|
|
|
|
// Build index ....
|
|
var hashes = 0L;
|
|
string prev = null;
|
|
m_indexfile.Position = 0;
|
|
foreach (var n in AllFileLines())
|
|
{
|
|
if (n.Key != prev)
|
|
{
|
|
hashes++;
|
|
prev = n.Key;
|
|
}
|
|
}
|
|
|
|
Console.WriteLine("Index file has {0} hashes in total", hashes);
|
|
|
|
var lookuptablesize = Math.Max(1, Math.Min(LOOKUP_TABLE_SIZE, hashes) - 1);
|
|
var lookupincrements = hashes / lookuptablesize;
|
|
|
|
Console.WriteLine("Building lookup table with {0} entries, giving increments of {1}", lookuptablesize, lookupincrements);
|
|
|
|
prev = null;
|
|
var prevoff = 0L;
|
|
var hc = 0L;
|
|
m_indexfile.Position = 0;
|
|
foreach (var n in AllFileLines())
|
|
{
|
|
if (n.Key != prev)
|
|
{
|
|
if ((hc % lookupincrements) == 0)
|
|
{
|
|
m_lookup.Add(n.Key);
|
|
m_offsets.Add(prevoff);
|
|
}
|
|
|
|
hc++;
|
|
prev = n.Key;
|
|
prevoff = m_indexfile.Position;
|
|
}
|
|
}
|
|
}
|
|
|
|
private string ReadNextLine()
|
|
{
|
|
var p = m_indexfile.Position;
|
|
var max = m_indexfile.Read(m_linebuf, 0, m_linebuf.Length);
|
|
if (max == 0)
|
|
return null;
|
|
|
|
var lfi = 0;
|
|
for (int i = 0; i < max; i++)
|
|
if (m_linebuf[i] == m_newline[lfi])
|
|
{
|
|
if (lfi == m_newline.Length - 1)
|
|
{
|
|
m_indexfile.Position = p + i + 1;
|
|
return System.Text.Encoding.Default.GetString(m_linebuf, 0, i - m_newline.Length + 1);
|
|
|
|
}
|
|
else
|
|
{
|
|
lfi++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lfi = 0;
|
|
}
|
|
|
|
throw new Exception(string.Format("Unexpected long line starting at offset {0}, read {1} bytes without a newline", p, m_linebuf.Length));
|
|
}
|
|
|
|
private IEnumerable<KeyValuePair<string, string>> AllFileLines()
|
|
{
|
|
while (true)
|
|
{
|
|
var str = ReadNextLine();
|
|
if (str == null)
|
|
break;
|
|
|
|
if (str.Length == 0)
|
|
continue;
|
|
|
|
var ix = str.IndexOf(", ", StringComparison.Ordinal);
|
|
if (ix < 0)
|
|
Console.WriteLine("Failed to parse line starting at offset {0} in index file, string: {1}", m_indexfile.Position - str.Length - m_newline.Length, str);
|
|
yield return new KeyValuePair<string, string>(str.Substring(0, ix), str.Substring(ix + 2));
|
|
}
|
|
}
|
|
|
|
public IEnumerable<string> ReadBlocklistHashes(string hash)
|
|
{
|
|
var bytes = ReadHash(hash);
|
|
for (var i = 0; i < bytes.Length; i += m_hashsize)
|
|
yield return Convert.ToBase64String(bytes, i, m_hashsize);
|
|
}
|
|
|
|
public byte[] ReadHash(string hash)
|
|
{
|
|
var ix = m_lookup.BinarySearch(hash, StringComparer.Ordinal);
|
|
if (ix < 0)
|
|
{
|
|
ix = ~ix;
|
|
if (ix != 0)
|
|
ix--;
|
|
}
|
|
|
|
m_indexfile.Position = m_offsets[ix];
|
|
|
|
foreach (var v in AllFileLines())
|
|
{
|
|
if (v.Key == hash)
|
|
using (var fs = m_cache.ReadBlock(v.Value, hash))
|
|
{
|
|
var buf = new byte[m_blocksize];
|
|
var l = Duplicati.Library.Utility.Utility.ForceStreamRead(fs, buf, buf.Length);
|
|
Array.Resize(ref buf, l);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
if (StringComparer.Ordinal.Compare(hash, v.Key) < 0)
|
|
break;
|
|
}
|
|
|
|
throw new Exception(string.Format("Unable to locate block with hash: {0}", hash));
|
|
}
|
|
|
|
public void WriteHash(Stream sw, string hash)
|
|
{
|
|
var h = ReadHash(hash);
|
|
sw.Write(h, 0, h.Length);
|
|
}
|
|
|
|
|
|
public void Dispose()
|
|
{
|
|
if (m_indexfile != null)
|
|
m_indexfile.Dispose();
|
|
m_indexfile = null;
|
|
}
|
|
}
|
|
|
|
private class CompressedFileMRUCache : IDisposable
|
|
{
|
|
private Dictionary<string, Library.Interface.ICompression> m_lookup = new Dictionary<string, Duplicati.Library.Interface.ICompression>();
|
|
private Dictionary<string, Stream> m_streams = new Dictionary<string, Stream>();
|
|
private List<string> m_mru = new List<string>();
|
|
private readonly Dictionary<string, string> m_options;
|
|
|
|
private const int MAX_OPEN_ARCHIVES = 20;
|
|
|
|
public CompressedFileMRUCache(Dictionary<string, string> options)
|
|
{
|
|
m_options = options;
|
|
}
|
|
|
|
public Stream ReadBlock(string filename, string hash)
|
|
{
|
|
Library.Interface.ICompression cf;
|
|
Stream stream;
|
|
if (!m_lookup.TryGetValue(filename, out cf) || !m_streams.TryGetValue(filename, out stream))
|
|
{
|
|
stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete);
|
|
cf = Library.DynamicLoader.CompressionLoader.GetModule(Path.GetExtension(filename).Trim('.'), stream, Library.Interface.ArchiveMode.Read, m_options);
|
|
if (cf == null)
|
|
{
|
|
stream.Dispose();
|
|
throw new Exception(string.Format("Unable to decompress {0}, no such compression module {1}", filename, Path.GetExtension(filename).Trim('.')));
|
|
}
|
|
m_lookup[filename] = cf;
|
|
m_streams[filename] = stream;
|
|
}
|
|
|
|
if (m_mru.Count > 0 && m_mru[0] != filename)
|
|
{
|
|
m_mru.Remove(filename);
|
|
m_mru.Insert(0, filename);
|
|
}
|
|
|
|
while (m_mru.Count > MAX_OPEN_ARCHIVES)
|
|
{
|
|
var f = m_mru.Last();
|
|
m_lookup[f].Dispose();
|
|
m_lookup.Remove(f);
|
|
m_streams[f].Dispose();
|
|
m_streams.Remove(f);
|
|
m_mru.Remove(f);
|
|
}
|
|
|
|
return cf.OpenRead(Library.Utility.Utility.Base64PlainToBase64Url(hash));
|
|
}
|
|
|
|
#region IDisposable implementation
|
|
|
|
public void Dispose()
|
|
{
|
|
foreach (var v in m_lookup.Values)
|
|
try { v.Dispose(); }
|
|
catch { }
|
|
|
|
foreach (var v in m_streams.Values)
|
|
try { v.Dispose(); }
|
|
catch { }
|
|
|
|
m_lookup = null;
|
|
m_streams = null;
|
|
m_mru = null;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
}
|
|
|