2019-09-08 21:39:04 +02:00
using System ;
using System.Collections.Generic ;
using System.Drawing ;
using System.Net ;
using System.Windows.Forms ;
using UpTool2.Properties ;
using System.Xml.Linq ;
using System.IO ;
using System.Diagnostics ;
using System.IO.Compression ;
using System.Security.Cryptography ;
2019-09-10 12:20:00 +02:00
using System.Linq ;
2019-09-29 16:19:57 +02:00
using Microsoft.VisualBasic ;
2019-10-21 14:39:33 +02:00
using System.Drawing.Drawing2D ;
using System.Drawing.Imaging ;
2019-09-08 21:39:04 +02:00
namespace UpTool2
{
public partial class MainForm : Form
{
2019-10-20 15:18:21 +02:00
#region Install / Remove / Upload ( Main local app management )
2019-09-08 21:39:04 +02:00
private void Action_install_Click ( object sender , EventArgs e )
{
string app = "" ;
string tmp = "" ;
2019-09-29 16:19:57 +02:00
#if ! DEBUG
2019-09-08 21:39:04 +02:00
try
{
2019-09-29 16:19:57 +02:00
#endif
2019-09-08 21:39:04 +02:00
App appI = ( App ) action_install . Tag ;
app = dir + @"\Apps\" + appI . ID . ToString ( ) ;
tmp = dir + @"\tmp" ;
if ( Directory . Exists ( tmp ) )
Directory . Delete ( tmp , true ) ;
Directory . CreateDirectory ( tmp ) ;
if ( Directory . Exists ( app ) )
Directory . Delete ( app , true ) ;
Directory . CreateDirectory ( app ) ;
2019-09-10 12:06:15 +02:00
if ( new DownloadDialog ( appI . file , app + @"\package.zip" ) . ShowDialog ( ) ! = DialogResult . OK )
throw new Exception ( "Download failed" ) ;
2019-10-20 18:31:32 +02:00
using ( SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider ( ) )
{
string pkghash = BitConverter . ToString ( sha256 . ComputeHash ( File . ReadAllBytes ( app + @"\package.zip" ) ) ) . Replace ( "-" , string . Empty ) . ToUpper ( ) ;
if ( pkghash ! = appI . hash . ToUpper ( ) )
throw new Exception ( "The hash is not equal to the one stored in the repo:\r\nPackage: " + pkghash + "\r\nOnline: " + appI . hash . ToUpper ( ) ) ;
}
2019-09-29 16:19:57 +02:00
completeInstall ( app , appI ) ;
#if ! DEBUG
2019-09-08 21:39:04 +02:00
}
catch ( Exception e1 )
{
if ( ! relE )
throw ;
if ( Directory . Exists ( app ) )
Directory . Delete ( app , true ) ;
MessageBox . Show ( e1 . ToString ( ) , "Install failed" ) ;
}
2019-09-29 16:19:57 +02:00
#endif
Directory . Delete ( tmp , true ) ;
}
2019-10-20 15:18:21 +02:00
private void Action_remove_Click ( object sender , EventArgs e )
{
try
{
string app = dir + @"\Apps\" + ( ( App ) action_remove . Tag ) . ID . ToString ( ) ;
string tmp = dir + @"\tmp" ;
if ( Directory . Exists ( tmp ) )
Directory . Delete ( tmp , true ) ;
Directory . CreateDirectory ( tmp ) ;
ZipFile . ExtractToDirectory ( app + @"\package.zip" , tmp ) ;
Process . Start ( new ProcessStartInfo { FileName = "cmd.exe" , Arguments = "/C \"" + tmp + "\\Remove.bat\"" , WorkingDirectory = app + @"\app" } ) . WaitForExit ( ) ;
Directory . Delete ( tmp , true ) ;
Directory . Delete ( app , true ) ;
if ( relE )
reloadElements ( ) ;
}
catch ( Exception e1 )
{
if ( ! relE )
throw ;
MessageBox . Show ( e1 . ToString ( ) , "Removal failed" ) ;
}
}
2019-09-29 16:19:57 +02:00
private void controls_upload_Click ( object sender , EventArgs e )
{
string app = "" ;
string tmp = "" ;
#if ! DEBUG
try
{
#endif
if ( searchPackageDialog . ShowDialog ( ) = = DialogResult . OK )
{
Guid ID = Guid . NewGuid ( ) ;
app = dir + @"\Apps\" + ID . ToString ( ) ;
while ( Directory . Exists ( app ) )
{
ID = Guid . NewGuid ( ) ;
app = dir + @"\Apps\" + ID . ToString ( ) ;
}
App appI = new App ( Interaction . InputBox ( "Name:" ) , "Locally installed package, removal only" , - 1 , "" , true , "" , ID , Color . Red , Resources . C_64 . ToBitmap ( ) , false , "" ) ;
Directory . CreateDirectory ( app ) ;
tmp = dir + @"\tmp" ;
if ( Directory . Exists ( tmp ) )
Directory . Delete ( tmp , true ) ;
Directory . CreateDirectory ( tmp ) ;
File . Copy ( searchPackageDialog . FileName , app + @"\package.zip" ) ;
completeInstall ( app , appI ) ;
}
#if ! DEBUG
}
catch ( Exception e1 )
{
if ( ! relE )
throw ;
if ( Directory . Exists ( app ) )
Directory . Delete ( app , true ) ;
MessageBox . Show ( e1 . ToString ( ) , "Install failed" ) ;
}
#endif
2019-09-29 16:32:16 +02:00
if ( tmp ! = "" & & Directory . Exists ( tmp ) )
Directory . Delete ( tmp , true ) ;
2019-09-29 16:19:57 +02:00
}
void completeInstall ( string app , App appI )
{
try
{
string tmp = dir + @"\tmp" ;
ZipFile . ExtractToDirectory ( app + @"\package.zip" , tmp ) ;
Directory . Move ( tmp + @"\Data" , app + @"\app" ) ;
if ( appI . runnable )
new XElement ( "app" , new XElement ( "Name" , appI . name ) , new XElement ( "Description" , appI . description ) , new XElement ( "Version" , appI . version ) , new XElement ( "MainFile" , appI . mainFile ) ) . Save ( app + @"\info.xml" ) ;
else
new XElement ( "app" , new XElement ( "Name" , appI . name ) , new XElement ( "Description" , appI . description ) , new XElement ( "Version" , appI . version ) ) . Save ( app + @"\info.xml" ) ;
Process . Start ( new ProcessStartInfo { FileName = "cmd.exe" , Arguments = "/C \"" + tmp + "\\Install.bat\"" , WorkingDirectory = app + @"\app" } ) . WaitForExit ( ) ;
if ( relE )
reloadElements ( ) ;
}
catch { throw ; }
2019-09-08 21:39:04 +02:00
}
2019-10-20 15:18:21 +02:00
#endregion
#region Repo management
void reloadElements ( )
{
//remove
toolTip . RemoveAll ( ) ;
clearSelection ( ) ;
infoPanel_Title . Invalidate ( ) ;
infoPanel_Description . Invalidate ( ) ;
int F = sidebarPanel . Controls . Count ;
for ( int i = 0 ; i < F ; i + + )
{
sidebarPanel . Controls [ 0 ] . Dispose ( ) ;
}
apps . Clear ( ) ;
//add
toolTip . SetToolTip ( controls_settings , "Settings" ) ;
toolTip . SetToolTip ( controls_reload , "Refresh repositories" ) ;
toolTip . SetToolTip ( controls_upload , "Install package from disk" ) ;
toolTip . SetToolTip ( filterBox , "Filter" ) ;
toolTip . SetToolTip ( action_install , "Install" ) ;
toolTip . SetToolTip ( action_remove , "Remove" ) ;
toolTip . SetToolTip ( action_update , "Update" ) ;
toolTip . SetToolTip ( action_run , "Run" ) ;
2019-10-20 18:25:00 +02:00
getReposFromDisk ( ) ;
2019-10-20 15:18:21 +02:00
foreach ( App app in apps . Values )
{
Panel sidebarIcon = new Panel ( ) ;
sidebarIcon . Tag = app ;
sidebarIcon . BackColor = app . color ;
sidebarIcon . Size = new Size ( 70 , 70 ) ;
sidebarIcon . BackgroundImage = app . icon ;
sidebarIcon . BackgroundImageLayout = ImageLayout . Stretch ;
2019-10-20 18:25:00 +02:00
sidebarIcon . Click + = ( object sender , EventArgs e ) = >
{
2019-10-20 15:18:21 +02:00
infoPanel_Title . Text = app . name ;
infoPanel_Title . ForeColor = app . local ? Color . Red : Color . Black ;
infoPanel_Description . Text = app . description ;
action_install . Tag = app ;
action_install . Enabled = ! ( app . local | | Directory . Exists ( dir + @"\Apps\" + app . ID . ToString ( ) ) ) ;
action_remove . Tag = app ;
action_remove . Enabled = Directory . Exists ( dir + @"\Apps\" + app . ID . ToString ( ) ) ;
action_update . Tag = app ;
string xml = dir + @"\Apps\" + app . ID . ToString ( ) + @"\info.xml" ;
action_update . Enabled = ( ! app . local ) & & File . Exists ( xml ) & & int . Parse ( XDocument . Load ( xml ) . Element ( "app" ) . Element ( "Version" ) . Value ) < app . version ;
action_run . Tag = app ;
action_run . Enabled = ( ! app . local ) & & app . runnable & & Directory . Exists ( dir + @"\Apps\" + app . ID . ToString ( ) ) ;
} ;
toolTip . SetToolTip ( sidebarIcon , app . name ) ;
sidebarPanel . Controls . Add ( sidebarIcon ) ;
}
updateSidebarV ( null , null ) ;
}
2019-10-20 18:25:00 +02:00
void getReposFromDisk ( )
2019-10-20 15:18:21 +02:00
{
apps . Clear ( ) ;
2019-10-20 18:25:00 +02:00
string xml = dir + @"\info.xml" ;
2019-10-21 14:39:33 +02:00
XDocument . Load ( xml ) . Element ( "meta" ) . Element ( "LocalRepo" ) . Elements ( ) . ToList ( ) . ForEach ( app = >
2019-10-20 18:25:00 +02:00
{
2019-10-21 14:39:33 +02:00
apps . Add ( Guid . Parse ( app . Element ( "ID" ) . Value ) , new App (
app . Element ( "Name" ) . Value ,
app . Element ( "Description" ) . Value ,
int . Parse ( app . Element ( "Version" ) . Value ) ,
app . Element ( "File" ) . Value ,
false ,
app . Element ( "Hash" ) . Value ,
Guid . Parse ( app . Element ( "ID" ) . Value ) ,
ColorTranslator . FromHtml ( app . Element ( "Color" ) . Value ) ,
app . Element ( "Icon" ) = = null ? Resources . C_64 . ToBitmap ( ) : ( Bitmap ) new ImageConverter ( ) . ConvertFrom ( Convert . FromBase64String ( app . Element ( "Icon" ) . Value ) ) ,
app . Element ( "MainFile" ) ! = null ,
app . Element ( "MainFile" ) ! = null ? "" : app . Element ( "MainFile" ) . Value
) ) ;
} ) ;
2019-10-20 18:25:00 +02:00
Directory . GetDirectories ( dir + @"\Apps\" ) . Where ( s = > ! apps . ContainsKey ( Guid . Parse ( Path . GetFileName ( s ) ) ) ) . ToList ( ) . ForEach ( s = >
{
Guid tmp = Guid . Parse ( Path . GetFileName ( s ) ) ;
XElement data = XDocument . Load ( getAppPath ( tmp ) + @"\info.xml" ) . Element ( "app" ) ;
apps . Add ( tmp , new App ( "(local) " + data . Element ( "Name" ) . Value , data . Element ( "Description" ) . Value , - 1 , "" , true , "" , tmp , Color . Red , Resources . C_64 . ToBitmap ( ) , data . Element ( "MainFile" ) ! = null , data . Element ( "MainFile" ) = = null ? "" : data . Element ( "MainFile" ) . Value ) ) ;
} ) ;
2019-10-20 15:18:21 +02:00
}
2019-10-20 18:25:00 +02:00
void fetchRepos ( )
2019-10-20 15:18:21 +02:00
{
2019-10-21 14:39:33 +02:00
string xml = dir + @"\info.xml" ;
XElement meta = XDocument . Load ( xml ) . Element ( "meta" ) ;
List < XElement > tmp_apps_list = new List < XElement > ( ) ;
if ( meta . Element ( "Repos" ) = = null )
meta . Add ( new XElement ( "Repos" ) ) ;
if ( meta . Element ( "Repos" ) . Elements ( "Repo" ) . Count ( ) = = 0 )
2019-10-21 15:18:08 +02:00
meta . Element ( "Repos" ) . Add ( new XElement ( "Repo" , new XElement ( "Name" , "UpTool2 official Repo" ) , new XElement ( "Link" , "https://github.com/CreepyCrafter24/UpTool2/releases/download/Repo/Repo.xml" ) ) ) ;
string [ ] repArr = meta . Element ( "Repos" ) . Elements ( "Repo" ) . Select ( s = > s . Element ( "Link" ) . Value ) . ToArray ( ) ;
2019-10-20 18:25:00 +02:00
using ( WebClient client = new WebClient ( ) )
2019-10-20 15:18:21 +02:00
{
2019-10-21 14:39:33 +02:00
for ( int i = 0 ; i < repArr . Length ; i + + )
2019-10-20 15:18:21 +02:00
{
2019-10-20 18:25:00 +02:00
#if ! DEBUG
try
2019-10-20 15:18:21 +02:00
{
2019-10-20 18:25:00 +02:00
#endif
2019-10-21 14:39:33 +02:00
XDocument repo = XDocument . Load ( repArr [ i ] ) ;
2019-10-20 18:25:00 +02:00
foreach ( XElement app in repo . Element ( "repo" ) . Elements ( "app" ) )
2019-10-20 15:18:21 +02:00
{
2019-10-21 14:39:33 +02:00
if ( tmp_apps_list . Where ( a = > a . Element ( "ID" ) . Value = = app . Element ( "ID" ) . Value ) . Count ( ) = = 0 | |
tmp_apps_list . Where ( a = > a . Element ( "ID" ) . Value = = app . Element ( "ID" ) . Value )
. Where ( a = > int . Parse ( a . Element ( "Version" ) . Value ) > = int . Parse ( app . Element ( "Version" ) . Value ) ) . Count ( ) = = 0 )
{
//"Sanity check"
int . Parse ( app . Element ( "Version" ) . Value ) ;
Guid . Parse ( app . Element ( "ID" ) . Value ) ;
ColorTranslator . FromHtml ( app . Element ( "Color" ) . Value ) ;
//Create XElement
tmp_apps_list . Add ( new XElement ( "App" ,
new XElement ( "Name" , app . Element ( "Name" ) . Value ) ,
new XElement ( "Description" , app . Element ( "Description" ) . Value ) ,
new XElement ( "Version" , app . Element ( "Version" ) . Value ) ,
new XElement ( "ID" , app . Element ( "ID" ) . Value ) ,
new XElement ( "File" , app . Element ( "File" ) . Value ) ,
new XElement ( "Hash" , app . Element ( "Hash" ) . Value )
) ) ;
if ( app . Element ( "MainFile" ) ! = null )
tmp_apps_list . Last ( ) . Add ( new XElement ( "MainFile" , app . Element ( "MainFile" ) . Value ) ) ;
if ( app . Element ( "Icon" ) ! = null )
{
#if ! DEBUG
try
{
#endif
//Scale Image and save as Base64
Image src = Image . FromStream ( client . OpenRead ( app . Element ( "Icon" ) . Value ) ) ;
Bitmap dest = new Bitmap ( 70 , 70 ) ;
dest . SetResolution ( src . HorizontalResolution , src . VerticalResolution ) ;
using ( Graphics g = Graphics . FromImage ( dest ) )
{
g . CompositingMode = CompositingMode . SourceCopy ;
g . CompositingQuality = CompositingQuality . HighQuality ;
g . InterpolationMode = InterpolationMode . HighQualityBicubic ;
g . SmoothingMode = SmoothingMode . HighQuality ;
g . PixelOffsetMode = PixelOffsetMode . HighQuality ;
using ( var wrapMode = new ImageAttributes ( ) )
{
wrapMode . SetWrapMode ( WrapMode . TileFlipXY ) ;
g . DrawImage ( src , new Rectangle ( 0 , 0 , 70 , 70 ) , 0 , 0 , src . Width , src . Height , GraphicsUnit . Pixel , wrapMode ) ;
}
}
using ( var ms = new MemoryStream ( ) )
{
dest . Save ( ms , ImageFormat . Png ) ;
tmp_apps_list . Last ( ) . Add ( new XElement ( "Icon" , Convert . ToBase64String ( ms . ToArray ( ) ) ) ) ;
}
#if ! DEBUG
}
catch { }
#endif
}
if ( app . Element ( "Color" ) = = null )
tmp_apps_list . Last ( ) . Add ( new XElement ( "Color" , "#FFFFFF" ) ) ;
else
tmp_apps_list . Last ( ) . Add ( new XElement ( "Color" , app . Element ( "Color" ) . Value ) ) ;
if ( tmp_apps_list . Where ( a = > a . Element ( "ID" ) . Value = = app . Element ( "ID" ) . Value ) . Count ( ) > 1 )
tmp_apps_list . Where ( a = > a . Element ( "ID" ) . Value = = app . Element ( "ID" ) . Value ) . Reverse ( ) . Skip ( 1 ) . ToList ( ) . ForEach ( a = > tmp_apps_list . Remove ( a ) ) ;
}
2019-10-20 15:18:21 +02:00
}
#if ! DEBUG
2019-10-20 18:25:00 +02:00
}
catch ( Exception e )
{
2019-10-21 14:39:33 +02:00
MessageBox . Show ( e . ToString ( ) , "Failed to load repo: " + repArr [ i ] ) ;
2019-10-20 18:25:00 +02:00
}
2019-10-20 15:18:21 +02:00
#endif
}
}
2019-10-21 14:39:33 +02:00
tmp_apps_list . Sort ( ( x , y ) = > x . Element ( "Name" ) . Value . CompareTo ( y . Element ( "Name" ) . Value ) ) ;
if ( meta . Element ( "LocalRepo" ) = = null )
meta . Add ( new XElement ( "LocalRepo" ) ) ;
XElement repos = meta . Element ( "LocalRepo" ) ;
2019-10-20 15:18:21 +02:00
repos . RemoveNodes ( ) ;
2019-10-21 14:39:33 +02:00
tmp_apps_list . ForEach ( app = > repos . Add ( app ) ) ;
2019-10-20 15:18:21 +02:00
meta . Save ( xml ) ;
}
#endregion
#region Run / Update / Reload / Settings ( Small links to other stuff )
private void Action_run_Click ( object sender , EventArgs e ) = > _ = Process . Start ( new ProcessStartInfo { FileName = getDataPath ( ( App ) action_run . Tag ) + "\\" + ( ( App ) action_run . Tag ) . mainFile , WorkingDirectory = getDataPath ( ( App ) action_run . Tag ) } ) ;
private void Action_update_Click ( object sender , EventArgs e )
{
try
{
relE = false ;
Action_remove_Click ( sender , e ) ;
Action_install_Click ( sender , e ) ;
}
catch ( Exception e1 )
{
MessageBox . Show ( e1 . ToString ( ) , "Install failed" ) ;
}
reloadElements ( ) ;
relE = true ;
}
2019-10-20 18:25:00 +02:00
private void Controls_reload_Click ( object sender , EventArgs e )
{
fetchRepos ( ) ;
reloadElements ( ) ;
}
2019-10-21 15:33:09 +02:00
private void Controls_settings_Click ( object sender , EventArgs e ) = > new SettingsForms ( ) . ShowDialog ( ) ;
2019-10-20 15:18:21 +02:00
#endregion
#region GUI ( stuff only present for GUI )
2019-09-10 10:02:24 +02:00
void clearSelection ( )
{
action_install . Enabled = false ;
action_remove . Enabled = false ;
action_update . Enabled = false ;
action_run . Enabled = false ;
infoPanel_Title . Text = "" ;
infoPanel_Description . Text = "" ;
}
private void updateSidebarV ( object sender , EventArgs e )
2019-09-09 17:50:33 +02:00
{
2019-09-10 10:02:24 +02:00
Enum . TryParse ( filterBox . SelectedValue . ToString ( ) , out Status status ) ;
2019-09-09 17:50:33 +02:00
for ( int i = 0 ; i < sidebarPanel . Controls . Count ; i + + )
{
Panel sidebarIcon = ( Panel ) sidebarPanel . Controls [ i ] ;
2019-09-10 10:02:24 +02:00
App app = ( App ) sidebarIcon . Tag ;
2019-09-29 16:19:57 +02:00
sidebarIcon . Visible = app . name . Contains ( searchBox . Text ) & & ( ( int ) app . status & ( int ) status ) ! = 0 ;
2019-09-10 10:02:24 +02:00
}
clearSelection ( ) ;
}
2019-10-20 15:18:21 +02:00
public MainForm ( )
{
InitializeComponent ( ) ;
filterBox . DataSource = Enum . GetValues ( typeof ( Status ) ) ;
2019-10-20 18:25:00 +02:00
fetchRepos ( ) ;
2019-10-20 15:18:21 +02:00
reloadElements ( ) ;
if ( ! Directory . Exists ( appsPath ) )
Directory . CreateDirectory ( appsPath ) ;
}
private void MainForm_Load ( object sender , EventArgs e )
{
Program . splash . Hide ( ) ;
BringToFront ( ) ;
}
#endregion
#region Definitions
2019-09-10 10:02:24 +02:00
private struct App : IEquatable < App >
{
public string name ;
public string description ;
public int version ;
public string file ;
2019-09-29 16:19:57 +02:00
public bool local ;
2019-09-10 10:02:24 +02:00
public string hash ;
public Guid ID ;
public Color color ;
public Image icon ;
public bool runnable ;
public string mainFile ;
2019-10-20 18:25:00 +02:00
public App ( string name , string description , int version , string file , bool local , string hash , Guid iD , Color color , Image icon , bool runnable , string mainFile )
2019-09-10 10:02:24 +02:00
{
this . name = name ? ? throw new ArgumentNullException ( nameof ( name ) ) ;
this . description = description ? ? throw new ArgumentNullException ( nameof ( description ) ) ;
this . version = version ;
this . file = file ? ? throw new ArgumentNullException ( nameof ( file ) ) ;
2019-09-29 16:19:57 +02:00
this . local = local ;
2019-09-10 10:02:24 +02:00
this . hash = hash ? ? throw new ArgumentNullException ( nameof ( hash ) ) ;
ID = iD ;
this . color = color ;
this . icon = icon ? ? throw new ArgumentNullException ( nameof ( icon ) ) ;
this . runnable = runnable ;
this . mainFile = mainFile ? ? throw new ArgumentNullException ( nameof ( mainFile ) ) ;
2019-09-09 17:50:33 +02:00
}
2019-09-10 10:02:24 +02:00
public Status status
{
get {
string dir = Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) + @"\UpTool2" ;
string xml = dir + @"\Apps\" + ID . ToString ( ) + @"\info.xml" ;
if ( File . Exists ( xml ) )
{
if ( int . Parse ( XDocument . Load ( xml ) . Element ( "app" ) . Element ( "Version" ) . Value ) < version )
return Status . Updatable ;
else
2019-09-29 16:19:57 +02:00
{
return local ? Status . Installed | Status . Local : Status . Installed ;
}
2019-09-10 10:02:24 +02:00
}
else
return Status . Not_Installed ;
}
}
public override bool Equals ( object obj ) = > obj is App app & & Equals ( app ) ;
public bool Equals ( App other ) = > ID . Equals ( other . ID ) ;
public override int GetHashCode ( ) = > 1213502048 + EqualityComparer < Guid > . Default . GetHashCode ( ID ) ;
public static bool operator = = ( App left , App right ) = > left . Equals ( right ) ;
public static bool operator ! = ( App left , App right ) = > ! ( left = = right ) ;
2019-09-09 17:50:33 +02:00
}
2019-10-20 15:18:21 +02:00
Dictionary < Guid , App > apps = new Dictionary < Guid , App > ( ) ;
enum Status { Not_Installed = 1 , Updatable = 2 , Installed = 4 , Local = 8 , All = 15 }
string dir = > Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) + @"\UpTool2" ;
string appsPath = > dir + @"\Apps" ;
2019-10-20 18:25:00 +02:00
string getAppPath ( App app ) = > getAppPath ( app . ID ) ;
string getDataPath ( App app ) = > getDataPath ( app . ID ) ;
string getAppPath ( Guid app ) = > appsPath + @"\" + app . ToString ( ) ;
string getDataPath ( Guid app ) = > getAppPath ( app ) + @"\app" ;
2019-10-20 15:18:21 +02:00
bool relE = true ;
#endregion
2019-09-08 21:39:04 +02:00
}
}