file scanner
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<RootNamespace>AZKiServer</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.AspNetCore" Version="2.64.0" />
|
<PackageReference Include="Grpc.AspNetCore" Version="2.64.0" />
|
||||||
|
<PackageReference Include="MaybeError" Version="1.2.0" />
|
||||||
<PackageReference Include="MongoDB.Driver" Version="3.5.2" />
|
<PackageReference Include="MongoDB.Driver" Version="3.5.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
74
AZKi Server/Models/MediaEntry.cs
Normal file
74
AZKi Server/Models/MediaEntry.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using MaybeError;
|
||||||
|
using MaybeError.Errors;
|
||||||
|
|
||||||
|
using MongoDB.Bson;
|
||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace AZKiServer.Models;
|
||||||
|
|
||||||
|
public partial class MediaEntry
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public ObjectId Id { get; set; }
|
||||||
|
public MediaType Type { get; set; }
|
||||||
|
public required string Filepath { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public byte CameraId { get; set; }
|
||||||
|
|
||||||
|
public static Maybe<MediaEntry> Parse(string relativePath)
|
||||||
|
{
|
||||||
|
var filename = Path.GetFileName(relativePath);
|
||||||
|
|
||||||
|
var match = FileParser().Match(filename);
|
||||||
|
if (!match.Success)
|
||||||
|
return new Error("Failed to parse file name");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var src = match.Groups["src"];
|
||||||
|
var cam = match.Groups["cam"];
|
||||||
|
var date = match.Groups["date"];
|
||||||
|
var ext = match.Groups["ext"];
|
||||||
|
|
||||||
|
return new MediaEntry
|
||||||
|
{
|
||||||
|
CameraId = byte.Parse(cam.Value),
|
||||||
|
Filepath = relativePath,
|
||||||
|
Date = ParseDate(date.Value),
|
||||||
|
Type = ext.Value switch
|
||||||
|
{
|
||||||
|
"mp4" => MediaType.Video,
|
||||||
|
_ => MediaType.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}catch(Exception ex)
|
||||||
|
{
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateTime ParseDate(string dateString)
|
||||||
|
{
|
||||||
|
var year = dateString[0..4];
|
||||||
|
var month = dateString[4..6];
|
||||||
|
var day = dateString[6..8];
|
||||||
|
var hour = dateString[8..10];
|
||||||
|
var minute = dateString[10..12];
|
||||||
|
var sec = dateString[12..];
|
||||||
|
return new DateTime(int.Parse(year), int.Parse(month), int.Parse(day), int.Parse(hour), int.Parse(minute), int.Parse(sec), DateTimeKind.Local);
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("(?'src'.+)_(?'cam'\\d+)_(?'date'\\d+).(?'ext'\\w+)")]
|
||||||
|
private static partial Regex FileParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public enum MediaType
|
||||||
|
{
|
||||||
|
Video,
|
||||||
|
Image
|
||||||
|
}
|
||||||
@@ -1,16 +1,55 @@
|
|||||||
|
|
||||||
|
using AZKiServer.Models;
|
||||||
|
|
||||||
namespace AZKiServer.Services;
|
namespace AZKiServer.Services;
|
||||||
|
|
||||||
public class FileScannerService : IHostedService
|
public class FileScannerService(MediaService mediaService, IConfiguration config, ILogger<FileScannerService> logger) : IHostedService, IDisposable
|
||||||
{
|
{
|
||||||
|
private Timer? _timer;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_timer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var path = config["SCAN_PATH"];
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
return Task.CompletedTask;
|
||||||
|
_timer = new Timer((_) =>
|
||||||
|
{
|
||||||
|
ScanFilesAsync(path).Wait();
|
||||||
|
}, null, TimeSpan.FromMinutes(1), TimeSpan.FromHours(1));
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
_timer?.Dispose();
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task ScanFilesAsync(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
|
||||||
|
var existingFiles = await mediaService.GetExistingFilePathsAsync();
|
||||||
|
var entries = new List<MediaEntry>();
|
||||||
|
foreach (var filePath in files)
|
||||||
|
{
|
||||||
|
var relativePath = Path.GetRelativePath(path, filePath);
|
||||||
|
if (existingFiles.Contains(relativePath))
|
||||||
|
continue;
|
||||||
|
entries.Add(MediaEntry.Parse(relativePath));
|
||||||
|
}
|
||||||
|
await mediaService.AddMediaBulkAsync(entries);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Failed to read directory contents");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
namespace AZKiServer.Services;
|
using AZKiServer.Models;
|
||||||
|
|
||||||
public class MediaService
|
using MongoDB.Driver;
|
||||||
|
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
|
||||||
|
namespace AZKiServer.Services;
|
||||||
|
|
||||||
|
public class MediaService(IMongoDatabase db)
|
||||||
{
|
{
|
||||||
|
public readonly IMongoCollection<MediaEntry> _entries = db.GetCollection<MediaEntry>("media");
|
||||||
|
public async Task<FrozenSet<string>> GetExistingFilePathsAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var files = await _entries.Find("{}").Project(m => m.Filepath).ToListAsync(cancellationToken);
|
||||||
|
return files.ToFrozenSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddMediaBulkAsync(List<MediaEntry> entries, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await _entries.InsertManyAsync(entries, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IndexCreation : BackgroundService
|
||||||
|
{
|
||||||
|
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
AZKi Server/Utilz/DateUtils.cs
Normal file
102
AZKi Server/Utilz/DateUtils.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
namespace AZKiServer.Utilz;
|
||||||
|
|
||||||
|
public enum DateInterval
|
||||||
|
{
|
||||||
|
Daily,
|
||||||
|
Weekly,
|
||||||
|
Monthly,
|
||||||
|
Yearly,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DateUtils
|
||||||
|
{
|
||||||
|
public static TimeSpan ToUnixTime(this DateTime date)
|
||||||
|
{
|
||||||
|
return date - DateTime.UnixEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateTime SnapToYear(this DateTime date)
|
||||||
|
{
|
||||||
|
return new DateTime(date.Year, 1, 1, 0, 0, 0, date.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateTimeOffset SnapToYear(this DateTimeOffset date)
|
||||||
|
{
|
||||||
|
return new DateTimeOffset(date.Year, 1, 1, 0, 0, 0, date.Offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateTime SnapToMonth(this DateTime date)
|
||||||
|
{
|
||||||
|
return new DateTime(date.Year, date.Month, 1, 0, 0, 0, date.Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateTimeOffset SnapToMonth(this DateTimeOffset date)
|
||||||
|
{
|
||||||
|
return new DateTimeOffset(date.Year, date.Month, 1, 0, 0, 0, date.Offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateTime SnapToWeek(this DateTime date)
|
||||||
|
{
|
||||||
|
var d = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, date.Kind);
|
||||||
|
if (date.DayOfWeek == DayOfWeek.Sunday)
|
||||||
|
return d;
|
||||||
|
else
|
||||||
|
return d.AddDays(-(int)date.DayOfWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateTimeOffset SnapToWeek(this DateTimeOffset date)
|
||||||
|
{
|
||||||
|
var d = new DateTimeOffset(date.Year, date.Month, date.Day, 0, 0, 0, date.Offset);
|
||||||
|
if (date.DayOfWeek == DayOfWeek.Sunday)
|
||||||
|
return d;
|
||||||
|
else
|
||||||
|
return d.AddDays(-(int)date.DayOfWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (DateTime from, DateTime to) ToInterval(this DateTime date, DateInterval internval)
|
||||||
|
{
|
||||||
|
return internval switch
|
||||||
|
{
|
||||||
|
DateInterval.Daily => (date, date.AddDays(1)),
|
||||||
|
DateInterval.Weekly => (date, date.AddDays(7)),
|
||||||
|
DateInterval.Monthly => (date, date.AddMonths(1)),
|
||||||
|
DateInterval.Yearly => (date, date.AddYears(1)),
|
||||||
|
DateInterval.All => (DateTime.MinValue, DateTime.MaxValue),
|
||||||
|
_ => throw new InvalidOperationException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (DateTimeOffset from, DateTimeOffset to) ToInterval(this DateTimeOffset date, DateInterval internval)
|
||||||
|
{
|
||||||
|
return internval switch
|
||||||
|
{
|
||||||
|
DateInterval.Daily => (date, date.AddDays(1)),
|
||||||
|
DateInterval.Weekly => (date, date.AddDays(7)),
|
||||||
|
DateInterval.Monthly => (date, date.AddMonths(1)),
|
||||||
|
DateInterval.Yearly => (date, date.AddYears(1)),
|
||||||
|
DateInterval.All => (DateTime.MinValue, DateTime.MaxValue),
|
||||||
|
_ => throw new InvalidOperationException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TimeSpan Days(this int days) => TimeSpan.FromDays(days);
|
||||||
|
|
||||||
|
public static TimeSpan Days(this float days) => TimeSpan.FromDays(days);
|
||||||
|
|
||||||
|
public static TimeSpan Hours(this int hours) => TimeSpan.FromHours(hours);
|
||||||
|
|
||||||
|
public static TimeSpan Hours(this float hours) => TimeSpan.FromHours(hours);
|
||||||
|
|
||||||
|
public static TimeSpan Minutes(this int minutes) => TimeSpan.FromMinutes(minutes);
|
||||||
|
|
||||||
|
public static TimeSpan Minutes(this float minutes) => TimeSpan.FromMinutes(minutes);
|
||||||
|
|
||||||
|
public static TimeSpan Seconds(this int seconds) => TimeSpan.FromSeconds(seconds);
|
||||||
|
|
||||||
|
public static TimeSpan Seconds(this float seconds) => TimeSpan.FromSeconds(seconds);
|
||||||
|
|
||||||
|
public static TimeSpan Miliseconds(this int miliseconds) => TimeSpan.FromMilliseconds(miliseconds);
|
||||||
|
|
||||||
|
public static TimeSpan Miliseconds(this float miliseconds) => TimeSpan.FromMilliseconds(miliseconds);
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"SCAN_LOCATION": "O:/cctv",
|
||||||
|
"DB_STRING": "mongodb://NinoIna:27017"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user