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.
2013-05-06 09:58:19 +02:00
using System ;
using System.Linq ;
using System.Collections.Generic ;
2013-05-06 20:51:15 +02:00
using Newtonsoft.Json.Serialization ;
2024-02-28 15:45:30 +01:00
using Duplicati.Library.Common ;
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main
2013-05-06 09:58:19 +02:00
{
public static class DatabaseLocator
{
public class BackendEntry
{
public string Type ;
public string Server ;
public string Path ;
public string Prefix ;
public string Username ;
2013-05-11 13:14:07 +02:00
//public string Passwordhash;
2013-05-06 09:58:19 +02:00
public int Port ;
public string Databasepath ;
public string ParameterFile ;
}
2013-09-06 23:44:39 +02:00
public static string GetDatabasePath ( string backend , Options options , bool autoCreate = true , bool anyUsername = false )
2013-05-06 09:58:19 +02:00
{
2014-07-25 22:17:48 +02:00
if ( options == null )
options = new Options ( new Dictionary < string , string >());
2013-05-08 19:57:13 +02:00
if (! string . IsNullOrEmpty ( options . Dbpath ))
2017-09-18 23:23:45 -06:00
return options . Dbpath ;
//Normal mode uses the systems "(Local) Application Data" folder
// %LOCALAPPDATA% on Windows, ~/.config on Linux
// Special handling for Windows:
// - Older versions use %APPDATA%
// - but new versions use %LOCALAPPDATA%
//
// If we find a new version, lets use that
// otherwise use the older location
//
var folder = System . IO . Path . Combine ( System . Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ), "Duplicati" );
2018-11-02 22:13:25 +01:00
if ( Platform . IsClientWindows )
2017-09-18 23:23:45 -06:00
{
var newlocation = System . IO . Path . Combine ( System . Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ), "Duplicati" );
var prevfile = System . IO . Path . Combine ( folder , "dbconfig.json" );
var curfile = System . IO . Path . Combine ( newlocation , "dbconfig.json" );
// If the new file exists, we use that
// If the new file does not exist, and the old file exists we use the old
// Otherwise we use the new location
if ( System . IO . File . Exists ( curfile ) || ! System . IO . File . Exists ( prevfile ))
folder = newlocation ;
2017-06-27 22:00:44 +02:00
}
2017-06-27 21:45:04 +02:00
2013-05-06 20:51:15 +02:00
var file = System . IO . Path . Combine ( folder , "dbconfig.json" );
2013-05-06 09:58:19 +02:00
List < BackendEntry > configs ;
if (! System . IO . File . Exists ( file ))
configs = new List < BackendEntry >();
else
2013-05-06 20:51:15 +02:00
configs = Newtonsoft . Json . JsonConvert . DeserializeObject < List < BackendEntry >>( System . IO . File . ReadAllText ( file , System . Text . Encoding . UTF8 ));
2013-05-06 09:58:19 +02:00
2013-05-08 21:29:59 +02:00
var uri = new Library . Utility . Uri ( backend );
2013-05-06 09:58:19 +02:00
string server = uri . Host ;
string path = uri . Path ;
string type = uri . Scheme ;
int port = uri . Port ;
string username = uri . Username ;
2013-05-13 22:32:05 +02:00
string prefix = options . Prefix ;
2013-05-06 09:58:19 +02:00
2018-09-26 20:39:14 -07:00
if ( username == null || uri . Password == null )
2013-05-06 09:58:19 +02:00
{
var sopts = DynamicLoader . BackendLoader . GetSupportedCommands ( backend );
var ropts = new Dictionary < string , string >( options . RawOptions );
foreach ( var k in uri . QueryParameters . AllKeys )
ropts [ k ] = uri . QueryParameters [ k ];
2014-03-10 00:23:37 +01:00
if ( sopts != null )
2013-05-06 09:58:19 +02:00
{
2014-03-10 00:23:37 +01:00
foreach ( var o in sopts )
{
2017-09-18 23:23:45 -06:00
if ( username == null && o . Aliases != null && o . Aliases . Contains ( "auth-username" , StringComparer . OrdinalIgnoreCase ) && ropts . ContainsKey ( o . Name ))
2014-03-10 00:23:37 +01:00
username = ropts [ o . Name ];
}
2013-05-06 09:58:19 +02:00
2014-03-10 00:23:37 +01:00
foreach ( var o in sopts )
{
2017-09-18 23:23:45 -06:00
if ( username == null && o . Name . Equals ( "auth-username" , StringComparison . OrdinalIgnoreCase ) && ropts . ContainsKey ( "auth-username" ))
2014-03-10 00:23:37 +01:00
username = ropts [ "auth-username" ];
}
2013-05-06 09:58:19 +02:00
}
}
//Now find the one that matches :)
var matches = ( from n in configs
where
n . Type == type &&
2013-05-11 13:14:07 +02:00
//n.Passwordhash == password &&
2013-05-06 09:58:19 +02:00
n . Username == username &&
n . Port == port &&
n . Server == server &&
n . Path == path &&
n . Prefix == prefix
select n ). ToList ();
if ( matches . Count > 1 )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( string . Format ( "Multiple sources found for: {0}" , backend ), "MultipleLocalDatabaseSourcesFound" );
2013-05-06 09:58:19 +02:00
2013-09-06 23:44:39 +02:00
// Re-select
if ( matches . Count == 0 && anyUsername && string . IsNullOrEmpty ( username ))
{
matches = ( from n in configs
where
n . Type == type &&
n . Port == port &&
n . Server == server &&
n . Path == path &&
n . Prefix == prefix
select n ). ToList ();
if ( matches . Count > 1 )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( String . Format ( "Multiple sources found for \"{0}\", try supplying --{1}" , backend , "auth-username" ), "MultipleLocalDatabaseSourcesFound" );
2013-09-06 23:44:39 +02:00
}
if ( matches . Count == 0 && ! autoCreate )
return null ;
2013-05-06 09:58:19 +02:00
if ( matches . Count == 0 )
{
var backupname = options . BackupName ;
2013-05-06 17:46:23 +02:00
if ( string . IsNullOrEmpty ( backupname ) || backupname == Options . DefaultBackupName )
2013-05-06 09:58:19 +02:00
backupname = GenerateRandomName ();
else
{
foreach ( var c in System . IO . Path . GetInvalidFileNameChars ())
backupname = backupname . Replace ( c . ToString (), "" );
}
var newpath = System . IO . Path . Combine ( folder , backupname + ".sqlite" );
int max_tries = 100 ;
while ( System . IO . File . Exists ( newpath ) && max_tries -- > 0 )
newpath = System . IO . Path . Combine ( folder , GenerateRandomName ());
if ( System . IO . File . Exists ( newpath ))
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( "Unable to find a unique name for the database, please use --dbpath" , "CannotCreateRandomName" );
2013-05-06 09:58:19 +02:00
//Create a new one, add it to the list, and save it
configs . Add ( new BackendEntry () {
Type = type ,
Server = server ,
Path = path ,
Prefix = prefix ,
Username = username ,
2013-05-11 13:14:07 +02:00
//Passwordhash = password,
2013-05-06 09:58:19 +02:00
Port = port ,
Databasepath = newpath ,
ParameterFile = null
});
2022-04-01 21:18:19 +02:00
if (! System . IO . Directory . Exists ( folder ))
System . IO . Directory . CreateDirectory ( folder );
2013-05-06 20:51:15 +02:00
var settings = new Newtonsoft . Json . JsonSerializerSettings ();
settings . Formatting = Newtonsoft . Json . Formatting . Indented ;
System . IO . File . WriteAllText ( file , Newtonsoft . Json . JsonConvert . SerializeObject ( configs , settings ), System . Text . Encoding . UTF8 );
2013-05-06 09:58:19 +02:00
return newpath ;
}
else
{
return matches [ 0 ]. Databasepath ;
}
}
2014-03-05 15:28:52 +01:00
public static string GenerateRandomName ()
2013-05-06 09:58:19 +02:00
{
var rnd = new Random ();
2018-05-13 15:29:55 -07:00
System . Text . StringBuilder backupName = new System . Text . StringBuilder ();
for ( var i = 0 ; i < 10 ; i ++)
2019-04-08 13:48:38 +02:00
backupName . Append (( char ) rnd . Next ( 'A' , 'Z' + 1 ));
2018-05-13 15:29:55 -07:00
return backupName . ToString ();
2013-05-06 09:58:19 +02:00
}
2016-10-13 21:45:31 +02:00
public static bool IsDatabasePathInUse ( string path )
{
var folder = System . IO . Path . Combine ( System . Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ), "Duplicati" );
if (! System . IO . Directory . Exists ( folder ))
return false ;
var file = System . IO . Path . Combine ( folder , "dbconfig.json" );
if (! System . IO . File . Exists ( file ))
return false ;
2018-05-16 19:40:51 +02:00
return Newtonsoft . Json . JsonConvert . DeserializeObject < List < BackendEntry >>( System . IO . File . ReadAllText ( file , System . Text . Encoding . UTF8 )). Any ( x => string . Equals ( path , x . Databasepath , Library . Utility . Utility . ClientFilenameStringComparison ));
2016-10-13 21:45:31 +02:00
}
2013-05-06 09:58:19 +02:00
}
}