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-03-31 19:33:34 +02:00
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
2017-01-09 11:35:38 +01:00
using Duplicati.Library.Interface ;
2019-08-31 14:44:12 -04:00
using Duplicati.Library.Main.Database ;
2019-11-20 20:14:15 -08:00
using Duplicati.Library.Main.Volumes ;
2019-09-29 20:16:28 -07:00
using Duplicati.Library.Utility ;
2013-03-31 19:33:34 +02:00
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main.Operation
2019-08-05 20:14:05 -04:00
{
2013-05-25 16:40:15 +02:00
internal class ListFilesHandler
2019-08-05 20:14:05 -04:00
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType < ListFilesHandler >();
2018-05-23 21:18:01 -07:00
private readonly string m_backendurl ;
private readonly Options m_options ;
private readonly ListResults m_result ;
2013-03-31 19:33:34 +02:00
2013-05-25 16:40:15 +02:00
public ListFilesHandler ( string backend , Options options , ListResults result )
2013-03-31 19:33:34 +02:00
{
m_backendurl = backend ;
m_options = options ;
2013-05-25 16:40:15 +02:00
m_result = result ;
2013-03-31 19:33:34 +02:00
}
2013-05-25 16:40:15 +02:00
public void Run ( IEnumerable < string > filterstrings = null , Library . Utility . IFilter compositefilter = null )
{
var parsedfilter = new Library . Utility . FilterExpression ( filterstrings );
2013-06-29 11:41:53 +02:00
var filter = Library . Utility . JoinedFilterExpression . Join ( parsedfilter , compositefilter );
2019-09-29 20:16:28 -07:00
var simpleList = !(( filter is FilterExpression expression && expression . Type == Library . Utility . FilterType . Simple ) || m_options . AllVersions );
2019-08-05 20:14:05 -04:00
2013-03-31 19:33:34 +02:00
//Use a speedy local query
2013-05-13 22:32:05 +02:00
if (! m_options . NoLocalDb && System . IO . File . Exists ( m_options . Dbpath ))
2019-08-05 20:14:05 -04:00
using ( var db = new Database . LocalListDatabase ( m_options . Dbpath ))
2013-05-25 16:40:15 +02:00
{
m_result . SetDatabase ( db );
2019-08-05 20:14:05 -04:00
using ( var filesets = db . SelectFileSets ( m_options . Time , m_options . Version ))
2013-05-11 12:03:15 +02:00
{
2017-04-06 11:54:22 +02:00
if (! filter . Empty )
2014-03-21 15:01:49 +01:00
{
if ( simpleList || ( m_options . ListFolderContents && ! m_options . AllVersions ))
2019-08-05 20:14:05 -04:00
{
2014-03-21 15:01:49 +01:00
filesets . TakeFirst ();
2019-08-05 20:14:05 -04:00
}
2014-03-21 15:01:49 +01:00
}
IEnumerable < Database . LocalListDatabase . IFileversion > files ;
2014-03-21 19:23:54 +01:00
if ( m_options . ListFolderContents )
2019-08-05 20:14:05 -04:00
{
2014-03-21 15:01:49 +01:00
files = filesets . SelectFolderContents ( filter );
2019-08-05 20:14:05 -04:00
}
2014-03-21 15:01:49 +01:00
else if ( m_options . ListPrefixOnly )
2019-08-05 20:14:05 -04:00
{
2014-03-21 15:01:49 +01:00
files = filesets . GetLargestPrefix ( filter );
2019-08-05 20:14:05 -04:00
}
2017-04-06 11:54:22 +02:00
else if ( filter . Empty )
2019-08-05 20:14:05 -04:00
{
2014-03-21 19:23:54 +01:00
files = null ;
2019-08-05 20:14:05 -04:00
}
2014-03-21 15:01:49 +01:00
else
2019-08-05 20:14:05 -04:00
{
2014-03-21 15:01:49 +01:00
files = filesets . SelectFiles ( filter );
2019-08-05 20:14:05 -04:00
}
2014-08-19 20:24:54 +02:00
if ( m_options . ListSetsOnly )
2019-08-05 20:14:05 -04:00
{
2014-08-19 20:24:54 +02:00
m_result . SetResult (
2019-08-05 20:14:05 -04:00
filesets . QuickSets . Select ( x => new ListResultFileset ( x . Version , x . IsFullBackup , x . Time , x . FileCount , x . FileSizes )). ToArray (),
2014-08-19 20:24:54 +02:00
null
);
2019-08-05 20:14:05 -04:00
}
2014-08-19 20:24:54 +02:00
else
2019-08-05 20:14:05 -04:00
{
2014-08-19 20:24:54 +02:00
m_result . SetResult (
2019-08-05 20:14:05 -04:00
filesets . Sets . Select ( x =>
new ListResultFileset ( x . Version , x . IsFullBackup , x . Time , x . FileCount , x . FileSizes )). ToArray (),
files == null
? null
: ( from n in files
select ( Duplicati . Library . Interface . IListResultFile )( new ListResultFile ( n . Path ,
n . Sizes . ToArray ())))
. ToArray ()
2014-08-19 20:24:54 +02:00
);
2019-08-05 20:14:05 -04:00
}
2013-05-25 16:40:15 +02:00
return ;
2013-05-11 12:03:15 +02:00
}
2013-05-25 16:40:15 +02:00
}
2019-08-05 20:14:05 -04:00
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "NoLocalDatabase" , "No local database, accessing remote store" );
2016-04-19 10:04:22 +02:00
2014-03-21 15:01:49 +01:00
//TODO: Add prefix and foldercontents
2016-04-19 10:04:22 +02:00
if ( m_options . ListFolderContents )
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( "Listing folder contents is not supported without a local database, consider using the \"repair\" option to rebuild the database." , "FolderContentListingRequiresLocalDatabase" );
2016-04-19 10:04:22 +02:00
else if ( m_options . ListPrefixOnly )
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( "Listing prefixes is not supported without a local database, consider using the \"repair\" option to rebuild the database." , "PrefixListingRequiresLocalDatabase" );
2013-04-01 13:05:55 +02:00
2013-03-31 19:33:34 +02:00
// Otherwise, grab info from remote location
2013-05-08 21:29:59 +02:00
using ( var tmpdb = new Library . Utility . TempFile ())
2016-04-06 20:40:34 +02:00
using ( var db = new Database . LocalDatabase ( tmpdb , "List" , true ))
2013-05-25 16:40:15 +02:00
using ( var backend = new BackendManager ( m_backendurl , m_options , m_result . BackendWriter , db ))
2013-03-31 19:33:34 +02:00
{
2013-05-25 16:40:15 +02:00
m_result . SetDatabase ( db );
2019-08-05 20:14:05 -04:00
2013-05-30 21:54:43 +02:00
var filteredList = ParseAndFilterFilesets ( backend . List (), m_options );
2013-05-11 12:03:15 +02:00
if ( filteredList . Count == 0 )
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( "No filesets found on remote target" , "EmptyRemoteFolder" );
2013-05-30 21:54:43 +02:00
2019-11-20 20:14:15 -08:00
var numberSeq = CreateResultSequence ( filteredList , backend , m_options );
2017-04-06 11:54:22 +02:00
if ( filter . Empty )
2013-05-25 16:40:15 +02:00
{
m_result . SetResult ( numberSeq , null );
2016-02-10 17:18:48 +01:00
m_result . EncryptedFiles = filteredList . Any ( x => ! string . IsNullOrWhiteSpace ( x . Value . EncryptionModule ));
2013-05-25 16:40:15 +02:00
return ;
}
2019-08-05 20:14:05 -04:00
2013-05-11 12:03:15 +02:00
var firstEntry = filteredList [ 0 ]. Value ;
filteredList . RemoveAt ( 0 );
2019-08-05 20:14:05 -04:00
Dictionary < string , List < long >> res ;
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2019-08-05 20:14:05 -04:00
2013-05-11 12:03:15 +02:00
using ( var tmpfile = backend . Get ( firstEntry . File . Name , firstEntry . File . Size , null ))
using ( var rd = new Volumes . FilesetVolumeReader ( RestoreHandler . GetCompressionModule ( firstEntry . File . Name ), tmpfile , m_options ))
if ( simpleList )
{
2013-05-25 16:40:15 +02:00
m_result . SetResult (
numberSeq . Take ( 1 ),
2013-05-11 12:03:15 +02:00
( from n in rd . Files
2019-08-05 20:14:05 -04:00
where Library . Utility . FilterExpression . Matches ( filter , n . Path )
orderby n . Path
select new ListResultFile ( n . Path , new long [] { n . Size }))
2013-05-11 12:03:15 +02:00
. ToArray ()
2013-05-25 16:40:15 +02:00
);
2019-08-05 20:14:05 -04:00
2013-05-25 16:40:15 +02:00
return ;
2013-05-11 12:03:15 +02:00
}
else
{
res = rd . Files
2013-06-30 12:42:13 +02:00
. Where ( x => Library . Utility . FilterExpression . Matches ( filter , x . Path ))
2013-05-11 12:03:15 +02:00
. ToDictionary (
2019-08-05 20:14:05 -04:00
x => x . Path ,
y =>
{
2013-05-11 12:03:15 +02:00
var lst = new List < long >();
lst . Add ( y . Size );
return lst ;
},
Library . Utility . Utility . ClientFilenameStringComparer
);
}
2019-08-05 20:14:05 -04:00
2013-05-11 12:03:15 +02:00
long flindex = 1 ;
2019-08-05 20:14:05 -04:00
foreach ( var flentry in filteredList )
using ( var tmpfile = backend . Get ( flentry . Value . File . Name , flentry . Value . File == null ? - 1 : flentry . Value . File . Size , null ))
2013-05-11 12:03:15 +02:00
using ( var rd = new Volumes . FilesetVolumeReader ( flentry . Value . CompressionModule , tmpfile , m_options ))
{
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2019-08-05 20:14:05 -04:00
foreach ( var p in from n in rd . Files where Library . Utility . FilterExpression . Matches ( filter , n . Path ) select n )
2013-05-11 12:03:15 +02:00
{
List < long > lst ;
if (! res . TryGetValue ( p . Path , out lst ))
{
lst = new List < long >();
res [ p . Path ] = lst ;
2019-08-05 20:14:05 -04:00
for ( var i = 0 ; i < flindex ; i ++)
2013-05-11 12:03:15 +02:00
lst . Add (- 1 );
}
2019-08-05 20:14:05 -04:00
2013-05-11 12:03:15 +02:00
lst . Add ( p . Size );
}
2019-08-05 20:14:05 -04:00
foreach ( var n in from i in res where i . Value . Count < flindex + 1 select i )
2013-05-11 12:03:15 +02:00
n . Value . Add (- 1 );
2019-08-05 20:14:05 -04:00
2013-05-11 12:03:15 +02:00
flindex ++;
}
2019-08-05 20:14:05 -04:00
2013-05-25 16:40:15 +02:00
m_result . SetResult (
2013-05-11 12:03:15 +02:00
numberSeq ,
from n in res
orderby n . Key
2013-05-25 16:40:15 +02:00
select ( Duplicati . Library . Interface . IListResultFile )( new ListResultFile ( n . Key , n . Value ))
);
2013-03-31 19:33:34 +02:00
}
}
2013-05-30 21:54:43 +02:00
public static List < KeyValuePair < long , Volumes . IParsedVolume >> ParseAndFilterFilesets ( IEnumerable < Duplicati . Library . Interface . IFileEntry > rawlist , Options options )
{
var parsedlist = ( from n in rawlist
2019-08-05 20:14:05 -04:00
let p = Volumes . VolumeBase . ParseFilename ( n )
where p != null && p . FileType == RemoteVolumeType . Files
orderby p . Time descending
select p ). ToArray ();
2013-05-30 21:54:43 +02:00
var filelistFilter = RestoreHandler . FilterNumberedFilelist ( options . Time , options . Version );
2019-08-05 20:14:05 -04:00
return filelistFilter ( parsedlist ). ToList ();
2013-05-30 21:54:43 +02:00
}
2019-08-05 20:14:05 -04:00
2019-11-20 20:14:15 -08:00
private static IEnumerable < IListResultFileset > CreateResultSequence ( IEnumerable < KeyValuePair < long , IParsedVolume >> filteredList , BackendManager backendManager , Options options )
2013-05-30 21:54:43 +02:00
{
2019-11-20 20:14:15 -08:00
List < IListResultFileset > list = new List < IListResultFileset >();
foreach ( KeyValuePair < long , IParsedVolume > entry in filteredList )
{
AsyncDownloader downloader = new AsyncDownloader ( new IRemoteVolume [] { new RemoteVolume ( entry . Value . File )}, backendManager );
foreach ( IAsyncDownloadedFile file in downloader )
{
2019-11-28 20:16:57 -08:00
// We must obtain the partial/full status from the fileset file in the dlist files.
// Without this, the restore dialog will show all versions as full, or all versions
// as partial. While the dlist files are already downloaded elsewhere, doing so again
// here is the most direct way to obtain the partial/full status without a major
// refactoring. Since restoring directly from the backend files should be a relatively
// rare event, we can work on improving the performance later.
2019-11-20 20:14:15 -08:00
VolumeBase . FilesetData filesetData = VolumeReaderBase . GetFilesetData ( entry . Value . CompressionModule , file . TempFile , options );
list . Add ( new ListResultFileset ( entry . Key , filesetData . IsFullBackup ? BackupType . FULL_BACKUP : BackupType . PARTIAL_BACKUP , entry . Value . Time . ToLocalTime (), - 1 , - 1 ));
}
}
return list . ToArray ();
2019-08-05 20:14:05 -04:00
}
2013-03-31 19:33:34 +02:00
}
}