feat: Add chain jobs and complete downloader job
This commit is contained in:
parent
be89bddf1b
commit
8fbe439ed4
@ -1,7 +1,10 @@
|
|||||||
|
using AutoScan.Jobs;
|
||||||
|
using AutoScan.Listener;
|
||||||
using AutoScan.Options;
|
using AutoScan.Options;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
using Quartz.Impl.Matchers;
|
||||||
|
|
||||||
namespace AutoScan;
|
namespace AutoScan;
|
||||||
|
|
||||||
@ -10,12 +13,14 @@ public class AutoScanApp
|
|||||||
private readonly AutoScanOptions _options;
|
private readonly AutoScanOptions _options;
|
||||||
private readonly ILogger<AutoScanApp> _logger;
|
private readonly ILogger<AutoScanApp> _logger;
|
||||||
private readonly IScheduler _scheduler;
|
private readonly IScheduler _scheduler;
|
||||||
|
private readonly IChainerListenerFactory _chainerListenerFactory;
|
||||||
|
|
||||||
public AutoScanApp(IOptions<AutoScanOptions> options, ILogger<AutoScanApp> logger, IScheduler scheduler)
|
public AutoScanApp(IOptions<AutoScanOptions> options, ILogger<AutoScanApp> logger, IScheduler scheduler, IChainerListenerFactory chainerListenerFactory)
|
||||||
{
|
{
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_scheduler = scheduler;
|
_scheduler = scheduler;
|
||||||
|
_chainerListenerFactory = chainerListenerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Run(CancellationToken cancellationToken)
|
public async Task Run(CancellationToken cancellationToken)
|
||||||
@ -24,25 +29,41 @@ public class AutoScanApp
|
|||||||
|
|
||||||
var at = DateTime.Now.AddMinutes(1).ToString("HH:mm");
|
var at = DateTime.Now.AddMinutes(1).ToString("HH:mm");
|
||||||
var cron = CronFromAt(at);
|
var cron = CronFromAt(at);
|
||||||
|
//var cron = CronFromAt(_options.At);
|
||||||
_logger.LogInformation("Waiting for next scan at {At} [{cron}].", at, cron);
|
_logger.LogInformation("Waiting for next scan at {At} [{cron}].", at, cron);
|
||||||
|
|
||||||
await _scheduler.Start(cancellationToken);
|
await _scheduler.Start(cancellationToken);
|
||||||
_logger.LogInformation("Scheduler started successfully!");
|
_logger.LogDebug("Scheduler started successfully!");
|
||||||
|
|
||||||
// define the job and tie it to our HelloJob class
|
const string group = "ScanGroup";
|
||||||
IJobDetail job = JobBuilder.Create<ScanJob>()
|
|
||||||
.WithIdentity("job1", "group1")
|
IJobDetail downloaderJob = JobBuilder.Create<DownloaderJob>()
|
||||||
|
.WithIdentity("downloader", group)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
IJobDetail scannerJob = JobBuilder.Create<ScannerJob>()
|
||||||
|
.WithIdentity("scanner", group)
|
||||||
|
.StoreDurably(true)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
ITrigger trigger = TriggerBuilder.Create()
|
ITrigger trigger = TriggerBuilder.Create()
|
||||||
.WithIdentity("trigger1", "group1")
|
.WithIdentity("trigger1", group)
|
||||||
.WithCronSchedule(cron)
|
.WithCronSchedule(cron)
|
||||||
|
.StartNow()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
await _scheduler.ScheduleJob(job, trigger, cancellationToken);
|
var chainer = _chainerListenerFactory.CreateChainerListener("Scan Chainer");
|
||||||
_logger.LogInformation("Scheduled job successfully!");
|
chainer.AddJobChainLink(downloaderJob.Key, scannerJob.Key);
|
||||||
|
|
||||||
|
_scheduler.ListenerManager.AddJobListener(chainer, GroupMatcher<JobKey>.GroupEquals(group));
|
||||||
|
|
||||||
|
await _scheduler.ScheduleJob(downloaderJob, trigger, cancellationToken);
|
||||||
|
await _scheduler.AddJob(scannerJob, false, true, cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogDebug("Scheduled job successfully!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private string CronFromAt(string at)
|
private string CronFromAt(string at)
|
||||||
{
|
{
|
||||||
var parts = at.Split(':');
|
var parts = at.Split(':');
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using AutoScan.Listener;
|
||||||
using CasaBotApp;
|
using CasaBotApp;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
@ -16,6 +17,7 @@ public static class DependencyInjectionExtensions
|
|||||||
{
|
{
|
||||||
q.UseMicrosoftDependencyInjectionJobFactory();
|
q.UseMicrosoftDependencyInjectionJobFactory();
|
||||||
});
|
});
|
||||||
|
services.AddTransient<IChainerListenerFactory, ChainerListenerFactory>();
|
||||||
|
|
||||||
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
||||||
|
|
||||||
|
109
src/CasaBot/AutoScan/Jobs/DownloaderJob.cs
Normal file
109
src/CasaBot/AutoScan/Jobs/DownloaderJob.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
using AutoScan.Options;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Quartz;
|
||||||
|
|
||||||
|
namespace AutoScan.Jobs;
|
||||||
|
|
||||||
|
public class DownloaderJob : IJob
|
||||||
|
{
|
||||||
|
private readonly ILogger<DownloaderJob> _logger;
|
||||||
|
private readonly AutoScanOptions _options;
|
||||||
|
private readonly ShinobiConnector _shinobiConnector;
|
||||||
|
|
||||||
|
public DownloaderJob(ILogger<DownloaderJob> logger, IOptionsSnapshot<AutoScanOptions> options, ShinobiConnector shinobiConnector)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_options = options.Value;
|
||||||
|
_shinobiConnector = shinobiConnector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Execute(IJobExecutionContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Scheduled scan executed at {At}", DateTime.Now);
|
||||||
|
|
||||||
|
if (_options.MediaFolder is null)
|
||||||
|
{
|
||||||
|
_logger.LogError("MediaFolder is not set in options!");
|
||||||
|
context.Result = new JobResult()
|
||||||
|
{
|
||||||
|
Status = JobResultStatus.JobFailed,
|
||||||
|
Result = "MediaFolder is not set in options!"
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//create from variable with the datetime of last night with the variables in options.From (23:00) and options.FromDayBefore (true) [yesterday]
|
||||||
|
//for example, if options.From is 23:00 and options.FromDayBefore is true, from should be yesterday at 23:00
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var minutes = _options.From.Split(":")[1];
|
||||||
|
var hours = _options.From.Split(":")[0];
|
||||||
|
var from = new DateTime(now.Year, now.Month, now.Day, int.Parse(hours), int.Parse(minutes), 0);
|
||||||
|
if(_options.FromDayBefore)
|
||||||
|
from = from.AddDays(-1);
|
||||||
|
|
||||||
|
//create to variable with the datetime of last night with the variables in options.To (1:00) [today]
|
||||||
|
//for example, if options.To is 1:00, to should be today at 1:00
|
||||||
|
minutes = _options.To.Split(":")[1];
|
||||||
|
hours = _options.To.Split(":")[0];
|
||||||
|
var to = new DateTime(now.Year, now.Month, now.Day, int.Parse(hours), int.Parse(minutes), 0);
|
||||||
|
|
||||||
|
_logger.LogInformation("Fetching videos from {From} to {To}", from, to);
|
||||||
|
var videos = await _shinobiConnector.FetchMonitorVideosBetween(from, to);
|
||||||
|
|
||||||
|
//if the amount of videos is greater than the max amount in options, log a warning
|
||||||
|
if (_options.MaxAmount > 0 && videos.Count > _options.MaxAmount)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Amount of videos fetched is greater than the max amount in options ({MaxAmount})", _options.MaxAmount);
|
||||||
|
videos = videos.Take(_options.MaxAmount).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
CleanMediaFolder();
|
||||||
|
|
||||||
|
//download each video to the media folder
|
||||||
|
foreach (var video in videos)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Downloading video {Filename}", video.filename);
|
||||||
|
await _shinobiConnector.DownloadMonitorVideo(video, _options.MediaFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Result = new JobResult()
|
||||||
|
{
|
||||||
|
Status = JobResultStatus.JobSucceeded,
|
||||||
|
Result = $"Downloaded {videos.Count} videos to {_options.MediaFolder}"
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanMediaFolder()
|
||||||
|
{
|
||||||
|
if (_options.MediaFolder is not null)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(_options.MediaFolder)!);
|
||||||
|
foreach (var file in Directory.GetFiles(_options.MediaFolder))
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_options.Scanner?.DetectionFolder is not null)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(_options.Scanner.DetectionFolder)!);
|
||||||
|
foreach (var file in Directory.GetFiles(_options.Scanner.DetectionFolder))
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_options.Screenshot?.Folder is not null)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(_options.Screenshot.Folder)!);
|
||||||
|
foreach (var file in Directory.GetFiles(_options.Screenshot.Folder))
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
13
src/CasaBot/AutoScan/Jobs/JobResult.cs
Normal file
13
src/CasaBot/AutoScan/Jobs/JobResult.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace AutoScan.Jobs;
|
||||||
|
|
||||||
|
public class JobResult
|
||||||
|
{
|
||||||
|
public JobResultStatus Status { get; set; }
|
||||||
|
public object? Result { get; set; }
|
||||||
|
}
|
||||||
|
public enum JobResultStatus
|
||||||
|
{
|
||||||
|
JobFailed,
|
||||||
|
JobSucceeded
|
||||||
|
}
|
||||||
|
|
18
src/CasaBot/AutoScan/Jobs/ScannerJob.cs
Normal file
18
src/CasaBot/AutoScan/Jobs/ScannerJob.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Quartz;
|
||||||
|
|
||||||
|
namespace AutoScan.Jobs;
|
||||||
|
|
||||||
|
public class ScannerJob : IJob
|
||||||
|
{
|
||||||
|
private readonly ILogger<ScannerJob> _logger;
|
||||||
|
public ScannerJob(ILogger<ScannerJob> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
public Task Execute(IJobExecutionContext context)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("ScannerJob is not implemented yet!");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
74
src/CasaBot/AutoScan/Listener/ChainerListener.cs
Normal file
74
src/CasaBot/AutoScan/Listener/ChainerListener.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
using AutoScan.Jobs;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Quartz;
|
||||||
|
using Quartz.Listener;
|
||||||
|
|
||||||
|
namespace AutoScan.Listener;
|
||||||
|
|
||||||
|
public class ChainerListener : JobListenerSupport
|
||||||
|
{
|
||||||
|
private readonly ILogger<ChainerListener> _logger;
|
||||||
|
private readonly Dictionary<JobKey, JobKey> _chainLinks;
|
||||||
|
|
||||||
|
public ChainerListener(string name, ILogger<ChainerListener> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
Name = name ?? throw new ArgumentException("Listener name cannot be null!");
|
||||||
|
_chainLinks = new Dictionary<JobKey, JobKey>();
|
||||||
|
}
|
||||||
|
public override string Name { get; }
|
||||||
|
|
||||||
|
public void AddJobChainLink(JobKey firstJob, JobKey secondJob)
|
||||||
|
{
|
||||||
|
if (firstJob == null || secondJob == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Key cannot be null!");
|
||||||
|
}
|
||||||
|
if (firstJob.Name == null || secondJob.Name == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Key cannot have a null name!");
|
||||||
|
}
|
||||||
|
|
||||||
|
_chainLinks.Add(firstJob, secondJob);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(context.Result is JobResult { Status: JobResultStatus.JobFailed })
|
||||||
|
{
|
||||||
|
_logger.LogError("There was an error in job {JobKey}. Chain will not continue", context.JobDetail.Key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jobException is not null)
|
||||||
|
{
|
||||||
|
_logger.LogError(jobException, "There was an error in job {JobKey}. Chain will not continue", context.JobDetail.Key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_chainLinks.TryGetValue(context.JobDetail.Key, out var sj);
|
||||||
|
|
||||||
|
if (sj == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Job '{JobKey}' will now chain to Job '{sj}'", context.JobDetail.Key, sj);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jobDataMap = new JobDataMap();
|
||||||
|
if (context.Result is JobResult { Result: not null } jobResult)
|
||||||
|
{
|
||||||
|
jobDataMap.Put("previousResult", jobResult.Result);
|
||||||
|
}
|
||||||
|
await context.Scheduler.TriggerJob(sj, jobDataMap, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (SchedulerException se)
|
||||||
|
{
|
||||||
|
_logger.LogError(se, "Error encountered during chaining to Job '{sj}'", sj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
src/CasaBot/AutoScan/Listener/CharinerFactory.cs
Normal file
25
src/CasaBot/AutoScan/Listener/CharinerFactory.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace AutoScan.Listener;
|
||||||
|
|
||||||
|
public interface IChainerListenerFactory
|
||||||
|
{
|
||||||
|
ChainerListener CreateChainerListener(string name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChainerListenerFactory : IChainerListenerFactory
|
||||||
|
{
|
||||||
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
|
||||||
|
public ChainerListenerFactory(ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_loggerFactory = loggerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChainerListener CreateChainerListener(string name)
|
||||||
|
{
|
||||||
|
var logger = _loggerFactory.CreateLogger<ChainerListener>();
|
||||||
|
return new ChainerListener(name, logger);
|
||||||
|
}
|
||||||
|
}
|
41
src/CasaBot/AutoScan/Models/ShinobiFetch.cs
Normal file
41
src/CasaBot/AutoScan/Models/ShinobiFetch.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
namespace AutoScan.Models;
|
||||||
|
|
||||||
|
//Bulk converted from endpoint response. TODO: Cleanup later, format properties names
|
||||||
|
public class VideoStorageLocations
|
||||||
|
{
|
||||||
|
public string dir { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VideoActionLink
|
||||||
|
{
|
||||||
|
public string changeToRead { get; set; }
|
||||||
|
public string changeToUnread { get; set; }
|
||||||
|
public string deleteVideo { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FetchVideoResponse
|
||||||
|
{
|
||||||
|
public bool endIsStartTo { get; set; }
|
||||||
|
public bool ok { get; set; }
|
||||||
|
public List<VideoDetail> videos { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VideoDetail
|
||||||
|
{
|
||||||
|
public string actionUrl { get; set; }
|
||||||
|
public int archive { get; set; }
|
||||||
|
public VideoStorageLocations details { get; set; }
|
||||||
|
public DateTime end { get; set; }
|
||||||
|
public string ext { get; set; }
|
||||||
|
public string filename { get; set; }
|
||||||
|
public string href { get; set; }
|
||||||
|
public string ke { get; set; }
|
||||||
|
public VideoActionLink links { get; set; }
|
||||||
|
public string mid { get; set; }
|
||||||
|
public string objects { get; set; }
|
||||||
|
public object saveDir { get; set; }
|
||||||
|
public int size { get; set; }
|
||||||
|
public int status { get; set; }
|
||||||
|
public DateTime time { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -3,13 +3,12 @@ namespace AutoScan.Options;
|
|||||||
public record AutoScanOptions
|
public record AutoScanOptions
|
||||||
{
|
{
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
public string? At { get; set; }
|
public string At { get; set; } = "06:00";
|
||||||
public bool FromDayBefore { get; set; }
|
public bool FromDayBefore { get; set; }
|
||||||
public string? From { get; set; }
|
public string From { get; set; } = "23:00";
|
||||||
public string? To { get; set; }
|
public string To { get; set; } = "1:00";
|
||||||
public int MaxAmount { get; set; }
|
public int MaxAmount { get; set; }
|
||||||
public string? MediaFolder { get; set; }
|
public string? MediaFolder { get; set; }
|
||||||
public ShinobiOptions? Shinobi { get; set; }
|
|
||||||
public ScannerOptions? Scanner { get; set; }
|
public ScannerOptions? Scanner { get; set; }
|
||||||
public ScreenshotOptions? Screenshot { get; set; }
|
public ScreenshotOptions? Screenshot { get; set; }
|
||||||
}
|
}
|
@ -1,24 +0,0 @@
|
|||||||
using AutoScan.Options;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Quartz;
|
|
||||||
|
|
||||||
namespace AutoScan;
|
|
||||||
|
|
||||||
public class ScanJob : IJob
|
|
||||||
{
|
|
||||||
private readonly ILogger<ScanJob> _logger;
|
|
||||||
private readonly AutoScanOptions _options;
|
|
||||||
|
|
||||||
public ScanJob(ILogger<ScanJob> logger, IOptionsSnapshot<AutoScanOptions> options)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_options = options.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Scheduled scan executed with ops: {Options}", _options);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +1,64 @@
|
|||||||
|
using AutoScan.Models;
|
||||||
|
using AutoScan.Options;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
|
||||||
namespace CasaBotApp;
|
namespace AutoScan;
|
||||||
|
|
||||||
public class ShinobiConnector
|
public class ShinobiConnector
|
||||||
{
|
{
|
||||||
//TODO move class to auto scan library
|
|
||||||
private readonly ILogger<ShinobiConnector> _logger;
|
private readonly ILogger<ShinobiConnector> _logger;
|
||||||
private readonly string _shinobivUrl = "";
|
|
||||||
private readonly string _apikey = "";
|
|
||||||
private readonly string _groupId = "";
|
|
||||||
private readonly string _monitorId = "";
|
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly ShinobiOptions _options;
|
||||||
|
|
||||||
public ShinobiConnector(ILogger<ShinobiConnector> logger, HttpClient httpClient)
|
public ShinobiConnector(ILogger<ShinobiConnector> logger, HttpClient httpClient, IOptions<ShinobiOptions> options)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_options = options.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchLastVideo(string filename = "2025-02-12T08-00-01.mp4")
|
public async Task<List<VideoDetail>> FetchMonitorVideosBetween(DateTime from, DateTime to)
|
||||||
{
|
{
|
||||||
const string fetchVideoEndpoint = "/{0}/videos/{1}/{2}/{3}";
|
var endpoint = $"{_options.URL}/{_options.APIKey}/videos/{_options.GroupId}/{_options.MonitorId}";
|
||||||
var endpoint = string.Format(_shinobivUrl+fetchVideoEndpoint, _apikey, _groupId, _monitorId, filename);
|
endpoint += $"?start={from:yyyy-MM-ddTHH:mm:sszzz}&end={to:yyyy-MM-ddTHH:mm:sszzz}";
|
||||||
_logger.LogInformation("Fetching video from endpoint: {Endpoint}", endpoint);
|
|
||||||
|
|
||||||
//fetch video
|
_logger.LogDebug("Fetching videos details from endpoint: {Endpoint}", endpoint);
|
||||||
const string mediaPath = @".\media\"; //TODO. Use options
|
|
||||||
var videoPath = mediaPath + filename;
|
//get from the server the response with type VideoDetails
|
||||||
|
var response = await _httpClient.GetFromJsonAsync<FetchVideoResponse>(endpoint);
|
||||||
|
|
||||||
|
if (response is null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("No videos found in the specified range");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Found {Count} videos in the specified range", response.videos.Count);
|
||||||
|
|
||||||
|
foreach (var video in response.videos.OrderBy(x => x.time))
|
||||||
|
{
|
||||||
|
video.end = video.end.ToLocalTime();
|
||||||
|
video.time = video.time.ToLocalTime();
|
||||||
|
_logger.LogDebug("Video: {Filename} - Time: {time} - Ends: {end}", video.filename, video.time, video.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.videos;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DownloadMonitorVideo(VideoDetail video, string downloadFolder)
|
||||||
|
{
|
||||||
|
var endpoint = $"{_options.URL}{video.href}";
|
||||||
|
_logger.LogDebug("Fetching video from endpoint: {Endpoint}", endpoint);
|
||||||
|
|
||||||
|
//Video filenames format: "monitorId-2025-02-15T07-45-01.mp4"
|
||||||
|
var videoTime = video.time.ToString("yyyy-MM-ddTHH-mm-ss");
|
||||||
|
var videoPath = Path.Combine(downloadFolder, $"{video.mid}-{videoTime}.{video.ext}");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//make sure the directory exists
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(mediaPath)!);
|
|
||||||
|
|
||||||
_logger.LogDebug("Cleaning media folder");
|
|
||||||
CleanDirectory(mediaPath);
|
|
||||||
_logger.LogDebug("Downloading video...");
|
_logger.LogDebug("Downloading video...");
|
||||||
var videoData = await _httpClient.GetByteArrayAsync(endpoint);
|
var videoData = await _httpClient.GetByteArrayAsync(endpoint);
|
||||||
|
|
||||||
@ -44,18 +68,11 @@ public class ShinobiConnector
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "An error occurred while downloading the video");
|
_logger.LogError(ex, "An error occurred while downloading the video {video}", video.filename);
|
||||||
}
|
throw;
|
||||||
}
|
|
||||||
|
|
||||||
private void CleanDirectory(string path)
|
|
||||||
{
|
|
||||||
DirectoryInfo di = new DirectoryInfo(path);
|
|
||||||
foreach (var file in di.GetFiles())
|
|
||||||
{
|
|
||||||
file.Delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using AutoScan;
|
using AutoScan;
|
||||||
|
using AutoScan.Options;
|
||||||
using CasaBotApp;
|
using CasaBotApp;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -30,7 +31,8 @@ services.AddLogging(builder =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
services.Configure<TelegramOptions>(configuration.GetSection("Telegram"));
|
services.Configure<TelegramOptions>(configuration.GetSection("Telegram"));
|
||||||
services.Configure<AutoScan.Options.AutoScanOptions>(configuration.GetSection("AutoScan"));
|
services.Configure<AutoScanOptions>(configuration.GetSection("AutoScan"));
|
||||||
|
services.Configure<ShinobiOptions>(configuration.GetSection("Shinobi"));
|
||||||
|
|
||||||
services.AddSingleton<BotHandler>();
|
services.AddSingleton<BotHandler>();
|
||||||
|
|
||||||
|
@ -4,13 +4,20 @@
|
|||||||
"Default": "Debug",
|
"Default": "Debug",
|
||||||
"System": "Information",
|
"System": "Information",
|
||||||
"Microsoft": "Information",
|
"Microsoft": "Information",
|
||||||
"Quartz": "Information"
|
"Quartz": "Information",
|
||||||
|
"System.Net.Http.HttpClient": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Telegram":{
|
"Telegram":{
|
||||||
"BotToken": "__token__",
|
"BotToken": "__token__",
|
||||||
"SubscribedChatIds": []
|
"SubscribedChatIds": []
|
||||||
},
|
},
|
||||||
|
"Shinobi": {
|
||||||
|
"URL": "http://localhost:8080",
|
||||||
|
"APIKey": "APIKEY",
|
||||||
|
"GroupId": "Group",
|
||||||
|
"MonitorId": "Monitor"
|
||||||
|
},
|
||||||
"AutoScan": {
|
"AutoScan": {
|
||||||
"Enabled": false,
|
"Enabled": false,
|
||||||
"At": "07:00",
|
"At": "07:00",
|
||||||
@ -18,20 +25,14 @@
|
|||||||
"From": "23:00",
|
"From": "23:00",
|
||||||
"To": "05:00",
|
"To": "05:00",
|
||||||
"MaxAmount": 1,
|
"MaxAmount": 1,
|
||||||
"MediaFolder": "./media/originals",
|
"MediaFolder": "./media/originals/",
|
||||||
"Shinobi": {
|
|
||||||
"URL": "http://localhost:8080",
|
|
||||||
"APIKey": "APIKEY",
|
|
||||||
"GroupId": "Group",
|
|
||||||
"MonitorId": "Monitor"
|
|
||||||
},
|
|
||||||
"Scanner": {
|
"Scanner": {
|
||||||
"Exe": "./dvr-scanner/dvr.exe",
|
"Exe": "./dvr-scanner/dvr.exe",
|
||||||
"ConfigFile": "./dvr-scanner/dvr-scan.cfg",
|
"ConfigFile": "./dvr-scanner/dvr-scan.cfg",
|
||||||
"DetectionFolder": "./media/detections"
|
"DetectionFolder": "./media/detections/"
|
||||||
},
|
},
|
||||||
"Screenshot": {
|
"Screenshot": {
|
||||||
"Folder": "./media/screenshots",
|
"Folder": "./media/screenshots/",
|
||||||
"OffsetSeconds": 0
|
"OffsetSeconds": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user