2024-02-28 15:45:30 +01:00
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System ;
2013-05-06 09:58:19 +02:00
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using Newtonsoft.Json ;
using System.IO ;
using Duplicati.Library.Interface ;
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main.Volumes
2013-05-06 09:58:19 +02:00
{
public class FilesetVolumeReader : VolumeReaderBase
{
private class FileEntryEnumerable : IEnumerable < IFileEntry >
{
private class FileEntryEnumerator : IEnumerator < IFileEntry >
{
private class FileEntry : IFileEntry
{
private class BlocklistHashEnumerable : IEnumerable < string >
{
private class BlocklistHashEnumerator : IEnumerator < string >
{
2018-05-23 21:18:01 -07:00
private readonly JsonReader m_reader ;
2013-05-06 09:58:19 +02:00
private string m_current ;
private bool m_firstReset = true ;
private bool m_done = false ;
public BlocklistHashEnumerator ( JsonReader reader )
{
m_reader = reader ;
}
public string Current
{
get { return m_current ; }
}
public void Dispose ()
{
}
object System . Collections . IEnumerator . Current
{
get { return this . Current ; }
}
public bool MoveNext ()
{
m_firstReset = false ;
if ( m_reader == null || m_done )
return false ;
if (! m_reader . Read ())
throw new InvalidDataException ( "Got EOF while reading blocklist hashes" );
if ( m_reader . TokenType == JsonToken . EndArray )
{
m_done = true ;
return false ;
}
if ( m_reader . TokenType != JsonToken . String )
throw new InvalidDataException ( string . Format ( "Invalid JSON, expected String, but found {0}, {1}" , m_reader . TokenType , m_reader . Value ));
m_current = m_reader . Value == null ? null : m_reader . Value . ToString ();
return true ;
}
public void Reset ()
{
if (! m_firstReset )
throw new NotImplementedException ();
}
}
2018-05-23 21:18:01 -07:00
private readonly JsonReader m_reader ;
2013-05-06 09:58:19 +02:00
public BlocklistHashEnumerable ( JsonReader reader )
{
m_reader = reader ;
}
public IEnumerator < string > GetEnumerator ()
{
return new BlocklistHashEnumerator ( m_reader );
}
System . Collections . IEnumerator System . Collections . IEnumerable . GetEnumerator ()
{
return this . GetEnumerator ();
}
}
public FilelistEntryType Type { get ; private set ; }
public string TypeString { get ; private set ; }
public string Path { get ; private set ; }
public string Hash { get ; private set ; }
public long Size { get ; private set ; }
public DateTime Time { get ; private set ; }
public string Metahash { get ; private set ; }
2016-04-03 23:10:47 +02:00
public string Metablockhash { get ; private set ; }
2013-05-06 09:58:19 +02:00
public long Metasize { get ; private set ; }
2016-04-03 23:10:47 +02:00
public string Blockhash { get ; private set ; }
public long Blocksize { get ; private set ; }
2013-05-06 09:58:19 +02:00
public IEnumerable < string > BlocklistHashes { get ; private set ; }
2016-04-04 18:11:48 +02:00
public IEnumerable < string > MetaBlocklistHashes { get ; private set ; }
2018-05-23 21:18:01 -07:00
private readonly JsonReader m_reader ;
2013-05-06 09:58:19 +02:00
public FileEntry ( JsonReader reader )
{
m_reader = reader ;
this . TypeString = ReadJsonStringProperty ( m_reader , "type" );
FilelistEntryType et ;
if (! Enum . TryParse < FilelistEntryType >( this . TypeString , true , out et ))
et = FilelistEntryType . Unknown ;
this . Type = et ;
if ( this . Type == FilelistEntryType . Unknown )
{
//Skip this entry by finding the EndObject that matches the StartObject
long balance = 1 ;
while ( balance > 0 && m_reader . Read ())
{
if ( m_reader . TokenType == JsonToken . StartObject )
balance ++;
else if ( m_reader . TokenType == JsonToken . EndObject )
balance --;
}
if ( balance != 0 )
throw new InvalidDataException ( string . Format ( "Invalid JSON, EOF found while reading entry of type {0}" , this . TypeString ));
}
else
{
this . Path = ReadJsonStringProperty ( m_reader , "path" );
if ( this . Type == FilelistEntryType . File || this . Type == FilelistEntryType . AlternateStream )
{
this . Hash = ReadJsonStringProperty ( m_reader , "hash" );
this . Size = ReadJsonInt64Property ( m_reader , "size" );
this . Time = ReadJsonDateTimeProperty ( m_reader , "time" );
}
if (! m_reader . Read ())
throw new InvalidDataException ( string . Format ( "Invalid JSON, EOF found while reading entry {0}" , this . Path ));
if ( m_reader . TokenType == JsonToken . PropertyName && m_reader . Value != null && m_reader . Value . ToString () == "metahash" )
{
if (! m_reader . Read ())
throw new InvalidDataException ( string . Format ( "Invalid JSON, EOF found while reading entry {0}" , this . Path ));
this . Metahash = m_reader . Value . ToString ();
this . Metasize = ReadJsonInt64Property ( m_reader , "metasize" );
if (! m_reader . Read ())
throw new InvalidDataException ( string . Format ( "Invalid JSON, EOF found while reading entry {0}" , this . Path ));
2016-04-03 23:10:47 +02:00
2016-04-04 18:11:48 +02:00
if ( m_reader . TokenType == JsonToken . PropertyName && m_reader . Value != null && m_reader . Value . ToString () == "metablocklists" )
{
var metadatablocklisthashes = new List < string >();
SkipJsonToken ( m_reader , JsonToken . StartArray );
if (! m_reader . Read ())
throw new InvalidDataException ( string . Format ( "Invalid JSON, EOF found while reading entry {0}" , this . Path ));
while ( m_reader . TokenType == JsonToken . String )
{
metadatablocklisthashes . Add ( m_reader . Value . ToString ());
if (! m_reader . Read ())
throw new InvalidDataException ( string . Format ( "Invalid JSON, EOF found while reading entry {0}" , this . Path ));
}
if ( m_reader . TokenType != JsonToken . EndArray )
throw new InvalidDataException ( string . Format ( "Invalid JSON, unexpected token {1} found while reading entry {0}" , this . Path , m_reader . TokenType ));
if (! m_reader . Read ())
throw new InvalidDataException ( string . Format ( "Invalid JSON, EOF found while reading entry {0}" , this . Path ));
this . MetaBlocklistHashes = metadatablocklisthashes ;
this . Metablockhash = null ;
}
else if ( m_reader . TokenType == JsonToken . PropertyName && m_reader . Value != null && m_reader . Value . ToString () == "metablockhash" )
2016-04-03 23:10:47 +02:00
{
if (! m_reader . Read ())
throw new InvalidDataException ( string . Format ( "Invalid JSON, EOF found while reading entry {0}" , this . Path ));
this . Metablockhash = m_reader . Value . ToString ();
if (! m_reader . Read ())
throw new InvalidDataException ( string . Format ( "Invalid JSON, EOF found while reading entry {0}" , this . Path ));
2016-04-04 18:11:48 +02:00
this . MetaBlocklistHashes = null ;
2016-04-03 23:10:47 +02:00
}
2013-05-06 09:58:19 +02:00
}
if (( this . Type == FilelistEntryType . File || this . Type == FilelistEntryType . AlternateStream ) && m_reader . TokenType == JsonToken . PropertyName && m_reader . Value != null && m_reader . Value . ToString () == "blocklists" )
{
SkipJsonToken ( m_reader , JsonToken . StartArray );
2016-04-03 23:10:47 +02:00
this . BlocklistHashes = new BlocklistHashEnumerable ( m_reader );
}
else if (( this . Type == FilelistEntryType . File || this . Type == FilelistEntryType . AlternateStream ) && m_reader . TokenType == JsonToken . PropertyName && m_reader . Value != null && m_reader . Value . ToString () == "blockhash" )
{
if (! m_reader . Read ())
throw new InvalidDataException ( string . Format ( "Invalid JSON, EOF found while reading entry {0}" , this . Path ));
this . Blockhash = m_reader . Value . ToString ();
this . Blocksize = ReadJsonInt64Property ( m_reader , "blocksize" );
this . BlocklistHashes = null ;
2013-05-06 09:58:19 +02:00
}
else
{
2016-04-03 23:10:47 +02:00
this . BlocklistHashes = null ;
2013-05-06 09:58:19 +02:00
if ( m_reader . TokenType != JsonToken . EndObject )
throw new InvalidDataException ( string . Format ( "Invalid JSON, expected EndObject, but found {0}, \"{1}\" while reading entry {2}" , m_reader . TokenType , m_reader . Value , this . Path ));
}
}
}
}
2018-05-23 21:18:01 -07:00
private readonly ICompression m_compression ;
2013-05-06 09:58:19 +02:00
private FileEntry m_current ;
private StreamReader m_stream ;
private JsonReader m_reader ;
private bool m_done ;
private bool m_first ;
public FileEntryEnumerator ( ICompression compression )
{
m_compression = compression ;
this . Reset ();
}
public IFileEntry Current
{
get { return m_current ; }
}
public void Dispose ()
{
if ( m_reader != null )
try { m_reader . Close (); }
finally { m_reader = null ; }
if ( m_stream != null )
try { m_stream . Dispose (); }
finally { m_stream = null ; }
}
object System . Collections . IEnumerator . Current
{
get { return this . Current ; }
}
public bool MoveNext ()
{
if ( m_done )
return false ;
if ( m_first )
{
m_first = false ;
}
else
{
while ( m_reader . TokenType != JsonToken . EndObject && m_reader . Read ())
{ /*skip */ }
}
if (! m_reader . Read ())
2018-05-24 20:16:46 -07:00
throw new InvalidDataException ( "Invalid JSON, EOF found while reading hashes" );
2013-05-06 09:58:19 +02:00
if ( m_reader . TokenType == JsonToken . EndArray )
{
m_done = true ;
m_current = null ;
return false ;
}
if ( m_reader . TokenType != JsonToken . StartObject )
throw new InvalidDataException ( string . Format ( "Invalid JSON, expected StartObject, but got {0}, {1}" , m_reader . TokenType , m_reader . Value ));
m_current = new FileEntry ( m_reader );
return true ;
}
public void Reset ()
{
this . Dispose ();
m_stream = new StreamReader ( m_compression . OpenRead ( FILELIST ));
m_reader = new JsonTextReader ( m_stream );
SkipJsonToken ( m_reader , JsonToken . StartArray );
m_current = null ;
m_done = false ;
m_first = true ;
}
}
2018-05-23 21:18:01 -07:00
private readonly ICompression m_compression ;
2013-05-06 09:58:19 +02:00
public FileEntryEnumerable ( ICompression compression )
{
m_compression = compression ;
}
public IEnumerator < IFileEntry > GetEnumerator ()
{
return new FileEntryEnumerator ( m_compression );
}
System . Collections . IEnumerator System . Collections . IEnumerable . GetEnumerator ()
{
return this . GetEnumerator ();
}
}
private class ControlFileEnumerable : IEnumerable < KeyValuePair < string , Stream >>
{
private class ControlFileEnumerator : IEnumerator < KeyValuePair < string , Stream >>
{
2018-05-23 21:18:01 -07:00
private readonly ICompression m_compression ;
2013-05-06 09:58:19 +02:00
private string [] m_files ;
private long m_index ;
private KeyValuePair < string , Stream >? m_current ;
public ControlFileEnumerator ( ICompression compression )
{
m_compression = compression ;
this . Reset ();
}
public KeyValuePair < string , Stream > Current { get { return m_current . Value ; } }
object System . Collections . IEnumerator . Current { get { return this . Current ; } }
public void Dispose ()
{
if ( m_current != null )
try { m_current . Value . Value . Dispose (); }
finally { m_current = null ; }
}
public bool MoveNext ()
{
if ( m_index + 1 >= m_files . Length )
return false ;
m_index ++;
if ( m_current != null )
try { m_current . Value . Value . Dispose (); }
finally { m_current = null ; }
m_current = new KeyValuePair < string , Stream >( m_files [ m_index ]. Substring ( CONTROL_FILES_FOLDER . Length ), m_compression . OpenRead ( m_files [ m_index ]));
return true ;
}
public void Reset ()
{
m_files = m_compression . ListFiles ( CONTROL_FILES_FOLDER );
m_index = - 1 ;
}
}
2018-05-23 21:18:01 -07:00
private readonly ICompression m_compression ;
2013-05-06 09:58:19 +02:00
public ControlFileEnumerable ( ICompression compression )
{
m_compression = compression ;
}
public IEnumerator < KeyValuePair < string , Stream >> GetEnumerator () { return new ControlFileEnumerator ( m_compression ); }
System . Collections . IEnumerator System . Collections . IEnumerable . GetEnumerator () { return this . GetEnumerator (); }
}
public FilesetVolumeReader ( ICompression compression , Options options )
: base ( compression , options )
{
}
public FilesetVolumeReader ( string compressor , string file , Options options )
: base ( compressor , file , options )
{
}
public IEnumerable < IFileEntry > Files { get { return new FileEntryEnumerable ( m_compression ); } }
public IEnumerable < KeyValuePair < string , Stream >> ControlFiles { get { return new ControlFileEnumerable ( m_compression ); } }
}
}