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.
2017-01-05 16:46:01 +01:00
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Linq ;
2017-01-09 11:35:38 +01:00
using Duplicati.Library.Interface ;
2017-01-05 16:46:01 +01:00
namespace Duplicati.Library.Main.Operation
{
internal class ListBrokenFilesHandler
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType ( typeof ( ListBrokenFilesHandler ));
2019-10-19 10:56:21 -07:00
protected readonly string m_backendurl ;
protected readonly Options m_options ;
protected readonly ListBrokenFilesResults m_result ;
2017-01-05 16:46:01 +01:00
public ListBrokenFilesHandler ( string backend , Options options , ListBrokenFilesResults result )
{
m_backendurl = backend ;
m_options = options ;
m_result = result ;
}
public void Run ( Library . Utility . IFilter filter , Func < long , DateTime , long , string , long , bool > callbackhandler = null )
{
if (! System . IO . File . Exists ( m_options . Dbpath ))
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( string . Format ( "Database file does not exist: {0}" , m_options . Dbpath ), "DatabaseDoesNotExist" );
2017-01-05 16:46:01 +01:00
using ( var db = new Database . LocalListBrokenFilesDatabase ( m_options . Dbpath ))
using ( var tr = db . BeginTransaction ())
DoRun ( db , tr , filter , callbackhandler );
}
public static Tuple < DateTime , long , long >[] GetBrokenFilesetsFromRemote ( string backendurl , BasicResults result , Database . LocalListBrokenFilesDatabase db , System . Data . IDbTransaction transaction , Options options , out List < Database . RemoteVolumeEntry > missing )
{
missing = null ;
var brokensets = db . GetBrokenFilesets ( options . Time , options . Version , transaction ). ToArray ();
if ( brokensets . Length == 0 )
{
if ( db . RepairInProgress )
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( "Cannot continue because the database is marked as being under repair, but does not have broken files." , "CannotListOnDatabaseInRepair" );
2017-01-05 16:46:01 +01:00
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "NoBrokenFilesetsInDatabase" , "No broken filesets found in database, checking for missing remote files" );
2017-01-05 16:46:01 +01:00
using ( var backend = new BackendManager ( backendurl , options , result . BackendWriter , db ))
{
var remotestate = FilelistProcessor . RemoteListAnalysis ( backend , options , db , result . BackendWriter , null );
if (! remotestate . ParsedVolumes . Any ())
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( "No remote volumes were found, refusing purge" , "CannotPurgeWithNoRemoteVolumes" );
2017-01-05 16:46:01 +01:00
missing = remotestate . MissingVolumes . ToList ();
if ( missing . Count == 0 )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "NoMissingFilesFound" , "Skipping operation because no files were found to be missing, and no filesets were recorded as broken." );
2017-01-05 16:46:01 +01:00
return null ;
}
// Mark all volumes as disposable
foreach ( var f in missing )
db . UpdateRemoteVolume ( f . Name , RemoteVolumeState . Deleting , f . Size , f . Hash , transaction );
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "MarkedRemoteFilesForDeletion" , "Marked {0} remote files for deletion" , missing . Count );
2017-01-05 16:46:01 +01:00
// Drop all content from tables
db . RemoveMissingBlocks ( missing . Select ( x => x . Name ), transaction );
}
brokensets = db . GetBrokenFilesets ( options . Time , options . Version , transaction ). ToArray ();
}
return brokensets ;
}
private void DoRun ( Database . LocalListBrokenFilesDatabase db , System . Data . IDbTransaction transaction , Library . Utility . IFilter filter , Func < long , DateTime , long , string , long , bool > callbackhandler )
{
if ( filter != null && ! filter . Empty )
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( "Filters are not supported for this operation" , "FiltersAreNotSupportedForListBrokenFiles" );
2017-01-05 16:46:01 +01:00
if ( db . PartiallyRecreated )
2018-03-12 14:07:11 +01:00
throw new UserInformationException ( "The command does not work on partially recreated databases" , "ListBrokenFilesDoesNotWorkOnPartialDatabase" );
2017-01-05 16:46:01 +01:00
List < Database . RemoteVolumeEntry > missing ;
var brokensets = GetBrokenFilesetsFromRemote ( m_backendurl , m_result , db , transaction , m_options , out missing );
if ( brokensets == null )
return ;
if ( brokensets . Length == 0 )
{
m_result . BrokenFiles = new Tuple < long , DateTime , IEnumerable < Tuple < string , long >>>[ 0 ];
2018-07-09 20:38:41 +02:00
if ( missing == null )
Logging . Log . WriteInformationMessage ( LOGTAG , "NoBrokenFilesets" , "Found no broken filesets" );
else if ( missing . Count == 0 )
Logging . Log . WriteInformationMessage ( LOGTAG , "NoBrokenFilesetsOrMissingFiles" , "Found no broken filesets and no missing remote files" );
else
Logging . Log . WriteInformationMessage ( LOGTAG , "NoBrokenSetsButMissingRemoteFiles" , string . Format ( "Found no broken filesets, but {0} missing remote files. Run purge-broken-files." , missing . Count ));
2017-01-05 16:46:01 +01:00
return ;
}
var fstimes = db . FilesetTimes . ToList ();
var brokenfilesets =
brokensets . Select ( x => new
{
Version = fstimes . FindIndex ( y => y . Key == x . Item2 ),
Timestamp = x . Item1 ,
FilesetID = x . Item2 ,
BrokenCount = x . Item3
}
)
. ToArray ();
m_result . BrokenFiles =
brokenfilesets . Select (
x => new Tuple < long , DateTime , IEnumerable < Tuple < string , long >>>(
x . Version ,
x . Timestamp ,
callbackhandler == null && ! m_options . ListSetsOnly
? db . GetBrokenFilenames ( x . FilesetID , transaction ). ToArray (). AsEnumerable ()
: new MockList < Tuple < string , long >>(( int ) x . BrokenCount )
))
. ToArray ();
if ( callbackhandler != null )
foreach ( var bs in brokenfilesets )
foreach ( var fe in db . GetBrokenFilenames ( bs . FilesetID , transaction ))
if (! callbackhandler ( bs . Version , bs . Timestamp , bs . BrokenCount , fe . Item1 , fe . Item2 ))
break ;
}
private class MockList < T > : IList < T >
{
public MockList ( int count )
{
Count = count ;
}
public T this [ int index ]
{
get { throw new NotImplementedException (); }
set { throw new NotImplementedException (); }
}
public int Count { get ; private set ; }
public bool IsReadOnly { get { return true ; }}
public void Add ( T item )
{
throw new NotImplementedException ();
}
public void Clear ()
{
throw new NotImplementedException ();
}
public bool Contains ( T item )
{
throw new NotImplementedException ();
}
public void CopyTo ( T [] array , int arrayIndex )
{
throw new NotImplementedException ();
}
public IEnumerator < T > GetEnumerator ()
{
throw new NotImplementedException ();
}
public int IndexOf ( T item )
{
throw new NotImplementedException ();
}
public void Insert ( int index , T item )
{
throw new NotImplementedException ();
}
public bool Remove ( T item )
{
throw new NotImplementedException ();
}
public void RemoveAt ( int index )
{
throw new NotImplementedException ();
}
IEnumerator IEnumerable . GetEnumerator ()
{
throw new NotImplementedException ();
}
}
}
}