2020-04-03 03:57:59 +00:00
using System ;
2020-04-05 21:15:01 +00:00
using System.Collections ;
2020-01-26 04:50:17 +00:00
using System.Collections.Generic ;
using System.Data ;
using System.Data.SqlClient ;
using System.Linq ;
using System.Threading.Tasks ;
using Dapper ;
2020-04-13 23:31:48 +00:00
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata ;
2020-01-26 04:50:17 +00:00
using Microsoft.Extensions.Configuration ;
2020-04-02 04:50:23 +00:00
using Newtonsoft.Json ;
2020-01-30 13:07:16 +00:00
using Wabbajack.BuildServer.Model.Models.Results ;
2020-01-26 04:50:17 +00:00
using Wabbajack.BuildServer.Models ;
2020-03-29 02:42:18 +00:00
using Wabbajack.BuildServer.Models.JobQueue ;
2020-04-13 23:31:48 +00:00
using Wabbajack.BuildServer.Models.Jobs ;
2020-01-26 04:50:17 +00:00
using Wabbajack.Common ;
2020-04-05 21:15:01 +00:00
using Wabbajack.Lib ;
2020-03-31 22:05:36 +00:00
using Wabbajack.Lib.Downloaders ;
2020-04-03 03:57:59 +00:00
using Wabbajack.Lib.ModListRegistry ;
2020-04-02 04:50:23 +00:00
using Wabbajack.Lib.NexusApi ;
2020-01-26 04:50:17 +00:00
using Wabbajack.VirtualFileSystem ;
namespace Wabbajack.BuildServer.Model.Models
{
public class SqlService
{
2020-01-29 23:41:53 +00:00
private AppSettings _settings ;
2020-01-26 04:50:17 +00:00
2020-01-29 23:41:53 +00:00
public SqlService ( AppSettings settings )
2020-01-26 04:50:17 +00:00
{
2020-01-29 23:41:53 +00:00
_settings = settings ;
2020-01-26 04:50:17 +00:00
}
2020-04-08 22:28:50 +00:00
public async Task < SqlConnection > Open ( )
2020-01-29 23:41:53 +00:00
{
var conn = new SqlConnection ( _settings . SqlConnection ) ;
await conn . OpenAsync ( ) ;
return conn ;
}
2020-01-26 04:50:17 +00:00
public async Task MergeVirtualFile ( VirtualFile vfile )
{
var files = new List < IndexedFile > ( ) ;
var contents = new List < ArchiveContent > ( ) ;
IngestFile ( vfile , files , contents ) ;
files = files . DistinctBy ( f = > f . Hash ) . ToList ( ) ;
contents = contents . DistinctBy ( c = > ( c . Parent , c . Path ) ) . ToList ( ) ;
2020-01-29 23:41:53 +00:00
await using var conn = await Open ( ) ;
await conn . ExecuteAsync ( "dbo.MergeIndexedFiles" , new { Files = files . ToDataTable ( ) , Contents = contents . ToDataTable ( ) } ,
2020-01-26 04:50:17 +00:00
commandType : CommandType . StoredProcedure ) ;
}
private static void IngestFile ( VirtualFile root , ICollection < IndexedFile > files , ICollection < ArchiveContent > contents )
{
files . Add ( new IndexedFile
{
2020-03-22 15:50:53 +00:00
Hash = ( long ) root . Hash ,
2020-01-26 04:50:17 +00:00
Sha256 = root . ExtendedHashes . SHA256 . FromHex ( ) ,
Sha1 = root . ExtendedHashes . SHA1 . FromHex ( ) ,
Md5 = root . ExtendedHashes . MD5 . FromHex ( ) ,
Crc32 = BitConverter . ToInt32 ( root . ExtendedHashes . CRC . FromHex ( ) ) ,
Size = root . Size
} ) ;
if ( root . Children = = null ) return ;
foreach ( var child in root . Children )
{
IngestFile ( child , files , contents ) ;
contents . Add ( new ArchiveContent
{
2020-03-22 15:50:53 +00:00
Parent = ( long ) root . Hash ,
Child = ( long ) child . Hash ,
2020-03-28 20:42:45 +00:00
Path = ( RelativePath ) child . Name
2020-01-26 04:50:17 +00:00
} ) ;
}
}
2020-03-22 15:50:53 +00:00
public async Task < bool > HaveIndexdFile ( Hash hash )
2020-01-26 04:50:17 +00:00
{
2020-01-29 23:41:53 +00:00
await using var conn = await Open ( ) ;
var row = await conn . QueryAsync ( @"SELECT * FROM IndexedFile WHERE Hash = @Hash" ,
2020-03-22 15:50:53 +00:00
new { Hash = ( long ) hash } ) ;
2020-01-26 04:50:17 +00:00
return row . Any ( ) ;
}
class ArchiveContentsResult
{
public long Parent { get ; set ; }
public long Hash { get ; set ; }
public long Size { get ; set ; }
public string Path { get ; set ; }
}
/// <summary>
/// Get the name, path, hash and size of the file with the provided hash, and all files perhaps
/// contained inside this file. Note: files themselves do not have paths, so the top level result
/// will have a null path
/// </summary>
/// <param name="hash">The xxHash64 of the file to look up</param>
/// <returns></returns>
public async Task < IndexedVirtualFile > AllArchiveContents ( long hash )
{
2020-01-29 23:41:53 +00:00
await using var conn = await Open ( ) ;
var files = await conn . QueryAsync < ArchiveContentsResult > ( @ "
2020-01-30 23:39:14 +00:00
SELECT 0 as Parent , i . Hash , i . Size , null as Path FROM IndexedFile i WHERE Hash = @Hash
2020-01-26 04:50:17 +00:00
UNION ALL
SELECT a . Parent , i . Hash , i . Size , a . Path FROM AllArchiveContent a
LEFT JOIN IndexedFile i ON i . Hash = a . Child
WHERE TopParent = @Hash ",
new { Hash = hash } ) ;
var grouped = files . GroupBy ( f = > f . Parent ) . ToDictionary ( f = > f . Key , f = > ( IEnumerable < ArchiveContentsResult > ) f ) ;
List < IndexedVirtualFile > Build ( long parent )
{
2020-01-30 23:39:14 +00:00
if ( grouped . TryGetValue ( parent , out var children ) )
2020-01-26 04:50:17 +00:00
{
2020-01-30 23:39:14 +00:00
return children . Select ( f = > new IndexedVirtualFile
{
2020-03-28 20:42:45 +00:00
Name = ( RelativePath ) f . Path ,
2020-03-22 15:50:53 +00:00
Hash = Hash . FromLong ( f . Hash ) ,
2020-01-30 23:39:14 +00:00
Size = f . Size ,
Children = Build ( f . Hash )
} ) . ToList ( ) ;
}
return new List < IndexedVirtualFile > ( ) ;
2020-01-26 04:50:17 +00:00
}
2020-01-30 23:53:10 +00:00
return Build ( 0 ) . FirstOrDefault ( ) ;
2020-01-26 04:50:17 +00:00
}
2020-01-30 13:07:16 +00:00
public async Task IngestAllMetrics ( IEnumerable < Metric > allMetrics )
{
await using var conn = await Open ( ) ;
await conn . ExecuteAsync ( @"INSERT INTO dbo.Metrics (Timestamp, Action, Subject, MetricsKey) VALUES (@Timestamp, @Action, @Subject, @MetricsKey)" , allMetrics ) ;
}
public async Task IngestMetric ( Metric metric )
{
await using var conn = await Open ( ) ;
await conn . ExecuteAsync ( @"INSERT INTO dbo.Metrics (Timestamp, Action, Subject, MetricsKey) VALUES (@Timestamp, @Action, @Subject, @MetricsKey)" , metric ) ;
}
public async Task < IEnumerable < AggregateMetric > > MetricsReport ( string action )
{
await using var conn = await Open ( ) ;
return ( await conn . QueryAsync < AggregateMetric > ( @ "
SELECT d . Date , d . GroupingSubject as Subject , Count ( * ) as Count FROM
( select DISTINCT CONVERT ( date , Timestamp ) as Date , GroupingSubject , Action , MetricsKey from dbo . Metrics ) m
RIGHT OUTER JOIN
( SELECT CONVERT ( date , DATEADD ( DAY , number + 1 , dbo . MinMetricDate ( ) ) ) as Date , GroupingSubject , Action
FROM master . . spt_values
CROSS JOIN (
SELECT DISTINCT GroupingSubject , Action FROM dbo . Metrics
WHERE MetricsKey is not null
AND Subject ! = ' Default '
AND TRY_CONVERT ( uniqueidentifier , Subject ) is null ) as keys
WHERE type = 'P'
AND DATEADD ( DAY , number + 1 , dbo . MinMetricDate ( ) ) < dbo . MaxMetricDate ( ) ) as d
ON m . Date = d . Date AND m . GroupingSubject = d . GroupingSubject AND m . Action = d . Action
WHERE d . Action = @action
2020-04-26 05:13:42 +00:00
AND d . Date > = DATEADD ( month , - 1 , GETUTCDATE ( ) )
2020-01-30 13:07:16 +00:00
group by d . Date , d . GroupingSubject , d . Action
ORDER BY d . Date , d . GroupingSubject , d . Action ", new {Action = action}))
. ToList ( ) ;
}
2020-03-29 02:42:18 +00:00
#region JobRoutines
/// <summary>
/// Enqueue a Job into the Job queue to be run at a later time
/// </summary>
/// <param name="job"></param>
/// <returns></returns>
public async Task EnqueueJob ( Job job )
{
await using var conn = await Open ( ) ;
await conn . ExecuteAsync (
2020-04-26 05:13:42 +00:00
@"INSERT INTO dbo.Jobs (Created, Priority, PrimaryKeyString, Payload, OnSuccess) VALUES (GETUTCDATE(), @Priority, @PrimaryKeyString, @Payload, @OnSuccess)" ,
2020-03-29 02:42:18 +00:00
new {
2020-04-03 03:57:59 +00:00
job . Priority ,
2020-04-11 13:59:15 +00:00
PrimaryKeyString = job . Payload . PrimaryKeyString ,
2020-04-06 20:48:54 +00:00
Payload = job . Payload . ToJson ( ) ,
OnSuccess = job . OnSuccess ? . ToJson ( ) ? ? null } ) ;
2020-03-29 02:42:18 +00:00
}
/// <summary>
/// Enqueue a Job into the Job queue to be run at a later time
/// </summary>
/// <param name="job"></param>
/// <returns></returns>
public async Task FinishJob ( Job job )
{
await using var conn = await Open ( ) ;
await conn . ExecuteAsync (
2020-04-26 05:13:42 +00:00
@"UPDATE dbo.Jobs SET Ended = GETUTCDATE(), Success = @Success, ResultContent = @ResultContent WHERE Id = @Id" ,
2020-04-03 03:57:59 +00:00
new {
job . Id ,
2020-03-29 02:42:18 +00:00
Success = job . Result . ResultType = = JobResultType . Success ,
2020-04-15 22:12:06 +00:00
ResultContent = job . Result
2020-03-29 02:42:18 +00:00
} ) ;
if ( job . OnSuccess ! = null )
await EnqueueJob ( job . OnSuccess ) ;
}
2020-04-15 22:12:06 +00:00
2020-03-29 02:42:18 +00:00
/// <summary>
/// Get a Job from the Job queue to run.
/// </summary>
/// <returns></returns>
public async Task < Job > GetJob ( )
{
await using var conn = await Open ( ) ;
2020-04-15 22:12:06 +00:00
var result = await conn . QueryAsync < ( long , DateTime , DateTime , DateTime , AJobPayload , int ) > (
2020-04-26 05:13:42 +00:00
@ "UPDATE jobs SET Started = GETUTCDATE(), RunBy = @RunBy
2020-04-11 13:59:15 +00:00
WHERE ID in ( SELECT TOP ( 1 ) ID FROM Jobs
WHERE Started is NULL
AND PrimaryKeyString NOT IN ( SELECT PrimaryKeyString from jobs WHERE Started IS NOT NULL and Ended IS NULL )
ORDER BY Priority DESC , Created ) ;
2020-04-15 22:12:06 +00:00
SELECT TOP ( 1 ) Id , Started , Ended , Created , Payload , Priority FROM jobs WHERE RunBy = @RunBy ORDER BY Started DESC ",
2020-03-29 02:42:18 +00:00
new { RunBy = Guid . NewGuid ( ) . ToString ( ) } ) ;
2020-04-15 22:12:06 +00:00
return result . Select ( k = >
2020-04-26 05:13:42 +00:00
new Job {
Id = k . Item1 ,
Started = k . Item2 ,
Ended = k . Item3 ,
Created = k . Item4 ,
Payload = k . Item5 ,
Priority = ( Job . JobPriority ) k . Item6
} ) . FirstOrDefault ( ) ;
2020-04-15 22:12:06 +00:00
}
2020-04-05 21:15:01 +00:00
public async Task < IEnumerable < Job > > GetRunningJobs ( )
{
await using var conn = await Open ( ) ;
var results =
2020-04-26 05:13:42 +00:00
await conn . QueryAsync < ( long , DateTime , DateTime , DateTime , AJobPayload , int ) > ( "SELECT Id, Started, Ended, Created, Payload, Priority FROM dbo.Jobs WHERE Started IS NOT NULL AND Ended IS NULL " ) ;
return results . Select ( k = >
new Job {
Id = k . Item1 ,
Started = k . Item2 ,
Ended = k . Item3 ,
Created = k . Item4 ,
Payload = k . Item5 ,
Priority = ( Job . JobPriority ) k . Item6
} ) ;
2020-04-05 21:15:01 +00:00
}
public async Task < IEnumerable < Job > > GetUnfinishedJobs ( )
{
await using var conn = await Open ( ) ;
var results =
2020-04-26 05:13:42 +00:00
await conn . QueryAsync < ( long , DateTime , DateTime , DateTime , AJobPayload , int ) > ( "SELECT Id, Started, Ended, Created, Payload, Priority from dbo.Jobs WHERE Ended IS NULL " ) ;
return results . Select ( k = >
new Job {
Id = k . Item1 ,
Started = k . Item2 ,
Ended = k . Item3 ,
Created = k . Item4 ,
Payload = k . Item5 ,
Priority = ( Job . JobPriority ) k . Item6
} ) ;
2020-04-05 21:15:01 +00:00
}
2020-03-29 02:42:18 +00:00
#endregion
#region TypeMappers
static SqlService ( )
{
2020-03-30 04:48:28 +00:00
SqlMapper . AddTypeHandler ( new HashMapper ( ) ) ;
2020-04-14 13:31:03 +00:00
SqlMapper . AddTypeHandler ( new RelativePathMapper ( ) ) ;
SqlMapper . AddTypeHandler ( new JsonMapper < AbstractDownloadState > ( ) ) ;
SqlMapper . AddTypeHandler ( new JsonMapper < AJobPayload > ( ) ) ;
2020-04-15 22:12:06 +00:00
SqlMapper . AddTypeHandler ( new JsonMapper < JobResult > ( ) ) ;
SqlMapper . AddTypeHandler ( new JsonMapper < Job > ( ) ) ;
2020-03-29 02:42:18 +00:00
}
2020-04-14 13:31:03 +00:00
public class JsonMapper < T > : SqlMapper . TypeHandler < T >
2020-03-29 02:42:18 +00:00
{
2020-04-14 13:31:03 +00:00
public override void SetValue ( IDbDataParameter parameter , T value )
2020-03-29 02:42:18 +00:00
{
2020-04-06 20:48:54 +00:00
parameter . Value = value . ToJson ( ) ;
2020-03-29 02:42:18 +00:00
}
2020-04-14 13:31:03 +00:00
public override T Parse ( object value )
2020-03-29 02:42:18 +00:00
{
2020-04-14 13:31:03 +00:00
return ( ( string ) value ) . FromJsonString < T > ( ) ;
}
}
public class RelativePathMapper : SqlMapper . TypeHandler < RelativePath >
{
public override void SetValue ( IDbDataParameter parameter , RelativePath value )
{
parameter . Value = value . ToJson ( ) ;
}
public override RelativePath Parse ( object value )
{
return ( RelativePath ) ( string ) value ;
2020-03-29 02:42:18 +00:00
}
}
2020-03-30 04:48:28 +00:00
class HashMapper : SqlMapper . TypeHandler < Hash >
{
public override void SetValue ( IDbDataParameter parameter , Hash value )
{
parameter . Value = ( long ) value ;
}
public override Hash Parse ( object value )
{
return Hash . FromLong ( ( long ) value ) ;
}
}
2020-01-30 13:07:16 +00:00
2020-03-29 02:42:18 +00:00
#endregion
2020-03-30 03:47:35 +00:00
public async Task AddUploadedFile ( UploadedFile uf )
{
await using var conn = await Open ( ) ;
await conn . ExecuteAsync (
"INSERT INTO dbo.UploadedFiles (Id, Name, Size, UploadedBy, Hash, UploadDate, CDNName) VALUES " +
"(@Id, @Name, @Size, @UploadedBy, @Hash, @UploadDate, @CDNName)" ,
new
{
Id = uf . Id . ToString ( ) ,
2020-04-03 03:57:59 +00:00
uf . Name ,
uf . Size ,
2020-03-30 03:47:35 +00:00
UploadedBy = uf . Uploader ,
Hash = ( long ) uf . Hash ,
2020-04-03 03:57:59 +00:00
uf . UploadDate ,
uf . CDNName
2020-03-30 03:47:35 +00:00
} ) ;
}
2020-04-05 21:15:01 +00:00
public async Task < UploadedFile > UploadedFileById ( Guid fileId )
{
await using var conn = await Open ( ) ;
return await conn . QueryFirstAsync < UploadedFile > ( "SELECT * FROM dbo.UploadedFiles WHERE Id = @Id" ,
new { Id = fileId . ToString ( ) } ) ;
}
2020-03-30 04:48:28 +00:00
public async Task < IEnumerable < UploadedFile > > AllUploadedFilesForUser ( string user )
{
await using var conn = await Open ( ) ;
return await conn . QueryAsync < UploadedFile > ( "SELECT * FROM dbo.UploadedFiles WHERE UploadedBy = @uploadedBy" ,
new { UploadedBy = user } ) ;
}
2020-04-05 21:15:01 +00:00
public async Task < IEnumerable < UploadedFile > > AllUploadedFiles ( )
{
await using var conn = await Open ( ) ;
2020-04-26 05:13:42 +00:00
return await conn . QueryAsync < UploadedFile > ( "SELECT Id, Name, Size, UploadedBy as Uploader, Hash, UploadDate, CDNName FROM dbo.UploadedFiles ORDER BY UploadDate DESC" ) ;
2020-04-05 21:15:01 +00:00
}
public async Task DeleteUploadedFile ( Guid dupId )
{
await using var conn = await Open ( ) ;
await conn . ExecuteAsync ( "SELECT * FROM dbo.UploadedFiles WHERE Id = @id" ,
new
{
Id = dupId . ToString ( )
} ) ;
}
2020-03-31 22:05:36 +00:00
public async Task AddDownloadState ( Hash hash , AbstractDownloadState state )
{
await using var conn = await Open ( ) ;
await conn . ExecuteAsync ( "INSERT INTO dbo.DownloadStates (Id, Hash, PrimaryKey, IniState, JsonState) " +
"VALUES (@Id, @Hash, @PrimaryKey, @IniState, @JsonState)" ,
new
{
Id = state . PrimaryKeyString . StringSha256Hex ( ) . FromHex ( ) ,
Hash = hash ,
PrimaryKey = state . PrimaryKeyString ,
IniState = string . Join ( "\n" , state . GetMetaIni ( ) ) ,
2020-04-06 20:48:54 +00:00
JsonState = state . ToJson ( )
2020-03-31 22:05:36 +00:00
} ) ;
}
public async Task < string > GetIniForHash ( Hash id )
{
await using var conn = await Open ( ) ;
var results = await conn . QueryAsync < string > ( "SELECT IniState FROM dbo.DownloadStates WHERE Hash = @Hash" ,
new {
Hash = id
} ) ;
return results . FirstOrDefault ( ) ;
}
public async Task < bool > HaveIndexedArchivePrimaryKey ( string key )
{
await using var conn = await Open ( ) ;
2020-04-03 03:57:59 +00:00
var results = await conn . QueryFirstOrDefaultAsync < string > (
"SELECT PrimaryKey FROM dbo.DownloadStates WHERE PrimaryKey = @PrimaryKey" ,
2020-03-31 22:05:36 +00:00
new { PrimaryKey = key } ) ;
2020-04-03 03:57:59 +00:00
return results ! = null ;
}
public async Task AddNexusFileInfo ( Game game , long modId , long fileId , DateTime lastCheckedUtc , NexusFileInfo data )
{
await using var conn = await Open ( ) ;
await conn . ExecuteAsync ( "INSERT INTO dbo.NexusFileInfos (Game, ModId, FileId, LastChecked, Data) VALUES " +
"(@Game, @ModId, @FileId, @LastChecked, @Data)" ,
new
{
Game = game . MetaData ( ) . NexusGameId ,
ModId = modId ,
FileId = fileId ,
LastChecked = lastCheckedUtc ,
Data = JsonConvert . SerializeObject ( data )
} ) ;
}
public async Task AddNexusModInfo ( Game game , long modId , DateTime lastCheckedUtc , ModInfo data )
{
await using var conn = await Open ( ) ;
await conn . ExecuteAsync (
@ "MERGE dbo.NexusModInfos AS Target
USING ( SELECT @Game Game , @ModId ModId , @LastChecked LastChecked , @Data Data ) AS Source
ON Target . Game = Source . Game AND Target . ModId = Source . ModId
WHEN MATCHED THEN UPDATE SET Target . Data = @Data , Target . LastChecked = @LastChecked
WHEN NOT MATCHED THEN INSERT ( Game , ModId , LastChecked , Data ) VALUES ( @Game , @ModId , @LastChecked , @Data ) ; ",
new
{
Game = game . MetaData ( ) . NexusGameId ,
ModId = modId ,
LastChecked = lastCheckedUtc ,
Data = JsonConvert . SerializeObject ( data )
} ) ;
}
public async Task AddNexusModFiles ( Game game , long modId , DateTime lastCheckedUtc , NexusApiClient . GetModFilesResponse data )
{
await using var conn = await Open ( ) ;
await conn . ExecuteAsync (
@ "MERGE dbo.NexusModFiles AS Target
USING ( SELECT @Game Game , @ModId ModId , @LastChecked LastChecked , @Data Data ) AS Source
ON Target . Game = Source . Game AND Target . ModId = Source . ModId
WHEN MATCHED THEN UPDATE SET Target . Data = @Data , Target . LastChecked = @LastChecked
WHEN NOT MATCHED THEN INSERT ( Game , ModId , LastChecked , Data ) VALUES ( @Game , @ModId , @LastChecked , @Data ) ; ",
new
{
Game = game . MetaData ( ) . NexusGameId ,
ModId = modId ,
LastChecked = lastCheckedUtc ,
Data = JsonConvert . SerializeObject ( data )
} ) ;
}
public async Task < ModInfo > GetNexusModInfoString ( Game game , long modId )
{
await using var conn = await Open ( ) ;
var result = await conn . QueryFirstOrDefaultAsync < string > (
"SELECT Data FROM dbo.NexusModInfos WHERE Game = @Game AND @ModId = ModId" ,
new { Game = game . MetaData ( ) . NexusGameId , ModId = modId } ) ;
return result = = null ? null : JsonConvert . DeserializeObject < ModInfo > ( result ) ;
}
public async Task < NexusApiClient . GetModFilesResponse > GetModFiles ( Game game , long modId )
{
await using var conn = await Open ( ) ;
var result = await conn . QueryFirstOrDefaultAsync < string > (
"SELECT Data FROM dbo.NexusModFiles WHERE Game = @Game AND @ModId = ModId" ,
new { Game = game . MetaData ( ) . NexusGameId , ModId = modId } ) ;
return result = = null ? null : JsonConvert . DeserializeObject < NexusApiClient . GetModFilesResponse > ( result ) ;
}
#region ModLists
2020-04-08 04:19:36 +00:00
public async Task < IEnumerable < ModListSummary > > GetModListSummaries ( )
2020-04-03 03:57:59 +00:00
{
await using var conn = await Open ( ) ;
var results = await conn . QueryAsync < string > ( "SELECT Summary from dbo.ModLists" ) ;
2020-04-08 04:19:36 +00:00
return results . Select ( s = > s . FromJsonString < ModListSummary > ( ) ) . ToList ( ) ;
2020-04-03 03:57:59 +00:00
}
public async Task < DetailedStatus > GetDetailedModlistStatus ( string machineUrl )
{
await using var conn = await Open ( ) ;
var result = await conn . QueryFirstOrDefaultAsync < string > ( "SELECT DetailedStatus from dbo.ModLists WHERE MachineURL = @MachineURL" ,
new
{
machineUrl
} ) ;
2020-04-06 20:48:54 +00:00
return result . FromJsonString < DetailedStatus > ( ) ;
2020-04-03 03:57:59 +00:00
}
public async Task < List < DetailedStatus > > GetDetailedModlistStatuses ( )
{
await using var conn = await Open ( ) ;
var results = await conn . QueryAsync < string > ( "SELECT DetailedStatus from dbo.ModLists" ) ;
2020-04-06 20:48:54 +00:00
return results . Select ( s = > s . FromJsonString < DetailedStatus > ( ) ) . ToList ( ) ;
2020-03-31 22:05:36 +00:00
}
2020-04-03 03:57:59 +00:00
#endregion
2020-04-05 21:15:01 +00:00
#region Logins
public async Task < string > AddLogin ( string name )
{
var key = NewAPIKey ( ) ;
await using var conn = await Open ( ) ;
await conn . ExecuteAsync ( "INSERT INTO dbo.ApiKeys (Owner, ApiKey) VALUES (@Owner, @ApiKey)" ,
new { Owner = name , ApiKey = key } ) ;
return key ;
}
public static string NewAPIKey ( )
{
var arr = new byte [ 128 ] ;
new Random ( ) . NextBytes ( arr ) ;
return arr . ToHex ( ) ;
}
public async Task < string > LoginByAPIKey ( string key )
{
await using var conn = await Open ( ) ;
var result = await conn . QueryAsync < string > ( @"SELECT Owner as Id FROM dbo.ApiKeys WHERE ApiKey = @ApiKey" ,
new { ApiKey = key } ) ;
return result . FirstOrDefault ( ) ;
}
public async Task < IEnumerable < ( string Owner , string Key ) > > GetAllUserKeys ( )
{
await using var conn = await Open ( ) ;
var result = await conn . QueryAsync < ( string Owner , string Key ) > ( "SELECT Owner, ApiKey FROM dbo.ApiKeys" ) ;
return result ;
}
#endregion
#region Auto - healing routines
public async Task < Archive > GetNexusStateByHash ( Hash startingHash )
{
await using var conn = await Open ( ) ;
2020-04-15 21:01:01 +00:00
var result = await conn . QueryFirstOrDefaultAsync < string > ( @ "SELECT JsonState FROM dbo.DownloadStates
2020-04-10 03:54:02 +00:00
WHERE Hash = @hash AND PrimaryKey like ' NexusDownloader + State | % ' ",
new { Hash = ( long ) startingHash } ) ;
2020-04-10 01:29:53 +00:00
return result = = null ? null : new Archive ( result . FromJsonString < AbstractDownloadState > ( ) )
2020-04-05 21:15:01 +00:00
{
Hash = startingHash
} ;
}
2020-04-15 03:25:00 +00:00
public async Task < Archive > GetStateByHash ( Hash startingHash )
{
await using var conn = await Open ( ) ;
var result = await conn . QueryFirstOrDefaultAsync < ( string , long ) > ( @ "SELECT JsonState, indexed.Size FROM dbo.DownloadStates state
LEFT JOIN dbo . IndexedFile indexed ON indexed . Hash = state . Hash
WHERE state . Hash = @hash ",
new { Hash = ( long ) startingHash } ) ;
2020-04-15 12:05:05 +00:00
return result = = default ? null : new Archive ( result . Item1 . FromJsonString < AbstractDownloadState > ( ) )
2020-04-15 03:25:00 +00:00
{
Hash = startingHash ,
Size = result . Item2
} ;
}
2020-04-05 21:15:01 +00:00
public async Task < Archive > DownloadStateByPrimaryKey ( string primaryKey )
{
await using var conn = await Open ( ) ;
var result = await conn . QueryFirstOrDefaultAsync < ( long Hash , string State ) > ( @"SELECT Hash, JsonState FROM dbo.DownloadStates WHERE PrimaryKey = @PrimaryKey" ,
new { PrimaryKey = primaryKey } ) ;
2020-04-10 01:29:53 +00:00
return result = = default ? null : new Archive ( result . State . FromJsonString < AbstractDownloadState > ( ) )
2020-04-05 21:15:01 +00:00
{
Hash = Hash . FromLong ( result . Hash )
} ;
}
#endregion
/// <summary>
/// Returns a hashset the only contains hashes from the input that do not exist in IndexedArchives
/// </summary>
/// <param name="searching"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task < HashSet < Hash > > FilterByExistingIndexedArchives ( HashSet < Hash > searching )
{
await using var conn = await Open ( ) ;
var found = await conn . QueryAsync < long > ( "SELECT Hash from dbo.IndexedFile WHERE Hash in @Hashes" ,
new { Hashes = searching . Select ( h = > ( long ) h ) } ) ;
return searching . Except ( found . Select ( h = > Hash . FromLong ( h ) ) . ToHashSet ( ) ) . ToHashSet ( ) ;
}
/// <summary>
/// Returns a hashset the only contains primary keys from the input that do not exist in IndexedArchives
/// </summary>
/// <param name="searching"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task < HashSet < string > > FilterByExistingPrimaryKeys ( HashSet < string > pks )
{
await using var conn = await Open ( ) ;
var found = await conn . QueryAsync < string > ( "SELECT Hash from dbo.IndexedFile WHERE PrimaryKey in @PrimaryKeys" ,
new { PrimaryKeys = pks . ToList ( ) } ) ;
return pks . Except ( found . ToHashSet ( ) ) . ToHashSet ( ) ;
}
public async Task < long > DeleteNexusModInfosUpdatedBeforeDate ( Game game , long modId , DateTime date )
{
await using var conn = await Open ( ) ;
var deleted = await conn . ExecuteScalarAsync < long > (
2020-04-11 03:16:10 +00:00
@ "DELETE FROM dbo.NexusModInfos WHERE Game = @Game AND ModID = @ModId AND LastChecked < @Date
2020-04-05 21:15:01 +00:00
SELECT @ @ROWCOUNT AS Deleted ",
new { Game = game . MetaData ( ) . NexusGameId , ModId = modId , @Date = date } ) ;
return deleted ;
}
public async Task < long > DeleteNexusModFilesUpdatedBeforeDate ( Game game , long modId , DateTime date )
{
await using var conn = await Open ( ) ;
var deleted = await conn . ExecuteScalarAsync < long > (
2020-04-11 03:16:10 +00:00
@ "DELETE FROM dbo.NexusModFiles WHERE Game = @Game AND ModID = @ModId AND LastChecked < @Date
2020-04-05 21:15:01 +00:00
SELECT @ @ROWCOUNT AS Deleted ",
2020-04-11 03:16:10 +00:00
new { Game = game . MetaData ( ) . NexusGameId , ModId = modId , Date = date } ) ;
2020-04-05 21:15:01 +00:00
return deleted ;
}
public async Task UpdateModListStatus ( ModListStatus dto )
2020-04-12 04:18:21 +00:00
{
}
public async Task IngestModList ( Hash hash , ModlistMetadata metadata , ModList modlist )
2020-04-05 21:15:01 +00:00
{
await using var conn = await Open ( ) ;
2020-04-12 04:18:21 +00:00
await using var tran = await conn . BeginTransactionAsync ( ) ;
await conn . ExecuteAsync ( @"DELETE FROM dbo.ModLists Where MachineUrl = @MachineUrl" ,
new { MachineUrl = metadata . Links . MachineURL } , tran ) ;
await conn . ExecuteAsync (
@"INSERT INTO dbo.ModLists (MachineUrl, Hash, Metadata, ModList) VALUES (@MachineUrl, @Hash, @Metadata, @ModList)" ,
2020-04-05 21:15:01 +00:00
new
{
2020-04-12 04:18:21 +00:00
MachineUrl = metadata . Links . MachineURL ,
Hash = hash ,
MetaData = metadata . ToJson ( ) ,
ModList = modlist . ToJson ( )
} , tran ) ;
var entries = modlist . Archives . Select ( a = >
new
{
MachineUrl = metadata . Links . MachineURL ,
Hash = a . Hash ,
Size = a . Size ,
State = a . State . ToJson ( ) ,
PrimaryKeyString = a . State . PrimaryKeyString
} ) . ToArray ( ) ;
await conn . ExecuteAsync ( @"DELETE FROM dbo.ModListArchives WHERE MachineURL = @machineURL" ,
new { MachineUrl = metadata . Links . MachineURL } , tran ) ;
foreach ( var entry in entries )
{
await conn . ExecuteAsync (
"INSERT INTO dbo.ModListArchives (MachineURL, Hash, Size, PrimaryKeyString, State) VALUES (@MachineURL, @Hash, @Size, @PrimaryKeyString, @State)" ,
entry , tran ) ;
}
await tran . CommitAsync ( ) ;
2020-04-05 21:15:01 +00:00
}
2020-04-12 04:18:21 +00:00
public async Task < bool > HaveIndexedModlist ( string machineUrl , Hash hash )
{
await using var conn = await Open ( ) ;
var result = await conn . QueryFirstOrDefaultAsync < string > (
"SELECT MachineURL from dbo.Modlists WHERE MachineURL = @MachineUrl AND Hash = @Hash" ,
new { MachineUrl = machineUrl , Hash = hash } ) ;
return result ! = null ;
}
public async Task < List < Archive > > GetNonNexusModlistArchives ( )
{
await using var conn = await Open ( ) ;
var results = await conn . QueryAsync < ( Hash Hash , long Size , string State ) > (
@"SELECT Hash, Size, State FROM dbo.ModListArchives WHERE PrimaryKeyString NOT LIKE 'NexusDownloader+State|%'" ) ;
2020-04-15 12:05:05 +00:00
return results . Select ( r = > new Archive ( r . State . FromJsonString < AbstractDownloadState > ( ) )
{
2020-04-12 04:18:21 +00:00
Size = r . Size ,
Hash = r . Hash ,
2020-04-15 12:05:05 +00:00
2020-04-12 04:18:21 +00:00
} ) . ToList ( ) ; }
public async Task UpdateNonNexusModlistArchivesStatus ( IEnumerable < ( Archive Archive , bool IsValid ) > results )
{
await using var conn = await Open ( ) ;
var trans = await conn . BeginTransactionAsync ( ) ;
await conn . ExecuteAsync ( "DELETE FROM dbo.ModlistArchiveStatus;" , transaction : trans ) ;
foreach ( var itm in results . DistinctBy ( itm = > ( itm . Archive . Hash , itm . Archive . State . PrimaryKeyString ) ) )
{
await conn . ExecuteAsync (
@ "INSERT INTO dbo.ModlistArchiveStatus (PrimaryKeyStringHash, PrimaryKeyString, Hash, IsValid)
VALUES ( HASHBYTES ( ' SHA2_256 ' , @PrimaryKeyString ) , @PrimaryKeyString , @Hash , @IsValid ) ", new
{
PrimaryKeyString = itm . Archive . State . PrimaryKeyString ,
Hash = itm . Archive . Hash ,
IsValid = itm . IsValid
} , trans ) ;
}
await trans . CommitAsync ( ) ;
}
2020-04-13 23:31:48 +00:00
public async Task < ValidationData > GetValidationData ( )
{
var nexusFiles = AllNexusFiles ( ) ;
var archiveStatus = AllModListArchivesStatus ( ) ;
var modLists = AllModLists ( ) ;
2020-04-14 13:31:03 +00:00
var archivePatches = AllArchivePatches ( ) ;
2020-04-13 23:31:48 +00:00
return new ValidationData
{
NexusFiles = await nexusFiles ,
ArchiveStatus = await archiveStatus ,
2020-04-14 13:31:03 +00:00
ModLists = await modLists ,
ArchivePatches = await archivePatches
2020-04-13 23:31:48 +00:00
} ;
}
public async Task < Dictionary < ( string PrimaryKeyString , Hash Hash ) , bool > > AllModListArchivesStatus ( )
{
await using var conn = await Open ( ) ;
var results =
await conn . QueryAsync < ( string , Hash , bool ) > (
@"SELECT PrimaryKeyString, Hash, IsValid FROM dbo.ModListArchiveStatus" ) ;
return results . ToDictionary ( v = > ( v . Item1 , v . Item2 ) , v = > v . Item3 ) ;
}
public async Task < HashSet < ( long NexusGameId , long ModId , long FileId ) > > AllNexusFiles ( )
{
await using var conn = await Open ( ) ;
var results = await conn . QueryAsync < ( long , long , long ) > ( @ "SELECT Game, ModId, p.file_id
FROM [ NexusModFiles ] files
CROSS APPLY
OPENJSON ( Data , ' $ . files ' ) WITH ( file_id bigint ' $ . file_id ' , category varchar ( max ) ' $ . category_name ' ) p
WHERE p . category is not null ");
return results . ToHashSet ( ) ;
}
public async Task < List < ( ModlistMetadata , ModList ) > > AllModLists ( )
{
await using var conn = await Open ( ) ;
var results = await conn . QueryAsync < ( string , string ) > ( @"SELECT Metadata, ModList FROM dbo.ModLists" ) ;
return results . Select ( m = > ( m . Item1 . FromJsonString < ModlistMetadata > ( ) , m . Item2 . FromJsonString < ModList > ( ) ) ) . ToList ( ) ;
}
public class ValidationData
{
public HashSet < ( long Game , long ModId , long FileId ) > NexusFiles { get ; set ; }
public Dictionary < ( string PrimaryKeyString , Hash Hash ) , bool > ArchiveStatus { get ; set ; }
public List < ( ModlistMetadata Metadata , ModList ModList ) > ModLists { get ; set ; }
2020-04-14 13:31:03 +00:00
public List < ArchivePatch > ArchivePatches { get ; set ; }
2020-04-13 23:31:48 +00:00
}
2020-04-14 13:31:03 +00:00
#region ArchivePatches
public class ArchivePatch
{
public Hash SrcHash { get ; set ; }
public AbstractDownloadState SrcState { get ; set ; }
public Hash DestHash { get ; set ; }
public AbstractDownloadState DestState { get ; set ; }
public RelativePath DestDownload { get ; set ; }
public RelativePath SrcDownload { get ; set ; }
public Uri CDNPath { get ; set ; }
}
public async Task UpsertArchivePatch ( ArchivePatch patch )
{
await using var conn = await Open ( ) ;
await using var trans = conn . BeginTransaction ( ) ;
await conn . ExecuteAsync ( @ "DELETE FROM dbo.ArchivePatches
WHERE SrcHash = @SrcHash
AND DestHash = @DestHash
AND SrcPrimaryKeyStringHash = HASHBYTES ( ' SHA2 - 256 ' , @SrcPrimaryKeyString )
AND DestPrimaryKeyStringHash = HASHBYTES ( ' SHA2 - 256 ' , @DestPrimaryKeyString ) ",
new
{
SrcHash = patch . SrcHash ,
DestHash = patch . DestHash ,
SrcPrimaryKeyString = patch . SrcState . PrimaryKeyString ,
DestPrimaryKeyString = patch . DestState . PrimaryKeyString
} , trans ) ;
await conn . ExecuteAsync ( @ "INSERT INTO dbo.ArchivePatches
( SrcHash , SrcPrimaryKeyString , SrcPrimaryKeyStringHash , SrcState ,
DestHash , DestPrimaryKeyString , DestPrimaryKeyStringHash , DestState ,
SrcDownload , DestDownload , CDNPath )
VALUES ( @SrcHash , @SrcPrimaryKeyString , HASHBYTES ( ' SHA2 - 256 ' , @SrcPrimaryKeyString ) , @SrcState ,
@DestHash , @DestPrimaryKeyString , HASHBYTES ( ' SHA2 - 256 ' , @DestPrimaryKeyString ) , @DestState ,
@SrcDownload , @DestDownload , @CDNPAth ) ",
new
{
SrcHash = patch . SrcHash ,
DestHash = patch . DestHash ,
SrcPrimaryKeyString = patch . SrcState . PrimaryKeyString ,
DestPrimaryKeyString = patch . DestState . PrimaryKeyString ,
SrcState = patch . SrcState . ToJson ( ) ,
DestState = patch . DestState . ToString ( ) ,
DestDownload = patch . DestDownload ,
SrcDownload = patch . SrcDownload ,
CDNPath = patch . CDNPath
} , trans ) ;
await trans . CommitAsync ( ) ;
}
public async Task < List < ArchivePatch > > AllArchivePatches ( )
{
await using var conn = await Open ( ) ;
var results =
await conn . QueryAsync < ( Hash , AbstractDownloadState , Hash , AbstractDownloadState , RelativePath , RelativePath , Uri ) > (
@"SELECT SrcHash, SrcState, DestHash, DestState, SrcDownload, DestDownload, CDNPath FROM dbo.ArchivePatches" ) ;
return results . Select ( a = > new ArchivePatch
{
SrcHash = a . Item1 ,
SrcState = a . Item2 ,
DestHash = a . Item3 ,
DestState = a . Item4 ,
SrcDownload = a . Item5 ,
DestDownload = a . Item6 ,
CDNPath = a . Item7
} ) . ToList ( ) ;
}
#endregion
2020-04-26 05:13:42 +00:00
public async Task < IEnumerable < Job > > GetAllJobs ( TimeSpan from )
{
await using var conn = await Open ( ) ;
var results =
await conn . QueryAsync < ( long , DateTime , DateTime , DateTime , AJobPayload , int ) > ( "SELECT Id, Started, Ended, Created, Payload, Priority from dbo.Jobs WHERE Created >= @FromTime " ,
new { FromTime = DateTime . UtcNow - from } ) ;
return results . Select ( k = >
new Job {
Id = k . Item1 ,
Started = k . Item2 ,
Ended = k . Item3 ,
Created = k . Item4 ,
Payload = k . Item5 ,
Priority = ( Job . JobPriority ) k . Item6
} ) ;
}
2020-01-26 04:50:17 +00:00
}
}