Initial commit
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace TvSyncApp
|
||||
{
|
||||
public class Downloader
|
||||
{
|
||||
private static readonly HttpClient _httpClient = new HttpClient();
|
||||
|
||||
public void Download(string url, string outputDir, string outputFile, ILogger log)
|
||||
{
|
||||
try
|
||||
{
|
||||
// log.Information("Downloading {Url} to {OutputFile}", url, outputFile);
|
||||
|
||||
// Ensure the output directory exists
|
||||
Directory.CreateDirectory(outputDir);
|
||||
|
||||
// Get JSON content as string
|
||||
var response = _httpClient.GetAsync(url).Result;
|
||||
response.EnsureSuccessStatusCode();
|
||||
string jsonString = response.Content.ReadAsStringAsync().Result;
|
||||
|
||||
// Parse and pretty-print JSON
|
||||
var jsonDoc = JsonDocument.Parse(jsonString);
|
||||
var formattedJson = JsonSerializer.Serialize(jsonDoc.RootElement, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
// Write to file
|
||||
string filePath = Path.Combine(outputDir, outputFile);
|
||||
File.WriteAllText(filePath, formattedJson);
|
||||
|
||||
//log.Information("Downloaded and formatted {OutputFile} successfully.", outputFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error(ex, "Failed to download {OutputFile} from {Url}", outputFile, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using Serilog;
|
||||
|
||||
namespace TvSyncApp
|
||||
{
|
||||
public class Loader
|
||||
{
|
||||
private readonly Downloader _downloader;
|
||||
|
||||
public Loader()
|
||||
{
|
||||
_downloader = new Downloader();
|
||||
}
|
||||
|
||||
public async Task<(int Success, int Failed)> LoadJsonAsync(
|
||||
Dictionary<string, string> directories,
|
||||
List<string> updateList,
|
||||
Dictionary<string, long> updateDict,
|
||||
Settings settings,
|
||||
int maxParallelDownloads = 5)
|
||||
{
|
||||
int success = 0;
|
||||
int failed = 0;
|
||||
int completed = 0;
|
||||
|
||||
|
||||
var semaphore = new System.Threading.SemaphoreSlim(maxParallelDownloads);
|
||||
|
||||
var filesNeeded = new List<(string Directory, string FileName)>();
|
||||
|
||||
foreach (var seriesId in updateList)
|
||||
{
|
||||
foreach (var dir in directories.Values)
|
||||
{
|
||||
string filePath = Path.Combine(dir, $"{seriesId}.json");
|
||||
if (!File.Exists(filePath))
|
||||
filesNeeded.Add((dir, $"{seriesId}.json"));
|
||||
}
|
||||
}
|
||||
|
||||
Log.Information("Downloading {Count} files...", filesNeeded.Count);
|
||||
var totalFiles = filesNeeded.Count;
|
||||
var progress = new Progress<int>(value =>
|
||||
{
|
||||
completed += value;
|
||||
Console.Write($"\rDownloading files: {completed}/{totalFiles}");
|
||||
});
|
||||
|
||||
var tasks = new List<Task>();
|
||||
foreach (var file in filesNeeded)
|
||||
{
|
||||
tasks.Add(DownloadWithThrottleAsync(file.Directory, file.FileName, settings, semaphore, progress));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
Console.WriteLine("\nDownload complete.");
|
||||
|
||||
foreach (var file in filesNeeded)
|
||||
{
|
||||
string path = Path.Combine(file.Directory, file.FileName);
|
||||
if (File.Exists(path)) success++;
|
||||
else failed++;
|
||||
}
|
||||
|
||||
return (success, failed);
|
||||
}
|
||||
|
||||
private async Task DownloadWithThrottleAsync(
|
||||
string directory,
|
||||
string fileName,
|
||||
Settings settings,
|
||||
System.Threading.SemaphoreSlim semaphore,
|
||||
IProgress<int> progress)
|
||||
{
|
||||
await semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
string seriesId = Path.GetFileNameWithoutExtension(fileName);
|
||||
string url;
|
||||
|
||||
if (directory.Contains("series", StringComparison.OrdinalIgnoreCase))
|
||||
url = settings.Json.ShowApi.Replace("<tvmazeid>", seriesId);
|
||||
else if (directory.Contains("episodes", StringComparison.OrdinalIgnoreCase))
|
||||
url = settings.Json.EpisodeApi.Replace("<tvmazeid>", seriesId);
|
||||
else if (directory.Contains("cast", StringComparison.OrdinalIgnoreCase))
|
||||
url = settings.Json.CastApi.Replace("<tvmazeid>", seriesId);
|
||||
else if (directory.Contains("crew", StringComparison.OrdinalIgnoreCase))
|
||||
url = settings.Json.CrewApi.Replace("<tvmazeid>", seriesId);
|
||||
else
|
||||
url = $"https://api.tvmaze.com/shows/{seriesId}";
|
||||
|
||||
// Download the file
|
||||
await Task.Run(() => _downloader.Download(url, directory, fileName, Log.Logger));
|
||||
|
||||
// Report progress
|
||||
progress?.Report(1);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to download {FileName}", fileName);
|
||||
progress?.Report(1); // still report so progress moves forward
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using MongoDB.Driver;
|
||||
using Serilog;
|
||||
using System.Text.Json;
|
||||
|
||||
public class MongoLoader
|
||||
{
|
||||
private readonly IMongoCollection<BsonDocument> _series;
|
||||
private readonly IMongoCollection<BsonDocument> _episodes;
|
||||
private readonly IMongoCollection<BsonDocument> _actors;
|
||||
private readonly IMongoCollection<BsonDocument> _characters;
|
||||
private readonly IMongoCollection<BsonDocument> _crew;
|
||||
|
||||
public MongoLoader(IMongoDatabase db)
|
||||
{
|
||||
_series = db.GetCollection<BsonDocument>("series");
|
||||
_episodes = db.GetCollection<BsonDocument>("episodes");
|
||||
_actors = db.GetCollection<BsonDocument>("actors");
|
||||
_characters = db.GetCollection<BsonDocument>("characters");
|
||||
_crew = db.GetCollection<BsonDocument>("crew");
|
||||
}
|
||||
|
||||
public async Task<(int Success, int Failed)> LoadJsonToMongoAsync(
|
||||
Dictionary<string, string> directories,
|
||||
List<string> updateList,
|
||||
Dictionary<string, long> updateDict)
|
||||
{
|
||||
int success = 0;
|
||||
int failed = 0;
|
||||
|
||||
var options = new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = Environment.ProcessorCount // or set manually (e.g. 4)
|
||||
};
|
||||
|
||||
await Parallel.ForEachAsync(updateList, options, async (seriesId, cancellationToken) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var seriesFile = Path.Combine(directories["SERIESDIR"], $"{seriesId}.json");
|
||||
if (!File.Exists(seriesFile))
|
||||
return;
|
||||
|
||||
var json = await File.ReadAllTextAsync(seriesFile, cancellationToken);
|
||||
var doc = BsonDocument.Parse(json);
|
||||
|
||||
await UpsertWithTimestamp(_series, doc, updateDict, seriesId);
|
||||
|
||||
await ProcessEpisodes(seriesId, directories, updateDict);
|
||||
await ProcessCast(seriesId, directories, updateDict);
|
||||
await ProcessCrew(seriesId, directories, updateDict);
|
||||
|
||||
Interlocked.Increment(ref success);
|
||||
|
||||
Log.Information("Processed series {SeriesId}", seriesId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref failed);
|
||||
Log.Error(ex, "Failed processing series {SeriesId}", seriesId);
|
||||
}
|
||||
});
|
||||
|
||||
return (success, failed);
|
||||
}
|
||||
|
||||
|
||||
private async Task UpsertWithTimestamp(
|
||||
IMongoCollection<BsonDocument> collection,
|
||||
BsonDocument doc,
|
||||
Dictionary<string, long> updateDict,
|
||||
string seriesId)
|
||||
{
|
||||
var id = doc.GetValue("id", null);
|
||||
if (id == null) return;
|
||||
|
||||
var filter = Builders<BsonDocument>.Filter.Eq("id", id);
|
||||
var existing = await collection.Find(filter).FirstOrDefaultAsync();
|
||||
|
||||
if (updateDict.TryGetValue(seriesId, out var newTimestamp))
|
||||
{
|
||||
if (existing == null ||
|
||||
!existing.Contains("updated") ||
|
||||
existing["updated"].AsInt64 < newTimestamp)
|
||||
{
|
||||
await collection.ReplaceOneAsync(filter, doc,
|
||||
new ReplaceOptions { IsUpsert = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessEpisodes(
|
||||
string seriesId,
|
||||
Dictionary<string, string> directories,
|
||||
Dictionary<string, long> updateDict)
|
||||
{
|
||||
var file = Path.Combine(directories["EPISODEDIR"], $"{seriesId}.json");
|
||||
if (!File.Exists(file)) return;
|
||||
|
||||
var json = await File.ReadAllTextAsync(file);
|
||||
var array = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(json);
|
||||
|
||||
if (array == null) return;
|
||||
|
||||
foreach (var episode in array)
|
||||
{
|
||||
episode["seriesid"] = int.Parse(seriesId);
|
||||
var doc = BsonDocument.Parse(JsonSerializer.Serialize(episode));
|
||||
await UpsertWithTimestamp(_episodes, doc, updateDict, seriesId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessCast(
|
||||
string seriesId,
|
||||
Dictionary<string, string> directories,
|
||||
Dictionary<string, long> updateDict)
|
||||
{
|
||||
var file = Path.Combine(directories["CASTDIR"], $"{seriesId}.json");
|
||||
if (!File.Exists(file)) return;
|
||||
|
||||
var json = await File.ReadAllTextAsync(file);
|
||||
|
||||
// ✅ Correct way to parse root JSON array
|
||||
var array = BsonSerializer.Deserialize<BsonArray>(json);
|
||||
|
||||
foreach (var item in array)
|
||||
{
|
||||
if (!item.IsBsonDocument)
|
||||
continue;
|
||||
|
||||
var doc = item.AsBsonDocument;
|
||||
|
||||
if (doc.Contains("person") && doc["person"].IsBsonDocument)
|
||||
{
|
||||
var person = doc["person"].AsBsonDocument;
|
||||
person["seriesid"] = int.Parse(seriesId);
|
||||
|
||||
await UpsertWithTimestamp(_actors, person, updateDict, seriesId);
|
||||
}
|
||||
|
||||
if (doc.Contains("character") && doc["character"].IsBsonDocument)
|
||||
{
|
||||
var character = doc["character"].AsBsonDocument;
|
||||
character["seriesid"] = int.Parse(seriesId);
|
||||
|
||||
await UpsertWithTimestamp(_characters, character, updateDict, seriesId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessCrew(
|
||||
string seriesId,
|
||||
Dictionary<string, string> directories,
|
||||
Dictionary<string, long> updateDict)
|
||||
{
|
||||
var file = Path.Combine(directories["CREWDIR"], $"{seriesId}.json");
|
||||
if (!File.Exists(file)) return;
|
||||
|
||||
var json = await File.ReadAllTextAsync(file);
|
||||
|
||||
// ✅ Correct way to parse root JSON array
|
||||
var array = BsonSerializer.Deserialize<BsonArray>(json);
|
||||
|
||||
foreach (var item in array)
|
||||
{
|
||||
if (!item.IsBsonDocument)
|
||||
continue;
|
||||
|
||||
var doc = item.AsBsonDocument;
|
||||
|
||||
if (doc.Contains("person") && doc["person"].IsBsonDocument)
|
||||
{
|
||||
var person = doc["person"].AsBsonDocument;
|
||||
|
||||
person["seriesid"] = int.Parse(seriesId);
|
||||
|
||||
// Safely get crew type
|
||||
person["crew_type"] = doc.Contains("type")
|
||||
? doc["type"]
|
||||
: "unknown";
|
||||
|
||||
await UpsertWithTimestamp(_crew, person, updateDict, seriesId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace TvSyncApp
|
||||
{
|
||||
public class Settings
|
||||
{
|
||||
public GlobalSettings? Global { get; set; }
|
||||
public DbSettings? DbSettings { get; set; }
|
||||
public ApiSettings? Api { get; set; }
|
||||
public JsonSettings? Json { get; set; }
|
||||
public DatabaseSettings? Database { get; set; }
|
||||
public UpdateTypeSettings? UpdateType { get; set; }
|
||||
|
||||
public static Settings Load(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"Settings file not found: {filePath}");
|
||||
|
||||
var json = File.ReadAllText(filePath);
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
AllowTrailingCommas = true
|
||||
};
|
||||
|
||||
return JsonSerializer.Deserialize<Settings>(json, options);
|
||||
}
|
||||
}
|
||||
|
||||
public class GlobalSettings
|
||||
{
|
||||
public string? LogLevel { get; set; }
|
||||
}
|
||||
|
||||
public class DbSettings
|
||||
{
|
||||
public string? SqlServerDriver { get; set; }
|
||||
public string? Hostname { get; set; }
|
||||
public string? MgHostname { get; set; }
|
||||
public string? DbName { get; set; }
|
||||
public string? MgDbName { get; set; }
|
||||
public string? MysqlUsername { get; set; }
|
||||
public string? PgsqlUsername { get; set; }
|
||||
public string? MssqlUsername { get; set; }
|
||||
public string? MysqlPassword { get; set; }
|
||||
public string? PgsqlPassword { get; set; }
|
||||
public string? MssqlPassword { get; set; }
|
||||
public int PgsqlPort { get; set; }
|
||||
public int MysqlPort { get; set; }
|
||||
public int MssqlPort { get; set; }
|
||||
public int MgPort { get; set; }
|
||||
}
|
||||
|
||||
public class ApiSettings
|
||||
{
|
||||
public string? Type { get; set; }
|
||||
}
|
||||
|
||||
public class JsonSettings
|
||||
{
|
||||
public string? SeriesApi { get; set; }
|
||||
public string? ShowApi { get; set; }
|
||||
public string? EpisodeApi { get; set; }
|
||||
public string? AliasApi { get; set; }
|
||||
public string? CastApi { get; set; }
|
||||
public string? CrewApi { get; set; }
|
||||
public string? CreditsApi { get; set; }
|
||||
public string? UpdatesApi { get; set; }
|
||||
}
|
||||
|
||||
public class DatabaseSettings
|
||||
{
|
||||
public string? DbType { get; set; }
|
||||
public string? UpdateSchema { get; set; }
|
||||
public int Initialize { get; set; }
|
||||
public int InitLoad { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateTypeSettings
|
||||
{
|
||||
public string? Type { get; set; }
|
||||
public string? Full { get; set; }
|
||||
public long Monthly { get; set; }
|
||||
public long Weekly { get; set; }
|
||||
public long Daily { get; set; }
|
||||
|
||||
[JsonPropertyName("12h")]
|
||||
public long H12 { get; set; }
|
||||
|
||||
[JsonPropertyName("6h")]
|
||||
public long H6 { get; set; }
|
||||
|
||||
public long Custom { get; set; }
|
||||
public int CacheSkip { get; set; }
|
||||
public int RefreshCache { get; set; }
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Npgsql;
|
||||
using Serilog;
|
||||
|
||||
namespace TvSyncApp
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
string settingsPath = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json");
|
||||
var settings = TvSyncApp.Settings.Load(settingsPath)
|
||||
?? throw new Exception("Failed to load settings from appsettings.json");
|
||||
|
||||
Console.WriteLine($"Log level: {settings.Global.LogLevel}");
|
||||
Console.WriteLine($"Database type: {settings.Database.DbType}");
|
||||
// Console.WriteLine($"TVMaze series API: {settings.Json.SeriesApi}");
|
||||
|
||||
string rootDir = Directory.GetCurrentDirectory();
|
||||
|
||||
var directories = new Dictionary<string, string>
|
||||
{
|
||||
["SERIESDIR"] = Path.Combine(rootDir, "cache", "series"),
|
||||
["EPISODEDIR"] = Path.Combine(rootDir, "cache", "episodes"),
|
||||
["CASTDIR"] = Path.Combine(rootDir, "cache", "cast"),
|
||||
// ["GUESTCASTDIR"] = Path.Combine(rootDir, "cache", "guestcast"),
|
||||
["CREWDIR"] = Path.Combine(rootDir, "cache", "crew"),
|
||||
["UNKNOWN"] = Path.Combine(rootDir, "cache", "unknown")
|
||||
};
|
||||
|
||||
CreateDirectories(rootDir);
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File(Path.Combine(rootDir, "log", "sync.log"))
|
||||
.CreateLogger();
|
||||
|
||||
Log.Information("Starting sync process...");
|
||||
|
||||
// --- Fetch series IDs from PostgreSQL ---
|
||||
string connectionString = $"Host={settings.DbSettings.Hostname};Port={settings.DbSettings.PgsqlPort};Username={settings.DbSettings.PgsqlUsername};Password={settings.DbSettings.PgsqlPassword};Database={settings.DbSettings.DbName}";
|
||||
List<int> seriesIds = GetSeriesIdsFromDb(connectionString);
|
||||
|
||||
Log.Information("Fetched {Count} series IDs from tvupdates table.", seriesIds.Count);
|
||||
|
||||
// Convert to string list if your Loader expects strings
|
||||
List<string> updateList = seriesIds.ConvertAll(id => id.ToString());
|
||||
|
||||
// Optionally, you can also fetch timestamps if needed
|
||||
Dictionary<string, long> updateDict = new Dictionary<string, long>();
|
||||
|
||||
var loader = new Loader();
|
||||
|
||||
var result = await loader.LoadJsonAsync(directories, updateList, updateDict, settings);
|
||||
|
||||
Log.Information("Sync complete. Success: {Success}, Failed: {Failed}", result.Success, result.Failed);
|
||||
Log.CloseAndFlush();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static List<int> GetSeriesIdsFromDb(string connectionString)
|
||||
{
|
||||
var seriesIds = new List<int>();
|
||||
|
||||
try
|
||||
{
|
||||
using var conn = new NpgsqlConnection(connectionString);
|
||||
conn.Open();
|
||||
|
||||
string query = "SELECT seriesid FROM updates.tvupdates ORDER BY seriesid";
|
||||
|
||||
using var cmd = new NpgsqlCommand(query, conn);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
if (!reader.IsDBNull(0))
|
||||
seriesIds.Add(reader.GetInt32(0));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to fetch series IDs from PostgreSQL.");
|
||||
}
|
||||
|
||||
return seriesIds;
|
||||
}
|
||||
|
||||
private static void CreateDirectories(string rootDir)
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(rootDir, "log"));
|
||||
Directory.CreateDirectory(Path.Combine(rootDir, "temp"));
|
||||
Directory.CreateDirectory(Path.Combine(rootDir, "cache", "series"));
|
||||
Directory.CreateDirectory(Path.Combine(rootDir, "cache", "episodes"));
|
||||
Directory.CreateDirectory(Path.Combine(rootDir, "cache", "cast"));
|
||||
Directory.CreateDirectory(Path.Combine(rootDir, "cache", "guestcast"));
|
||||
Directory.CreateDirectory(Path.Combine(rootDir, "cache", "crew"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"global": {
|
||||
"loglevel": "info"
|
||||
},
|
||||
"dbsettings": {
|
||||
"sqlserver_driver": "ODBC Driver 13 for SQL Server",
|
||||
"hostname": "192.168.129.248",
|
||||
"mghostname": "192.168.128.24",
|
||||
"dbname": "media_dbsync",
|
||||
"mgdbname": "tvdata",
|
||||
"mysqlusername": "root",
|
||||
"pgsqlusername": "postgres",
|
||||
"mssqlusername": "sa",
|
||||
"mysqlpassword": "Optimus0329",
|
||||
"pgsqlpassword": "Optimus0329",
|
||||
"mssqlpassword": "Optimus0329",
|
||||
"pgsqlport": 5432,
|
||||
"mysqlport": 3306,
|
||||
"mssqlport": 1433,
|
||||
"mgport": 27017
|
||||
},
|
||||
"api": {
|
||||
"type": "tvmaze"
|
||||
},
|
||||
"json": {
|
||||
// "seriesapi": "http://api.tvmaze.com/shows?page=<pagenum>",
|
||||
"showapi": "http://api.tvmaze.com/shows/<tvmazeid>",
|
||||
"episodeapi": "http://api.tvmaze.com/shows/<tvmazeid>/episodes",
|
||||
"aliasapi": "http://api.tvmaze.com/shows/<tvmazeid>/akas",
|
||||
"castapi": "http://api.tvmaze.com/shows/<tvmazeid>/cast",
|
||||
"crewapi": "http://api.tvmaze.com/shows/<tvmazeid>/crew",
|
||||
"creditsapi": "http://api.tvmaze.com/people/<actorid>/castcredits",
|
||||
"updatesapi": "http://api.tvmaze.com/updates/shows"
|
||||
},
|
||||
"database": {
|
||||
"dbtype": "pgsql",
|
||||
"updateschema": "updates",
|
||||
"initialize": 0,
|
||||
"initload": 0
|
||||
},
|
||||
"updatetype": {
|
||||
"type": "full",
|
||||
"full": "full",
|
||||
"monthly": 1219200,
|
||||
"weekly": 604800,
|
||||
"daily": 86400,
|
||||
"12h": 23200,
|
||||
"6h": 16600,
|
||||
"custom": 4150,
|
||||
"cacheskip": 1,
|
||||
"refreshcache": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MongoDB.driver" Version="3.6.0" />
|
||||
<PackageReference Include="Npgsql" Version="10.0.1" />
|
||||
<PackageReference Include="Serilog" Version="4.3.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36930.0 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dbsync", "dbsync.csproj", "{06905DC5-E7BA-4CD0-9F8F-FD0E60C9DB71}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{06905DC5-E7BA-4CD0-9F8F-FD0E60C9DB71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{06905DC5-E7BA-4CD0-9F8F-FD0E60C9DB71}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{06905DC5-E7BA-4CD0-9F8F-FD0E60C9DB71}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{06905DC5-E7BA-4CD0-9F8F-FD0E60C9DB71}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5F508F17-C255-49C1-B01D-13B2B739B1FF}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Reference in New Issue
Block a user