2012-05-31 15:30:52 +00:00
using System ;
2011-06-06 18:48:52 +00:00
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using HttpServer.MVC ;
using HttpServer.HttpModules ;
2011-11-21 22:28:20 +00:00
using System.IO ;
using Duplicati.Server.Serialization ;
2011-06-06 18:48:52 +00:00
namespace Duplicati.Server
{
public class WebServer
{
2012-05-21 19:06:39 +00:00
/// <summary>
/// Option for changing the webroot folder
/// </summary>
private const string OPTION_WEBROOT = "webservice-webroot" ;
/// <summary>
/// Option for changing the webservice listen port
/// </summary>
private const string OPTION_PORT = "webservice-port" ;
2011-11-21 22:28:20 +00:00
/// <summary>
/// The single webserver instance
/// </summary>
2011-06-06 18:48:52 +00:00
private HttpServer . HttpServer m_server ;
2012-11-18 16:24:32 +00:00
/// <summary>
/// Sets up the webserver and starts it
/// </summary>
/// <param name="options">A set of options</param>
2012-05-21 19:06:39 +00:00
public WebServer ( IDictionary < string , string > options )
2011-06-06 18:48:52 +00:00
{
m_server = new HttpServer . HttpServer ();
m_server . Add ( new DynamicHandler ());
2012-03-31 17:44:21 +00:00
string webroot = System . IO . Path . GetDirectoryName ( System . Reflection . Assembly . GetExecutingAssembly (). Location );
#if DEBUG
//For debug we go "../../../.." to get out of "GUI/Duplicati.GUI.TrayIcon/bin/debug"
string tmpwebroot = System . IO . Path . GetFullPath ( System . IO . Path . Combine ( webroot , ".." , ".." , ".." , ".." ));
tmpwebroot = System . IO . Path . Combine ( tmpwebroot , "Server" );
if ( System . IO . Directory . Exists ( System . IO . Path . Combine ( tmpwebroot , "webroot" )))
webroot = tmpwebroot ;
2012-11-18 16:24:32 +00:00
else {
2012-03-31 17:44:21 +00:00
//If we are running the server standalone, we only need to exit "bin/Debug"
tmpwebroot = System . IO . Path . GetFullPath ( System . IO . Path . Combine ( webroot , ".." , ".." ));
if ( System . IO . Directory . Exists ( System . IO . Path . Combine ( tmpwebroot , "webroot" )))
webroot = tmpwebroot ;
}
2012-11-18 16:24:32 +00:00
if ( Library . Utility . Utility . IsClientOSX )
{
string osxTmpWebRoot = System . IO . Path . GetFullPath ( System . IO . Path . Combine ( webroot , ".." , ".." , ".." , ".." , ".." , ".." , ".." ));
osxTmpWebRoot = System . IO . Path . Combine ( osxTmpWebRoot , "Server" );
if ( System . IO . Directory . Exists ( System . IO . Path . Combine ( osxTmpWebRoot , "webroot" )))
webroot = osxTmpWebRoot ;
}
2012-03-31 17:44:21 +00:00
#endif
2012-11-18 16:24:32 +00:00
2012-05-21 19:06:39 +00:00
webroot = System . IO . Path . Combine ( webroot , "webroot" );
if ( options . ContainsKey ( OPTION_WEBROOT ))
{
string userroot = options [ OPTION_WEBROOT ];
#if DEBUG
//In debug mode we do not care where the path points
#else
//In release mode we check that the usersupplied path is located
// in the same folders as the running application, to avoid users
// that inadvertently expose top level folders
if (! string . IsNullOrWhiteSpace ( userroot )
&&
(
userroot . StartsWith ( Library . Utility . Utility . AppendDirSeparator ( System . Reflection . Assembly . GetExecutingAssembly (). Location ), Library . Utility . Utility . ClientFilenameStringComparision )
||
userroot . StartsWith ( Library . Utility . Utility . AppendDirSeparator ( Program . StartupPath ), Library . Utility . Utility . ClientFilenameStringComparision )
)
)
#endif
{
webroot = userroot ;
}
}
FileModule fh = new FileModule ( "/" , webroot );
2011-06-06 18:48:52 +00:00
fh . AddDefaultMimeTypes ();
2012-05-21 19:06:39 +00:00
fh . MimeTypes . Add ( "htc" , "text/x-component" );
fh . MimeTypes . Add ( "json" , "application/json" );
2011-06-06 18:48:52 +00:00
m_server . Add ( fh );
2012-05-31 15:30:52 +00:00
m_server . Add ( new IndexHtmlHandler ( System . IO . Path . Combine ( webroot , "status-window.html" )));
2012-03-29 18:02:07 +00:00
#if DEBUG
//For debugging, it is nice to know when we get a 404
m_server . Add ( new DebugReportHandler ());
#endif
2012-05-21 19:06:39 +00:00
int port ;
string portstring ;
if (! options . TryGetValue ( OPTION_PORT , out portstring ) || ! int . TryParse ( portstring , out port ))
port = 8080 ;
2011-06-06 18:48:52 +00:00
m_server . Start ( System . Net . IPAddress . Any , port );
}
private class BodyWriter : System . IO . StreamWriter , IDisposable
{
private HttpServer . IHttpResponse m_resp ;
2011-11-21 22:28:20 +00:00
// We override the format provider so all JSON output uses US format
public override IFormatProvider FormatProvider
{
get { return System . Globalization . CultureInfo . InvariantCulture ; }
}
2011-06-06 18:48:52 +00:00
public BodyWriter ( HttpServer . IHttpResponse resp )
2011-11-21 22:28:20 +00:00
: base ( resp . Body , resp . Encoding )
2011-06-06 18:48:52 +00:00
{
m_resp = resp ;
}
2011-11-21 22:28:20 +00:00
protected override void Dispose ( bool disposing )
2011-06-06 18:48:52 +00:00
{
2011-11-21 22:28:20 +00:00
if (! m_resp . HeadersSent )
{
base . Flush ();
m_resp . ContentLength = base . BaseStream . Length ;
m_resp . Send ();
}
2011-06-06 18:48:52 +00:00
base . Dispose ( disposing );
}
}
2012-05-31 15:30:52 +00:00
private class IndexHtmlHandler : HttpModule
{
private string m_defaultdoc ;
public IndexHtmlHandler ( string defaultdoc ) { m_defaultdoc = defaultdoc ; }
public override bool Process ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session )
{
if (( request . Uri . AbsolutePath == "/" || request . Uri . AbsolutePath == "/index.html" || request . Uri . AbsolutePath == "/index.htm" ) && System . IO . File . Exists ( m_defaultdoc ))
{
response . Status = System . Net . HttpStatusCode . OK ;
response . Reason = "OK" ;
response . ContentType = "text/html" ;
using ( var fs = System . IO . File . OpenRead ( m_defaultdoc ))
{
response . ContentLength = fs . Length ;
response . Body = fs ;
response . Send ();
}
return true ;
}
return false ;
}
}
2012-03-29 18:02:07 +00:00
private class DebugReportHandler : HttpModule
{
public override bool Process ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session )
{
System . Diagnostics . Trace . WriteLine ( string . Format ( "Rejecting request for {0}" , request . Uri ));
return false ;
}
}
2012-11-18 16:24:32 +00:00
private class Serializer : Server . Serialization . Serializer
{
private class DataModelConverter < T > : Newtonsoft . Json . Converters . CustomCreationConverter < T >
where T : System . Data . LightDatamodel . IDataClass , new ()
{
private readonly System . Data . LightDatamodel . IDataFetcher m_parent ;
public DataModelConverter ( System . Data . LightDatamodel . IDataFetcher parent )
{
m_parent = parent ;
}
public override T Create ( Type objectType )
{
if ( objectType != typeof ( T ))
throw new InvalidOperationException ( string . Format ( "Cannot instanciate type {0} from a type {1} converter" , objectType , typeof ( T )));
T v = new T ();
m_parent . Add ( v );
return v ;
}
}
private class StartObjectConverter < T > : Newtonsoft . Json . Converters . CustomCreationConverter < T >
{
private T m_obj ;
public StartObjectConverter ( T obj )
{
m_obj = obj ;
}
public override T Create ( Type objectType )
{
if ( objectType != typeof ( T ))
throw new InvalidOperationException ( string . Format ( "Cannot instanciate type {0} from a type {1} converter" , objectType , typeof ( T )));
return m_obj ;
}
}
public static T Deserialize < T >( System . IO . TextReader sr , System . Data . LightDatamodel . IDataFetcher dataparent )
where T : System . Data . LightDatamodel . IDataClass , new ()
{
Newtonsoft . Json . JsonSerializer jsonSerializer = Newtonsoft . Json . JsonSerializer . Create ( m_jsonSettings );
jsonSerializer . Converters . Add ( new DataModelConverter < T >( dataparent ));
using ( var jsonReader = new Newtonsoft . Json . JsonTextReader ( sr ))
{
jsonReader . Culture = System . Globalization . CultureInfo . InvariantCulture ;
return jsonSerializer . Deserialize < T >( jsonReader );
}
}
public static T Deserialize < T >( System . IO . TextReader sr , T startobj )
{
Newtonsoft . Json . JsonSerializer jsonSerializer = Newtonsoft . Json . JsonSerializer . Create ( m_jsonSettings );
jsonSerializer . Converters . Add ( new StartObjectConverter < T >( startobj ));
using ( var jsonReader = new Newtonsoft . Json . JsonTextReader ( sr ))
{
jsonReader . Culture = System . Globalization . CultureInfo . InvariantCulture ;
return jsonSerializer . Deserialize < T >( jsonReader );
}
}
}
2011-06-06 18:48:52 +00:00
private class DynamicHandler : HttpModule
{
2011-11-21 22:28:20 +00:00
private delegate void ProcessSub ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter writer );
2011-06-06 18:48:52 +00:00
private readonly Dictionary < string , ProcessSub > SUPPORTED_METHODS ;
public DynamicHandler ()
{
SUPPORTED_METHODS = new Dictionary < string , ProcessSub >( System . StringComparer . InvariantCultureIgnoreCase );
2011-11-21 22:28:20 +00:00
//Make a list of all supported actions
SUPPORTED_METHODS . Add ( "supported-actions" , ListSupportedActions );
2012-11-18 16:24:32 +00:00
SUPPORTED_METHODS . Add ( "system-info" , ListSystemInfo );
2011-06-06 18:48:52 +00:00
SUPPORTED_METHODS . Add ( "list-schedules" , ListSchedules );
SUPPORTED_METHODS . Add ( "get-current-state" , GetCurrentState );
2012-06-06 12:58:27 +00:00
SUPPORTED_METHODS . Add ( "get-progress-state" , GetProgressState );
2011-11-21 22:28:20 +00:00
SUPPORTED_METHODS . Add ( "list-application-settings" , ListApplicationSettings );
SUPPORTED_METHODS . Add ( "list-installed-backends" , GetInstalledBackends );
SUPPORTED_METHODS . Add ( "list-installed-encryption-modules" , ListInstalledEncryptionModules );
SUPPORTED_METHODS . Add ( "list-installed-compression-modules" , ListInstalledCompressionModules );
SUPPORTED_METHODS . Add ( "list-installed-generic-modules" , ListInstalledGenericModules );
SUPPORTED_METHODS . Add ( "list-options" , ListCoreOptions );
SUPPORTED_METHODS . Add ( "list-recent-completed" , ListRecentCompleted );
SUPPORTED_METHODS . Add ( "get-recent-log-details" , GetLogBlob );
SUPPORTED_METHODS . Add ( "send-command" , SendCommand );
2012-05-05 20:13:33 +00:00
SUPPORTED_METHODS . Add ( "get-backup-defaults" , GetBackupDefaults );
2012-07-21 20:19:51 +00:00
SUPPORTED_METHODS . Add ( "get-folder-contents" , GetFolderContents );
2012-07-21 20:51:17 +00:00
SUPPORTED_METHODS . Add ( "get-schedule-details" , GetScheduleDetails );
2012-11-18 16:24:32 +00:00
SUPPORTED_METHODS . Add ( "add-schedule" , AddSchedule );
SUPPORTED_METHODS . Add ( "update-schedule" , UpdateSchedule );
SUPPORTED_METHODS . Add ( "delete-schedule" , DeleteSchedule );
2011-06-06 18:48:52 +00:00
}
2011-11-21 22:28:20 +00:00
public override bool Process ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session )
2011-06-06 18:48:52 +00:00
{
2011-11-21 22:28:20 +00:00
//We use the fake entry point /control.cgi to listen for requests
//This ensures that the rest of the webserver can just serve plain files
2011-06-06 18:48:52 +00:00
if (! request . Uri . AbsolutePath . Equals ( "/control.cgi" , StringComparison . InvariantCultureIgnoreCase ))
return false ;
HttpServer . HttpInput input = request . Method . ToUpper () == "POST" ? request . Form : request . QueryString ;
string action = input [ "action" ]. Value ?? "" ;
2011-11-21 22:28:20 +00:00
//Lookup the actual handler method
2011-06-06 18:48:52 +00:00
ProcessSub method ;
SUPPORTED_METHODS . TryGetValue ( action , out method );
2011-11-21 22:28:20 +00:00
if ( method == null ) {
2011-06-06 18:48:52 +00:00
response . Status = System . Net . HttpStatusCode . NotImplemented ;
response . Reason = "Unsupported action: " + ( action == null ? "<null>" : "" );
response . Send ();
2011-11-21 22:28:20 +00:00
} else {
2011-06-06 18:48:52 +00:00
//Default setup
response . Status = System . Net . HttpStatusCode . OK ;
response . Reason = "OK" ;
2011-11-21 22:28:20 +00:00
#if DEBUG
2011-06-06 18:48:52 +00:00
response . ContentType = "text/plain" ;
2011-11-21 22:28:20 +00:00
#else
response . ContentType = "text/json" ;
#endif
using ( BodyWriter bw = new BodyWriter ( response ))
{
try
{
method ( request , response , session , bw );
}
catch ( Exception ex )
{
if (! response . HeadersSent )
{
response . Status = System . Net . HttpStatusCode . InternalServerError ;
response . Reason = ex . Message ;
response . ContentType = "text/plain" ;
bw . WriteLine ( "Internal error" );
#if DEBUG
bw . Write ( "Stacktrace: " + ex . ToString ());
#endif
bw . Flush ();
}
}
}
2011-06-06 18:48:52 +00:00
}
return true ;
}
2011-11-21 22:28:20 +00:00
private void ReportError ( HttpServer . IHttpResponse response , BodyWriter bw , string message )
2011-06-06 18:48:52 +00:00
{
2011-11-21 22:28:20 +00:00
response . Status = System . Net . HttpStatusCode . InternalServerError ;
response . Reason = message ;
2011-06-06 18:48:52 +00:00
2011-11-21 22:28:20 +00:00
OutputObject ( bw , new { Error = message });
}
private void OutputObject ( BodyWriter b , object o )
{
2012-11-18 16:24:32 +00:00
Serializer . SerializeJson ( b , o );
}
private void ListSystemInfo ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , new
{
APIVersion = 1 ,
ServerVersion = System . Reflection . Assembly . GetExecutingAssembly (). GetName (). Version . ToString (),
ServerVersionName = License . VersionNumbers . Version ,
OSType = Library . Utility . Utility . IsClientLinux ? ( Library . Utility . Utility . IsClientOSX ? "OSX" : "Linux" ) : "Windows" ,
DirectorySeparator = System . IO . Path . DirectorySeparatorChar ,
PathSeparator = System . IO . Path . PathSeparator ,
CaseSensitiveFilesystem = Duplicati . Library . Utility . Utility . IsFSCaseSensitive ,
MonoVersion = Duplicati . Library . Utility . Utility . IsMono ? Duplicati . Library . Utility . Utility . MonoVersion . ToString () : null ,
MachineName = System . Environment . MachineName ,
NewLine = System . Environment . NewLine ,
CLRVersion = System . Environment . Version . ToString (),
CLROSInfo = new
{
Platform = System . Environment . OSVersion . Platform . ToString (),
ServicePack = System . Environment . OSVersion . ServicePack ,
Version = System . Environment . OSVersion . Version . ToString (),
VersionString = System . Environment . OSVersion . VersionString
}
});
2011-11-21 22:28:20 +00:00
}
2011-06-06 18:48:52 +00:00
2011-11-21 22:28:20 +00:00
private void ListSupportedActions ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , new { Version = 1 , Methods = SUPPORTED_METHODS . Keys });
}
2011-06-06 18:48:52 +00:00
2011-11-21 22:28:20 +00:00
private void ListSchedules ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , Program . DataConnection . GetObjects < Datamodel . Schedule >());
2011-06-06 18:48:52 +00:00
}
2012-07-21 20:19:51 +00:00
private void GetFolderContents ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
HttpServer . HttpInput input = request . Method . ToUpper () == "POST" ? request . Form : request . QueryString ;
if ( input [ "path" ] == null || input [ "path" ]. Value == null )
{
ReportError ( response , bw , "The path parameter was not set" );
return ;
}
2012-07-21 20:26:03 +00:00
bool skipFiles = Library . Utility . Utility . ParseBool ( input [ "onlyfolders" ]. Value , false );
2012-07-21 20:19:51 +00:00
string path = input [ "path" ]. Value ;
if (! path . StartsWith ( "/" ))
{
ReportError ( response , bw , "The path parameter must start with a forward-slash" );
return ;
}
try
{
2012-11-18 16:24:32 +00:00
if (! path . EndsWith ( "/" ))
path += '/' ;
2012-07-21 20:19:51 +00:00
2012-11-18 16:24:32 +00:00
IEnumerable < Serializable . TreeNode > res ;
2012-07-21 20:19:51 +00:00
2012-11-18 16:24:32 +00:00
if (! Library . Utility . Utility . IsClientLinux && path . Equals ( "/" ))
{
res =
from di in System . IO . DriveInfo . GetDrives ()
where di . DriveType == DriveType . Fixed || di . DriveType == DriveType . Network || di . DriveType == DriveType . Removable
select new Serializable . TreeNode ()
{
id = "/" + di . RootDirectory . FullName . Replace ( '\\' , '/' ),
text = di . RootDirectory . FullName . Replace ( '\\' , ' ' ) + "(" + di . DriveType + ")" ,
iconCls = "x-tree-icon-drive"
};
2012-07-21 20:19:51 +00:00
}
else
{
2012-11-18 16:24:32 +00:00
//Helper function for finding out if a folder has sub elements
Func < string , bool > hasSubElements = ( p ) => skipFiles ? Directory . EnumerateDirectories ( p ). Any () : Directory . EnumerateFileSystemEntries ( p ). Any ();
//Helper function for dealing with exceptions when accessing off-limits folders
Func < string , bool > isEmptyFolder = ( p ) =>
{
try { return ! hasSubElements ( p ); }
catch { }
return true ;
};
//Helper function for dealing with exceptions when accessing off-limits folders
Func < string , bool > canAccess = ( p ) =>
{
try { hasSubElements ( p ); return true ; }
catch { }
return false ;
};
res =
from s in System . IO . Directory . EnumerateFileSystemEntries ( Library . Utility . Utility . IsClientLinux ? path : path . Substring ( 1 ). Replace ( '/' , '\\' ))
let attr = System . IO . File . GetAttributes ( s )
let isSymlink = ( attr & FileAttributes . ReparsePoint ) != 0
let isFolder = ( attr & FileAttributes . Directory ) != 0
let isFile = ! isFolder
let isHidden = ( attr & FileAttributes . Hidden ) != 0
let accesible = isFile || canAccess ( s )
let isLeaf = isFile || ! accesible || isEmptyFolder ( s )
let rawid = isFolder ? Library . Utility . Utility . AppendDirSeparator ( s ) : s
where ! skipFiles || isFolder
select new Serializable . TreeNode ()
{
id = Library . Utility . Utility . IsClientLinux ? rawid : "/" + rawid . Replace ( '\\' , '/' ),
text = System . IO . Path . GetFileName ( s ),
iconCls = isFolder ? ( accesible ? "x-tree-icon-parent" : "x-tree-icon-locked" ) : "x-tree-icon-leaf" ,
leaf = isLeaf
};
2012-07-21 20:19:51 +00:00
}
2012-11-18 16:24:32 +00:00
2012-07-21 20:19:51 +00:00
OutputObject ( bw , res );
}
catch ( Exception ex )
{
ReportError ( response , bw , "Failed to process the path: " + ex . Message );
}
}
2012-06-06 12:58:27 +00:00
private bool LongPollCheck ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , BodyWriter bw , EventPollNotify poller , ref long id , out bool isError )
2011-06-06 18:48:52 +00:00
{
2012-05-31 15:30:52 +00:00
HttpServer . HttpInput input = request . Method . ToUpper () == "POST" ? request . Form : request . QueryString ;
if ( Library . Utility . Utility . ParseBool ( input [ "longpoll" ]. Value , false ))
{
long lastEventId ;
if (! long . TryParse ( input [ "lasteventid" ]. Value , out lastEventId ))
{
ReportError ( response , bw , "When activating long poll, the request must include the last event id" );
2012-06-06 12:58:27 +00:00
isError = true ;
return false ;
2012-05-31 15:30:52 +00:00
}
TimeSpan ts ;
try { ts = Library . Utility . Timeparser . ParseTimeSpan ( input [ "duration" ]. Value ); }
catch ( Exception ex )
{
ReportError ( response , bw , "Invalid duration: " + ex . Message );
2012-06-06 12:58:27 +00:00
isError = true ;
return false ;
2012-05-31 15:30:52 +00:00
}
if ( ts <= TimeSpan . FromSeconds ( 10 ) || ts . TotalMilliseconds > int . MaxValue )
{
ReportError ( response , bw , "Invalid duration, must be at least 10 seconds, and less than " + int . MaxValue + " milliseconds" );
2012-06-06 12:58:27 +00:00
isError = true ;
return false ;
2012-05-31 15:30:52 +00:00
}
2012-06-06 12:58:27 +00:00
isError = false ;
2012-07-14 10:42:10 +00:00
id = poller . Wait ( lastEventId , ( int ) ts . TotalMilliseconds );
2012-06-06 12:58:27 +00:00
return true ;
}
isError = false ;
return false ;
}
private void GetProgressState ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
bool isError ;
long id = 0 ;
if ( LongPollCheck ( request , response , bw , Program . ProgressEventNotifyer , ref id , out isError ) || ! isError )
{
//TODO: Don't block if the backup is completed when entering the wait state
2012-07-14 10:42:10 +00:00
var ev = Program . Runner . LastEvent ;
ev . LastEventID = id ;
OutputObject ( bw , ev );
2012-06-06 12:58:27 +00:00
}
}
private void GetCurrentState ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
bool isError ;
long id = 0 ;
if ( LongPollCheck ( request , response , bw , Program . StatusEventNotifyer , ref id , out isError ))
{
2012-05-31 20:32:56 +00:00
//Make sure we do not report a higher number than the eventnotifyer says
var st = new Serializable . ServerStatus ();
st . LastEventID = id ;
OutputObject ( bw , st );
}
2012-06-06 12:58:27 +00:00
else if (! isError )
2012-05-31 20:32:56 +00:00
{
OutputObject ( bw , new Serializable . ServerStatus ());
2012-05-31 15:30:52 +00:00
}
2011-11-21 22:28:20 +00:00
}
2011-06-06 18:48:52 +00:00
2011-11-21 22:28:20 +00:00
private void GetInstalledBackends ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , Duplicati . Library . DynamicLoader . BackendLoader . Backends );
}
2011-06-06 18:48:52 +00:00
2011-11-21 22:28:20 +00:00
private void ListInstalledEncryptionModules ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , Duplicati . Library . DynamicLoader . EncryptionLoader . Modules );
}
2011-06-06 18:48:52 +00:00
2011-11-21 22:28:20 +00:00
private void ListInstalledCompressionModules ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , Duplicati . Library . DynamicLoader . CompressionLoader . Modules );
}
2011-06-06 18:48:52 +00:00
2011-11-21 22:28:20 +00:00
private void ListInstalledGenericModules ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , Duplicati . Library . DynamicLoader . GenericLoader . Modules );
}
2011-06-06 18:48:52 +00:00
2011-11-21 22:28:20 +00:00
private void ListCoreOptions ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , new Duplicati . Library . Main . Options ( new Dictionary < string , string >()). SupportedCommands );
}
2011-06-06 18:48:52 +00:00
2011-11-21 22:28:20 +00:00
private void ListRecentCompleted ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , Program . DataConnection . GetObjects < Datamodel . Log >( "EndTime > ? AND SubAction LIKE ? ORDER BY EndTime DESC" , Library . Utility . Timeparser . ParseTimeInterval ( new Datamodel . ApplicationSettings ( Program . DataConnection ). RecentBackupDuration , DateTime . Now , true ), "Primary" ));
}
2011-06-06 18:48:52 +00:00
2011-11-21 22:28:20 +00:00
private void GetLogBlob ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
HttpServer . HttpInput input = request . Method . ToUpper () == "POST" ? request . Form : request . QueryString ;
long id ;
if (! long . TryParse ( input [ "id" ]. Value , out id ))
ReportError ( response , bw , "Invalid or missing log id" );
else
{
Datamodel . LogBlob lb = Program . DataConnection . GetObject < Datamodel . LogBlob >( "LogId = ?" , id );
OutputObject ( bw , lb );
2011-06-06 18:48:52 +00:00
}
}
2011-11-21 22:28:20 +00:00
private void ListApplicationSettings ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , new Datamodel . ApplicationSettings ( Program . DataConnection ));
}
2012-05-05 20:13:33 +00:00
private void GetBackupDefaults ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
OutputObject ( bw , new Serializable . JobSettings ());
}
2012-07-21 20:51:17 +00:00
private void GetScheduleDetails ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
HttpServer . HttpInput input = request . Method . ToUpper () == "POST" ? request . Form : request . QueryString ;
long id ;
if (! long . TryParse ( input [ "id" ]. Value , out id ))
ReportError ( response , bw , "Invalid or missing schedule id" );
else
{
Datamodel . Schedule sc = Program . DataConnection . GetObjectById < Datamodel . Schedule >( id );
2012-11-18 16:24:32 +00:00
OutputObject ( bw , new
{
success = true ,
data = new
{
Schedule = sc ,
Task = sc . Task
}
2012-07-21 20:51:17 +00:00
});
}
}
2012-05-05 20:13:33 +00:00
2012-11-18 16:24:32 +00:00
private void UpdateSchedule ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
string schedule_string = request . Form [ "schedule" ]. Value ;
string task_string = request . Form [ "task" ]. Value ;
if ( string . IsNullOrWhiteSpace ( schedule_string ))
{
ReportError ( response , bw , "Missing Schedule object" );
return ;
}
if ( string . IsNullOrWhiteSpace ( task_string ))
{
ReportError ( response , bw , "Missing Task object" );
return ;
}
long id ;
if (! long . TryParse ( request . Form [ "id" ]. Value , out id ))
{
ReportError ( response , bw , "Invalid or missing schedule id" );
return ;
}
try
{
var con = new System . Data . LightDatamodel . DataFetcherNested ( Program . DataConnection );
var schedule = con . GetObjectById < Datamodel . Schedule >( id );
if ( schedule == null )
{
ReportError ( response , bw , "Invalid or missing schedule id" );
return ;
}
Serializer . Deserialize < Datamodel . Schedule >( new StringReader ( schedule_string ), schedule );
var task = Serializer . Deserialize < Datamodel . Task >( new StringReader ( task_string ), schedule . Task );
//TODO: Validate, duplicate names etc.
con . CommitRecursive ( schedule );
OutputObject ( bw , new { status = "OK" });
}
catch ( Exception ex )
{
ReportError ( response , bw , string . Format ( "Unable to parse schedule or task object: {0}" , ex . Message ));
}
}
private void AddSchedule ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
string schedule_string = request . Form [ "schedule" ]. Value ;
string task_string = request . Form [ "task" ]. Value ;
if ( string . IsNullOrWhiteSpace ( schedule_string ))
{
ReportError ( response , bw , "Missing Schedule object" );
return ;
}
if ( string . IsNullOrWhiteSpace ( task_string ))
{
ReportError ( response , bw , "Missing Task object" );
return ;
}
try
{
var con = new System . Data . LightDatamodel . DataFetcherNested ( Program . DataConnection );
var schedule = Serializer . Deserialize < Datamodel . Schedule >( new StringReader ( schedule_string ), con );
var task = Serializer . Deserialize < Datamodel . Task >( new StringReader ( task_string ), con );
schedule . Task = task ;
//TODO: Validate, duplicate names etc.
con . CommitRecursive ( schedule );
OutputObject ( bw , new { status = "OK" });
}
catch ( Exception ex )
{
ReportError ( response , bw , string . Format ( "Unable to parse schedule or task object: {0}" , ex . Message ));
}
}
private void DeleteSchedule ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
HttpServer . HttpInput input = request . Method . ToUpper () == "POST" ? request . Form : request . QueryString ;
long id ;
if (! long . TryParse ( input [ "id" ]. Value , out id ))
{
ReportError ( response , bw , "Invalid or missing schedule id" );
return ;
}
var con = new System . Data . LightDatamodel . DataFetcherNested ( Program . DataConnection );
var schedule = con . GetObjectById < Datamodel . Schedule >( id );
if ( schedule == null )
{
ReportError ( response , bw , "Invalid or missing schedule id" );
return ;
}
if ( Program . WorkThread . Active )
{
try
{
//TODO: It's not safe to access the values like this,
//because the runner thread might interfere
if ( Program . WorkThread . CurrentTask . Schedule . ID == schedule . ID )
{
bool force ;
if (! bool . TryParse ( input [ "force" ]. Value , out force ))
force = false ;
if (! force )
{
OutputObject ( bw , new { status = "failed" , reason = "backup-in-progress" });
return ;
}
bool hasPaused = Program . LiveControl . State == LiveControls . LiveControlState . Paused ;
Program . LiveControl . Pause ();
if ( Program . WorkThread . CurrentTask . Schedule . ID == schedule . ID )
Program . Runner . Terminate ();
try
{
for ( int i = 0 ; i < 10 ; i ++)
if ( Program . WorkThread . Active )
{
IDuplicityTask t = Program . WorkThread . CurrentTask ;
if ( t != null && t . Schedule . ID == schedule . ID )
System . Threading . Thread . Sleep ( 1000 );
else
break ;
}
else
break ;
}
finally
{
}
if ( Program . WorkThread . Active )
{
IDuplicityTask t = Program . WorkThread . CurrentTask ;
if ( t == null && t . Schedule . ID == schedule . ID )
{
if ( hasPaused )
Program . LiveControl . Resume ();
OutputObject ( bw , new { status = "failed" , reason = "backup-unstoppable" });
return ;
}
}
if ( hasPaused )
Program . LiveControl . Resume ();
}
}
catch ( Exception ex )
{
OutputObject ( bw , new { status = "error" , message = ex . Message });
return ;
}
}
//TODO: Locking! Clients will request the list while the delete takes place
//Prior to deleting we need to make sure all relations are loaded
Queue < object > unvisited = new Queue < object >();
Dictionary < object , object > visited = new Dictionary < object , object >();
//Load the schedule in the transaction context
object entryItem = con . GetObjectById < Datamodel . Schedule >( id );
unvisited . Enqueue ( entryItem );
visited [ entryItem ] = null ;
//Traverse the object relations
while ( unvisited . Count > 0 )
{
object x = unvisited . Dequeue ();
foreach ( System . Reflection . PropertyInfo pi in x . GetType (). GetProperties ())
{
if ( pi . PropertyType != typeof ( string ) && ( typeof ( System . Data . LightDatamodel . IDataClass ). IsAssignableFrom ( pi . PropertyType ) || typeof ( System . Collections . IEnumerable ). IsAssignableFrom ( pi . PropertyType )))
try
{
object tmp = pi . GetValue ( x , null );
foreach ( object i in tmp as System . Collections . IEnumerable ?? new object [] { tmp })
if ( i as System . Data . LightDatamodel . IDataClass != null && ! visited . ContainsKey ( i ))
{
visited [ i ] = null ;
unvisited . Enqueue ( i );
}
}
catch
{
//TODO: Perhaps log this?
}
}
}
//Remove all entries visited
foreach ( System . Data . LightDatamodel . IDataClass o in visited . Keys )
con . DeleteObject ( o );
//TODO: The worker may schedule the task while we attempt to de-schedule it
foreach ( IDuplicityTask t in Program . WorkThread . CurrentTasks )
if ( t != null && t is IncrementalBackupTask && (( IncrementalBackupTask ) t ). Schedule . ID == schedule . ID )
Program . WorkThread . RemoveTask ( t );
//Persist to database
con . CommitAllRecursive ();
//We have fiddled with the schedules
Program . Scheduler . Reschedule ();
OutputObject ( bw , new { status = "OK" });
}
2011-11-21 22:28:20 +00:00
private void SendCommand ( HttpServer . IHttpRequest request , HttpServer . IHttpResponse response , HttpServer . Sessions . IHttpSession session , BodyWriter bw )
{
HttpServer . HttpInput input = request . Method . ToUpper () == "POST" ? request . Form : request . QueryString ;
string command = input [ "command" ]. Value ?? "" ;
long id ;
switch ( command . ToLowerInvariant ())
{
case "pause" :
2012-05-21 19:06:39 +00:00
if ( input . Contains ( "duration" ) && ! string . IsNullOrWhiteSpace ( input [ "duration" ]. Value ))
2011-11-21 22:28:20 +00:00
{
TimeSpan ts ;
try { ts = Library . Utility . Timeparser . ParseTimeSpan ( input [ "duration" ]. Value ); }
catch ( Exception ex )
{
ReportError ( response , bw , ex . Message );
return ;
}
2012-05-21 19:06:39 +00:00
if ( ts . TotalMilliseconds > 0 )
Program . LiveControl . Pause ( ts );
else
Program . LiveControl . Pause ();
2011-11-21 22:28:20 +00:00
}
else
{
Program . LiveControl . Pause ();
}
break ;
case "resume" :
Program . LiveControl . Resume ();
break ;
case "stop" :
if (! Program . Runner . IsStopRequested )
Program . Runner . Stop ( CloseReason . UserClosing );
break ;
case "abort" :
if (! Program . Runner . IsStopRequested )
{
Program . Runner . Stop ( CloseReason . UserClosing );
Program . Runner . Stop ( CloseReason . UserClosing );
}
break ;
case "run" :
case "run-backup" :
{
Datamodel . Schedule schedule = null ;
if ( long . TryParse ( input [ "id" ]. Value , out id ))
schedule = Program . DataConnection . GetObjectById < Datamodel . Schedule >( id );
if ( schedule == null )
{
ReportError ( response , bw , string . Format ( "No backup found for id: {0}" , input [ "id" ]. Value ));
return ;
}
2012-05-21 19:06:39 +00:00
if ( Library . Utility . Utility . ParseBoolOption ( input . ToDictionary ( x => x . Name , x => x . Value ), "full" ))
2011-11-21 22:28:20 +00:00
Program . WorkThread . AddTask ( new FullBackupTask ( schedule ));
else
Program . WorkThread . AddTask ( new IncrementalBackupTask ( schedule ));
}
break ;
2011-11-23 20:52:28 +00:00
case "clear-warning" :
Program . HasWarning = false ;
break ;
case "clear-error" :
Program . HasError = false ;
break ;
2011-11-21 22:28:20 +00:00
default :
ReportError ( response , bw , string . Format ( "Unsupported command {0}" , command ));
break ;
}
OutputObject ( bw , new { Status = "OK" });
}
2011-06-06 18:48:52 +00:00
}
}
}