-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCacheService.cs
More file actions
189 lines (160 loc) · 8.02 KB
/
CacheService.cs
File metadata and controls
189 lines (160 loc) · 8.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using Microsoft.Data.Sqlite;
using MediaBrowser.Model.Logging;
using ProviderInfo = StreamingCatalogs.Data.ProviderInfo;
using StreamingCatalogs.Data;
using StreamingCatalogs.Services.Tmdb.Dto;
namespace StreamingCatalogs.Services
{
public class CacheService
{
private readonly ILogger _logger;
private readonly string _dbPath;
public CacheService(ILogManager logger, MediaBrowser.Controller.Configuration.IServerConfigurationManager serverConfigurationManager)
{
_logger = logger.GetLogger(GetType().Name);
var pluginDataPath = Path.Combine(serverConfigurationManager.ApplicationPaths.PluginConfigurationsPath, "StreamingCatalogs");
Directory.CreateDirectory(pluginDataPath); // Ensure the directory exists
_dbPath = Path.Combine(pluginDataPath, "cache.db");
}
private SqliteConnection CreateConnection() => new SqliteConnection($"Data Source={_dbPath}");
public async Task InitializeAsync()
{
_logger.Info("Initializing streaming catalog cache database at {0}", _dbPath);
using var connection = CreateConnection();
var sql = @"
CREATE TABLE IF NOT EXISTS CatalogItems (
TmdbId INTEGER NOT NULL,
ProviderId TEXT NOT NULL,
CountryCode TEXT NOT NULL,
MediaType TEXT NOT NULL,
Title TEXT,
Overview TEXT,
PosterPath TEXT,
BackdropPath TEXT,
ReleaseYear INTEGER,
Popularity REAL,
IsInLibrary INTEGER NOT NULL DEFAULT 0,
LastUpdatedUtc TEXT NOT NULL,
PRIMARY KEY (TmdbId, ProviderId, CountryCode)
);
CREATE INDEX IF NOT EXISTS IX_CatalogItems_Provider_Country_Media ON CatalogItems (ProviderId, CountryCode, MediaType);
";
await connection.ExecuteAsync(sql);
}
public async Task SaveCatalogItemsAsync(string providerId, string countryCode, string mediaType, IEnumerable<TmdbItem> items)
{
var now = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture);
var catalogItems = items.Select(item => new CatalogItem
{
TmdbId = item.Id,
ProviderId = providerId,
CountryCode = countryCode,
MediaType = mediaType,
Title = (mediaType == "tv" ? item.Name : item.Title) ?? string.Empty,
Overview = item.Overview,
PosterPath = item.PosterPath,
BackdropPath = item.BackdropPath,
ReleaseYear = GetYearFromDateString(mediaType == "tv" ? item.FirstAirDate : item.ReleaseDate),
Popularity = item.Popularity,
IsInLibrary = false, // This will be updated later in a separate step
LastUpdatedUtc = now
}).ToList();
if (!catalogItems.Any()) return;
using var connection = CreateConnection();
await connection.OpenAsync();
using var transaction = connection.BeginTransaction();
try
{
// Atomically delete old items for this specific catalog and insert new ones
var deleteSql = "DELETE FROM CatalogItems WHERE ProviderId = @ProviderId AND CountryCode = @CountryCode AND MediaType = @MediaType;";
await connection.ExecuteAsync(deleteSql, new { ProviderId = providerId, CountryCode = countryCode, MediaType = mediaType }, transaction);
var insertSql = @"
INSERT INTO CatalogItems (TmdbId, ProviderId, CountryCode, MediaType, Title, Overview, PosterPath, BackdropPath, ReleaseYear, Popularity, IsInLibrary, LastUpdatedUtc)
VALUES (@TmdbId, @ProviderId, @CountryCode, @MediaType, @Title, @Overview, @PosterPath, @BackdropPath, @ReleaseYear, @Popularity, @IsInLibrary, @LastUpdatedUtc);
";
await connection.ExecuteAsync(insertSql, catalogItems, transaction);
transaction.Commit();
_logger.Info("Successfully cached {0} {1} items for provider {2} in {3}.", catalogItems.Count, mediaType, providerId, countryCode);
}
catch (Exception ex)
{
_logger.ErrorException("Failed to save catalog items to cache. Rolling back transaction.", ex);
await transaction.RollbackAsync();
throw;
}
}
public async Task<IEnumerable<CatalogItem>> GetItems(string mediaType, string providerId, string countryCode, int limit, string sort)
{
using var connection = CreateConnection();
var sqlBuilder = new StringBuilder("SELECT * FROM CatalogItems WHERE ProviderId = @ProviderId AND CountryCode = @CountryCode AND MediaType = @MediaType ");
sqlBuilder.Append(sort?.ToLowerInvariant() switch
{
"new" => "ORDER BY ReleaseYear DESC, Popularity DESC ",
_ => "ORDER BY Popularity DESC ",
});
sqlBuilder.Append("LIMIT @Limit;");
return await connection.QueryAsync<CatalogItem>(sqlBuilder.ToString(), new { ProviderId = providerId, CountryCode = countryCode, MediaType = mediaType, Limit = limit });
}
public async Task<IEnumerable<ProviderInfo>> GetAvailableProviders(string countryCode)
{
using var connection = CreateConnection();
var sql = @"
SELECT ProviderId, COUNT(TmdbId) as ItemCount
FROM CatalogItems
WHERE CountryCode = @CountryCode
GROUP BY ProviderId
HAVING ItemCount > 0
ORDER BY ProviderId;
";
return await connection.QueryAsync<ProviderInfo>(sql, new { CountryCode = countryCode });
}
public async Task UpdateLibraryStatusAsync(Dictionary<int, bool> libraryStatus)
{
if (libraryStatus == null || !libraryStatus.Any())
{
return;
}
_logger.Info("Updating 'IsInLibrary' status for {0} items in the cache.", libraryStatus.Count);
using var connection = CreateConnection();
await connection.OpenAsync();
using var transaction = connection.BeginTransaction();
var sql = "UPDATE CatalogItems SET IsInLibrary = @IsInLibrary WHERE TmdbId = @TmdbId;";
// Dapper can execute this against a list of objects for a bulk update
var updateParams = libraryStatus.Select(kvp => new { TmdbId = kvp.Key, IsInLibrary = kvp.Value });
await connection.ExecuteAsync(sql, updateParams, transaction);
transaction.Commit();
}
public async Task<IEnumerable<CatalogItem>> GetAllCachedItems()
{
using var connection = CreateConnection();
// Return all items for grouping in the API.
return await connection.QueryAsync<CatalogItem>("SELECT * FROM CatalogItems;");
}
public async Task<IEnumerable<int>> GetDistinctTmdbIdsFromCache()
{
using var connection = CreateConnection();
return await connection.QueryAsync<int>("SELECT DISTINCT TmdbId FROM CatalogItems;");
}
private static int? GetYearFromDateString(string? date)
{
if (string.IsNullOrEmpty(date) || date.Length < 4)
{
return null;
}
if (int.TryParse(date.AsSpan(0, 4), out var year))
{
return year;
}
return null;
}
}
}