fix semver function, flawed tests

refactor: centralize metadata provider
This commit is contained in:
5rahim
2024-09-15 20:17:01 -04:00
parent ac4f783549
commit 73591bbd55
63 changed files with 1210 additions and 869 deletions

11
.golangci.yml Normal file
View File

@@ -0,0 +1,11 @@
run:
concurrency: 4
timeout: 1m
issues-exit-code: 1
tests: true
linters:
disable-all: true
enable:
- exhaustruct

View File

@@ -23586,75 +23586,15 @@
"comments": []
},
{
"filepath": "../internal/api/metadata/provider.go",
"filename": "provider.go",
"name": "Provider",
"formattedName": "Metadata_Provider",
"filepath": "../internal/api/metadata/anime.go",
"filename": "anime.go",
"name": "AnimeWrapperImpl",
"formattedName": "Metadata_AnimeWrapperImpl",
"package": "metadata",
"fields": [
{
"name": "logger",
"jsonName": "logger",
"goType": "zerolog.Logger",
"typescriptType": "Logger",
"usedStructName": "zerolog.Logger",
"required": false,
"public": false,
"comments": []
},
{
"name": "fileCacher",
"jsonName": "fileCacher",
"goType": "filecache.Cacher",
"typescriptType": "Filecache_Cacher",
"usedStructName": "filecache.Cacher",
"required": false,
"public": false,
"comments": []
}
],
"comments": []
},
{
"filepath": "../internal/api/metadata/provider.go",
"filename": "provider.go",
"name": "NewProviderOptions",
"formattedName": "Metadata_NewProviderOptions",
"package": "metadata",
"fields": [
{
"name": "Logger",
"jsonName": "Logger",
"goType": "zerolog.Logger",
"typescriptType": "Logger",
"usedStructName": "zerolog.Logger",
"required": false,
"public": true,
"comments": []
},
{
"name": "FileCacher",
"jsonName": "FileCacher",
"goType": "filecache.Cacher",
"typescriptType": "Filecache_Cacher",
"usedStructName": "filecache.Cacher",
"required": false,
"public": true,
"comments": []
}
],
"comments": []
},
{
"filepath": "../internal/api/metadata/wrapper.go",
"filename": "wrapper.go",
"name": "MediaWrapper",
"formattedName": "Metadata_MediaWrapper",
"package": "metadata",
"fields": [
{
"name": "anizipMedia",
"jsonName": "anizipMedia",
"name": "metadata",
"jsonName": "metadata",
"goType": "",
"typescriptType": "any",
"required": true,
@@ -23705,22 +23645,61 @@
"comments": []
},
{
"filepath": "../internal/api/metadata/wrapper.go",
"filename": "wrapper.go",
"name": "NewMediaWrapperOptions",
"formattedName": "Metadata_NewMediaWrapperOptions",
"filepath": "../internal/api/metadata/provider.go",
"filename": "provider.go",
"name": "ProviderImpl",
"formattedName": "Metadata_ProviderImpl",
"package": "metadata",
"fields": [
{
"name": "AnizipMedia",
"jsonName": "AnizipMedia",
"goType": "anizip.Media",
"typescriptType": "Anizip_Media",
"usedStructName": "anizip.Media",
"name": "logger",
"jsonName": "logger",
"goType": "zerolog.Logger",
"typescriptType": "Logger",
"usedStructName": "zerolog.Logger",
"required": false,
"public": true,
"public": false,
"comments": []
},
{
"name": "fileCacher",
"jsonName": "fileCacher",
"goType": "filecache.Cacher",
"typescriptType": "Filecache_Cacher",
"usedStructName": "filecache.Cacher",
"required": false,
"public": false,
"comments": []
},
{
"name": "animeMetadataCache",
"jsonName": "animeMetadataCache",
"goType": "",
"typescriptType": "any",
"required": false,
"public": false,
"comments": []
},
{
"name": "anizipCache",
"jsonName": "anizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"required": false,
"public": false,
"comments": []
}
],
"comments": []
},
{
"filepath": "../internal/api/metadata/provider.go",
"filename": "provider.go",
"name": "NewProviderImplOptions",
"formattedName": "Metadata_NewProviderImplOptions",
"package": "metadata",
"fields": [
{
"name": "Logger",
"jsonName": "Logger",
@@ -23730,15 +23709,217 @@
"required": false,
"public": true,
"comments": []
},
{
"name": "FileCacher",
"jsonName": "FileCacher",
"goType": "filecache.Cacher",
"typescriptType": "Filecache_Cacher",
"usedStructName": "filecache.Cacher",
"required": false,
"public": true,
"comments": []
}
],
"comments": []
},
{
"filepath": "../internal/api/metadata/wrapper.go",
"filename": "wrapper.go",
"name": "MediaWrapperEpisodeMetadata",
"formattedName": "Metadata_MediaWrapperEpisodeMetadata",
"filepath": "../internal/api/metadata/types.go",
"filename": "types.go",
"name": "Platform",
"formattedName": "Metadata_Platform",
"package": "metadata",
"fields": [],
"aliasOf": {
"goType": "string",
"typescriptType": "string",
"declaredValues": [
"\"anilist\"",
"\"mal\""
]
},
"comments": []
},
{
"filepath": "../internal/api/metadata/types.go",
"filename": "types.go",
"name": "AnimeMetadata",
"formattedName": "Metadata_AnimeMetadata",
"package": "metadata",
"fields": [
{
"name": "Titles",
"jsonName": "titles",
"goType": "map[string]string",
"typescriptType": "Record\u003cstring, string\u003e",
"required": false,
"public": true,
"comments": []
},
{
"name": "Episodes",
"jsonName": "episodes",
"goType": "map[string]EpisodeMetadata",
"typescriptType": "Record\u003cstring, Metadata_EpisodeMetadata\u003e",
"usedStructName": "metadata.EpisodeMetadata",
"required": false,
"public": true,
"comments": []
},
{
"name": "EpisodeCount",
"jsonName": "episodeCount",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "SpecialCount",
"jsonName": "specialCount",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "Mappings",
"jsonName": "mappings",
"goType": "AnimeMappings",
"typescriptType": "Metadata_AnimeMappings",
"usedStructName": "metadata.AnimeMappings",
"required": false,
"public": true,
"comments": []
}
],
"comments": []
},
{
"filepath": "../internal/api/metadata/types.go",
"filename": "types.go",
"name": "AnimeMappings",
"formattedName": "Metadata_AnimeMappings",
"package": "metadata",
"fields": [
{
"name": "AnimeplanetId",
"jsonName": "animeplanetId",
"goType": "string",
"typescriptType": "string",
"required": true,
"public": true,
"comments": []
},
{
"name": "KitsuId",
"jsonName": "kitsuId",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "MalId",
"jsonName": "malId",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "Type",
"jsonName": "type",
"goType": "string",
"typescriptType": "string",
"required": true,
"public": true,
"comments": []
},
{
"name": "AnilistId",
"jsonName": "anilistId",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "AnisearchId",
"jsonName": "anisearchId",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "AnidbId",
"jsonName": "anidbId",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "NotifymoeId",
"jsonName": "notifymoeId",
"goType": "string",
"typescriptType": "string",
"required": true,
"public": true,
"comments": []
},
{
"name": "LivechartId",
"jsonName": "livechartId",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "ThetvdbId",
"jsonName": "thetvdbId",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "ImdbId",
"jsonName": "imdbId",
"goType": "string",
"typescriptType": "string",
"required": true,
"public": true,
"comments": []
},
{
"name": "ThemoviedbId",
"jsonName": "themoviedbId",
"goType": "string",
"typescriptType": "string",
"required": true,
"public": true,
"comments": []
}
],
"comments": []
},
{
"filepath": "../internal/api/metadata/types.go",
"filename": "types.go",
"name": "EpisodeMetadata",
"formattedName": "Metadata_EpisodeMetadata",
"package": "metadata",
"fields": [
{
@@ -23746,40 +23927,34 @@
"jsonName": "anidbId",
"goType": "int",
"typescriptType": "number",
"required": false,
"required": true,
"public": true,
"comments": [
" Episode AniDB ID"
]
"comments": []
},
{
"name": "TvdbId",
"jsonName": "tvdbId",
"goType": "int64",
"goType": "int",
"typescriptType": "number",
"required": false,
"required": true,
"public": true,
"comments": [
" Episode TVDB ID"
]
"comments": []
},
{
"name": "Title",
"jsonName": "title",
"goType": "string",
"typescriptType": "string",
"required": false,
"required": true,
"public": true,
"comments": [
" Episode title"
]
"comments": []
},
{
"name": "Image",
"jsonName": "image",
"goType": "string",
"typescriptType": "string",
"required": false,
"required": true,
"public": true,
"comments": []
},
@@ -23788,7 +23963,7 @@
"jsonName": "airDate",
"goType": "string",
"typescriptType": "string",
"required": false,
"required": true,
"public": true,
"comments": []
},
@@ -23797,7 +23972,7 @@
"jsonName": "length",
"goType": "int",
"typescriptType": "number",
"required": false,
"required": true,
"public": true,
"comments": []
},
@@ -23806,7 +23981,7 @@
"jsonName": "summary",
"goType": "string",
"typescriptType": "string",
"required": false,
"required": true,
"public": true,
"comments": []
},
@@ -23815,7 +23990,7 @@
"jsonName": "overview",
"goType": "string",
"typescriptType": "string",
"required": false,
"required": true,
"public": true,
"comments": []
},
@@ -23824,7 +23999,43 @@
"jsonName": "episodeNumber",
"goType": "int",
"typescriptType": "number",
"required": false,
"required": true,
"public": true,
"comments": []
},
{
"name": "Episode",
"jsonName": "episode",
"goType": "string",
"typescriptType": "string",
"required": true,
"public": true,
"comments": []
},
{
"name": "SeasonNumber",
"jsonName": "seasonNumber",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "AbsoluteEpisodeNumber",
"jsonName": "absoluteEpisodeNumber",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
},
{
"name": "AnidbEid",
"jsonName": "anidbEid",
"goType": "int",
"typescriptType": "number",
"required": true,
"public": true,
"comments": []
}
@@ -24962,18 +25173,6 @@
"public": true,
"comments": []
},
{
"name": "AnizipCache",
"jsonName": "AnizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"required": false,
"public": true,
"comments": [
" AnizipCache holds fetched AniZip media for 30 minutes. (used by route handlers)"
]
},
{
"name": "AnilistClient",
"jsonName": "AnilistClient",
@@ -30434,10 +30633,11 @@
"comments": []
},
{
"name": "anizipMediaCache",
"jsonName": "anizipMediaCache",
"goType": "",
"typescriptType": "any",
"name": "metadataProvider",
"jsonName": "metadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": false,
"comments": []
@@ -31821,16 +32021,6 @@
" All local files"
]
},
{
"name": "AnizipCache",
"jsonName": "AnizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"required": false,
"public": true,
"comments": []
},
{
"name": "AnimeCollection",
"jsonName": "AnimeCollection",
@@ -31985,11 +32175,11 @@
"comments": []
},
{
"name": "AnizipMedia",
"jsonName": "AnizipMedia",
"goType": "anizip.Media",
"typescriptType": "Anizip_Media",
"usedStructName": "anizip.Media",
"name": "AnimeMetadata",
"jsonName": "AnimeMetadata",
"goType": "metadata.AnimeMetadata",
"typescriptType": "Metadata_AnimeMetadata",
"usedStructName": "metadata.AnimeMetadata",
"required": false,
"public": true,
"comments": []
@@ -32824,16 +33014,6 @@
"public": true,
"comments": []
},
{
"name": "AnizipCache",
"jsonName": "AnizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"required": false,
"public": true,
"comments": []
},
{
"name": "Platform",
"jsonName": "Platform",
@@ -33107,11 +33287,11 @@
"comments": []
},
{
"name": "AnizipMedia",
"jsonName": "AnizipMedia",
"goType": "anizip.Media",
"typescriptType": "Anizip_Media",
"usedStructName": "anizip.Media",
"name": "AnimeMetadata",
"jsonName": "AnimeMetadata",
"goType": "metadata.AnimeMetadata",
"typescriptType": "Metadata_AnimeMetadata",
"usedStructName": "metadata.AnimeMetadata",
"required": false,
"public": true,
"comments": [
@@ -33607,16 +33787,6 @@
"public": true,
"comments": []
},
{
"name": "AnizipCache",
"jsonName": "AnizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"required": false,
"public": true,
"comments": []
},
{
"name": "SilencedMediaIds",
"jsonName": "SilencedMediaIds",
@@ -33916,11 +34086,11 @@
"comments": []
},
{
"name": "anizipCache",
"jsonName": "anizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"name": "metadataProvider",
"jsonName": "metadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": false,
"comments": []
@@ -34032,11 +34202,11 @@
"comments": []
},
{
"name": "AnizipCache",
"jsonName": "AnizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"name": "MetadataProvider",
"jsonName": "MetadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": true,
"comments": []
@@ -34211,6 +34381,16 @@
"comments": [
" AutoDownloader instance is required to refresh queue."
]
},
{
"name": "metadataProvider",
"jsonName": "metadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": false,
"comments": []
}
],
"comments": []
@@ -34290,6 +34470,16 @@
"required": false,
"public": true,
"comments": []
},
{
"name": "MetadataProvider",
"jsonName": "MetadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": true,
"comments": []
}
],
"comments": []
@@ -35244,16 +35434,6 @@
"public": true,
"comments": []
},
{
"name": "AnizipCache",
"jsonName": "AnizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"required": false,
"public": true,
"comments": []
},
{
"name": "Platform",
"jsonName": "Platform",
@@ -35264,6 +35444,16 @@
"public": true,
"comments": []
},
{
"name": "MetadataProvider",
"jsonName": "MetadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": true,
"comments": []
},
{
"name": "AnilistRateLimiter",
"jsonName": "AnilistRateLimiter",
@@ -35582,6 +35772,16 @@
"public": true,
"comments": []
},
{
"name": "MetadataProvider",
"jsonName": "MetadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": true,
"comments": []
},
{
"name": "LocalFiles",
"jsonName": "LocalFiles",
@@ -35602,16 +35802,6 @@
"public": true,
"comments": []
},
{
"name": "AnizipCache",
"jsonName": "AnizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"required": false,
"public": true,
"comments": []
},
{
"name": "Logger",
"jsonName": "Logger",
@@ -35672,11 +35862,11 @@
"comments": []
},
{
"name": "anizipCache",
"jsonName": "anizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"name": "metadataProvider",
"jsonName": "metadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": false,
"comments": []
@@ -35732,11 +35922,11 @@
"comments": []
},
{
"name": "anizipMedia",
"jsonName": "anizipMedia",
"goType": "anizip.Media",
"typescriptType": "Anizip_Media",
"usedStructName": "anizip.Media",
"name": "animeMetadata",
"jsonName": "animeMetadata",
"goType": "metadata.AnimeMetadata",
"typescriptType": "Metadata_AnimeMetadata",
"usedStructName": "metadata.AnimeMetadata",
"required": false,
"public": false,
"comments": []
@@ -35900,6 +36090,16 @@
"required": false,
"public": true,
"comments": []
},
{
"name": "MetadataProvider",
"jsonName": "MetadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": true,
"comments": []
}
],
"comments": []
@@ -39673,15 +39873,6 @@
"public": true,
"comments": []
},
{
"name": "MpvType",
"jsonName": "MpvType",
"goType": "string",
"typescriptType": "string",
"required": true,
"public": true,
"comments": []
},
{
"name": "WSEventManager",
"jsonName": "WSEventManager",
@@ -43552,6 +43743,15 @@
],
"comments": []
},
{
"filepath": "../internal/offline/mock.go",
"filename": "mock.go",
"name": "MockHub",
"formattedName": "Offline_MockHub",
"package": "offline",
"fields": [],
"comments": []
},
{
"filepath": "../internal/offline/models.go",
"filename": "models.go",
@@ -44019,15 +44219,6 @@
],
"comments": []
},
{
"filepath": "../internal/offline/test_helper.go",
"filename": "test_helper.go",
"name": "MockHub",
"formattedName": "Offline_MockHub",
"package": "offline",
"fields": [],
"comments": []
},
{
"filepath": "../internal/onlinestream/providers/gogoanime.go",
"filename": "gogoanime.go",
@@ -44171,11 +44362,11 @@
"comments": []
},
{
"name": "anizipCache",
"jsonName": "anizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"name": "metadataProvider",
"jsonName": "metadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": false,
"comments": []
@@ -44420,11 +44611,11 @@
"comments": []
},
{
"name": "AnizipCache",
"jsonName": "AnizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"name": "MetadataProvider",
"jsonName": "MetadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": true,
"comments": []
@@ -49224,6 +49415,16 @@
"public": false,
"comments": []
},
{
"name": "metadataProvider",
"jsonName": "metadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": false,
"comments": []
},
{
"name": "activeTorrentCountCtxCancel",
"jsonName": "activeTorrentCountCtxCancel",
@@ -49302,6 +49503,16 @@
"required": true,
"public": true,
"comments": []
},
{
"name": "MetadataProvider",
"jsonName": "MetadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": true,
"comments": []
}
],
"comments": []
@@ -49683,6 +49894,16 @@
"required": false,
"public": false,
"comments": []
},
{
"name": "metadataProvider",
"jsonName": "metadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": false,
"comments": []
}
],
"comments": []
@@ -49818,6 +50039,16 @@
"required": false,
"public": true,
"comments": []
},
{
"name": "MetadataProvider",
"jsonName": "MetadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": true,
"comments": []
}
],
"comments": []
@@ -50837,16 +51068,6 @@
"public": false,
"comments": []
},
{
"name": "anizipCache",
"jsonName": "anizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"required": false,
"public": false,
"comments": []
},
{
"name": "settings",
"jsonName": "settings",
@@ -51368,11 +51589,11 @@
"comments": []
},
{
"name": "AnizipCache",
"jsonName": "AnizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"name": "MetadataProvider",
"jsonName": "MetadataProvider",
"goType": "metadata.Provider",
"typescriptType": "Metadata_Provider",
"usedStructName": "metadata.Provider",
"required": false,
"public": true,
"comments": []
@@ -51689,16 +51910,6 @@
"public": false,
"comments": []
},
{
"name": "anizipCache",
"jsonName": "anizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"required": false,
"public": false,
"comments": []
},
{
"name": "baseAnimeCache",
"jsonName": "baseAnimeCache",
@@ -51831,16 +52042,6 @@
"public": true,
"comments": []
},
{
"name": "AnizipCache",
"jsonName": "AnizipCache",
"goType": "anizip.Cache",
"typescriptType": "Anizip_Cache",
"usedStructName": "anizip.Cache",
"required": false,
"public": true,
"comments": []
},
{
"name": "TorrentRepository",
"jsonName": "TorrentRepository",

View File

@@ -1,10 +1,5 @@
package anizip
import (
"regexp"
"strconv"
)
func (m *Media) GetTitle() string {
if m == nil {
return ""
@@ -68,40 +63,3 @@ func (e *Episode) GetTitle() string {
}
return ""
}
func ExtractEpisodeInteger(s string) (int, bool) {
pattern := "[0-9]+"
regex := regexp.MustCompile(pattern)
// Find the first match in the input string.
match := regex.FindString(s)
if match != "" {
// Convert the matched string to an integer.
num, err := strconv.Atoi(match)
if err != nil {
return 0, false
}
return num, true
}
return 0, false
}
func OffsetEpisode(s string, offset int) string {
pattern := "([0-9]+)"
regex := regexp.MustCompile(pattern)
// Replace the first matched integer with the incremented value.
result := regex.ReplaceAllStringFunc(s, func(matched string) string {
num, err := strconv.Atoi(matched)
if err == nil {
num = num + offset
return strconv.Itoa(num)
} else {
return matched
}
})
return result
}

View File

@@ -5,78 +5,86 @@ import (
"fmt"
"github.com/rs/zerolog"
"github.com/samber/mo"
"regexp"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/mappings"
"seanime/internal/api/tvdb"
"seanime/internal/util"
"seanime/internal/util/filecache"
"strconv"
"strings"
"time"
)
type (
AnimeWrapperImpl struct {
anizipMedia mo.Option[*anizip.Media]
baseAnime *anilist.BaseAnime
fileCacher *filecache.Cacher
logger *zerolog.Logger
metadata mo.Option[*AnimeMetadata]
baseAnime *anilist.BaseAnime
fileCacher *filecache.Cacher
logger *zerolog.Logger
// TVDB
tvdbEpisodes []*tvdb.Episode
}
)
func (aw *AnimeWrapperImpl) GetEpisodeMetadata(epNum int) EpisodeMetadata {
meta := EpisodeMetadata{
EpisodeNumber: epNum,
func (aw *AnimeWrapperImpl) GetEpisodeMetadata(epNum int) (ret EpisodeMetadata) {
ret = EpisodeMetadata{
AnidbId: 0,
TvdbId: 0,
Title: "",
Image: "",
AirDate: "",
Length: 0,
Summary: "",
Overview: "",
EpisodeNumber: epNum,
Episode: strconv.Itoa(epNum),
SeasonNumber: 0,
AbsoluteEpisodeNumber: 0,
AnidbEid: 0,
}
defer util.HandlePanicInModuleThen("api/metadata/GetEpisodeMetadata", func() {})
hasTVDBMetadata := aw.tvdbEpisodes != nil && len(aw.tvdbEpisodes) > 0
anizipEpisode := mo.None[*anizip.Episode]()
if aw.anizipMedia.IsAbsent() {
meta.Image = aw.baseAnime.GetBannerImageSafe()
episode := mo.None[*EpisodeMetadata]()
if aw.metadata.IsAbsent() {
ret.Image = aw.baseAnime.GetBannerImageSafe()
} else {
anizipEpisodeF, found := aw.anizipMedia.MustGet().FindEpisode(strconv.Itoa(epNum))
episodeF, found := aw.metadata.MustGet().FindEpisode(strconv.Itoa(epNum))
if found {
meta.AnidbId = anizipEpisodeF.AnidbEid
anizipEpisode = mo.Some(anizipEpisodeF)
episode = mo.Some(episodeF)
}
}
// If we don't have AniZip metadata, just return the metadata containing the image
if anizipEpisode.IsAbsent() {
return meta
if episode.IsAbsent() {
return ret
}
// TVDB metadata
ret = *episode.MustGet()
// If TVDB metadata is available, use it to populate the image
if hasTVDBMetadata {
tvdbEpisode, found := aw.GetTVDBEpisodeByNumber(epNum)
if found {
meta.Image = tvdbEpisode.Image
meta.TvdbId = tvdbEpisode.ID
ret.Image = tvdbEpisode.Image
ret.TvdbId = int(tvdbEpisode.ID)
}
}
if meta.Image == "" {
// If TVDB image is not set, use AniZip image, if that is not set, use the AniList banner image
if ret.Image == "" {
// Set AniZip image if TVDB image is not set
if anizipEpisode.MustGet().Image != "" {
meta.Image = anizipEpisode.MustGet().Image
if episode.MustGet().Image != "" {
ret.Image = episode.MustGet().Image
} else {
// If AniZip image is not set, use the base media image
meta.Image = aw.baseAnime.GetBannerImageSafe()
ret.Image = aw.baseAnime.GetBannerImageSafe()
}
}
meta.AirDate = anizipEpisode.MustGet().AirDate
meta.Length = anizipEpisode.MustGet().Length
if anizipEpisode.MustGet().Runtime > 0 {
meta.Length = anizipEpisode.MustGet().Runtime
}
meta.Summary = strings.ReplaceAll(anizipEpisode.MustGet().Summary, "`", "'")
meta.Overview = strings.ReplaceAll(anizipEpisode.MustGet().Overview, "`", "'")
return meta
return ret
}
func getTvdbIDFromAnimeLists(anidbID int) (tvdbID int, ok bool) {
@@ -89,17 +97,17 @@ func getTvdbIDFromAnimeLists(anidbID int) (tvdbID int, ok bool) {
func (aw *AnimeWrapperImpl) EmptyTVDBEpisodesBucket(mediaId int) error {
if aw.anizipMedia.IsAbsent() {
if aw.metadata.IsAbsent() {
return nil
}
// Get TVDB ID
var tvdbId int
tvdbId = aw.anizipMedia.MustGet().Mappings.ThetvdbID
tvdbId = aw.metadata.MustGet().Mappings.ThetvdbId
if tvdbId == 0 {
if aw.anizipMedia.MustGet().Mappings.AnidbID > 0 {
if aw.metadata.MustGet().Mappings.AnidbId > 0 {
// Try to get it from the mappings
tvdbId, _ = getTvdbIDFromAnimeLists(aw.anizipMedia.MustGet().Mappings.AnidbID)
tvdbId, _ = getTvdbIDFromAnimeLists(aw.metadata.MustGet().Mappings.AnidbId)
}
}
@@ -113,17 +121,17 @@ func (aw *AnimeWrapperImpl) EmptyTVDBEpisodesBucket(mediaId int) error {
func (aw *AnimeWrapperImpl) GetTVDBEpisodes(populate bool) ([]*tvdb.Episode, error) {
key := aw.baseAnime.GetID()
if aw.anizipMedia.IsAbsent() {
return nil, errors.New("metadata: anizip media is absent")
if aw.metadata.IsAbsent() {
return nil, errors.New("metadata: anime metadata is absent")
}
// Get TVDB ID
var tvdbId int
tvdbId = aw.anizipMedia.MustGet().Mappings.ThetvdbID
tvdbId = aw.metadata.MustGet().Mappings.ThetvdbId
if tvdbId == 0 {
if aw.anizipMedia.MustGet().Mappings.AnidbID > 0 {
if aw.metadata.MustGet().Mappings.AnidbId > 0 {
// Try to get it from the mappings
tvdbId, _ = getTvdbIDFromAnimeLists(aw.anizipMedia.MustGet().Mappings.AnidbID)
tvdbId, _ = getTvdbIDFromAnimeLists(aw.metadata.MustGet().Mappings.AnidbId)
}
}
@@ -145,14 +153,15 @@ func (aw *AnimeWrapperImpl) GetTVDBEpisodes(populate bool) ([]*tvdb.Episode, err
var err error
tv := tvdb.NewTVDB(&tvdb.NewTVDBOptions{
ApiKey: "", // Empty
Logger: aw.logger,
})
episodes, err = tv.FetchSeriesEpisodes(tvdbId, tvdb.FilterEpisodeMediaInfo{
Year: aw.baseAnime.GetStartDate().GetYear(),
Month: aw.baseAnime.GetStartDate().GetMonth(),
TotalEp: aw.anizipMedia.MustGet().GetMainEpisodeCount(),
AbsoluteOffset: aw.anizipMedia.MustGet().GetOffset(),
TotalEp: aw.metadata.MustGet().GetMainEpisodeCount(),
AbsoluteOffset: aw.metadata.MustGet().GetOffset(),
})
if err != nil {
return nil, err
@@ -181,3 +190,40 @@ func (aw *AnimeWrapperImpl) GetTVDBEpisodeByNumber(number int) (*tvdb.Episode, b
return nil, false
}
func ExtractEpisodeInteger(s string) (int, bool) {
pattern := "[0-9]+"
regex := regexp.MustCompile(pattern)
// Find the first match in the input string.
match := regex.FindString(s)
if match != "" {
// Convert the matched string to an integer.
num, err := strconv.Atoi(match)
if err != nil {
return 0, false
}
return num, true
}
return 0, false
}
func OffsetAnidbEpisode(s string, offset int) string {
pattern := "([0-9]+)"
regex := regexp.MustCompile(pattern)
// Replace the first matched integer with the incremented value.
result := regex.ReplaceAllStringFunc(s, func(matched string) string {
num, err := strconv.Atoi(matched)
if err == nil {
num = num + offset
return strconv.Itoa(num)
} else {
return matched
}
})
return result
}

View File

@@ -1,4 +1,4 @@
package anizip
package metadata
import (
"testing"
@@ -17,9 +17,9 @@ func TestOffsetEpisode(t *testing.T) {
}
for _, c := range cases {
actual := OffsetEpisode(c.input, 1)
actual := OffsetAnidbEpisode(c.input, 1)
if actual != c.expected {
t.Errorf("OffsetEpisode(%s, 1) == %s, expected %s", c.input, actual, c.expected)
t.Errorf("OffsetAnidbEpisode(%s, 1) == %s, expected %s", c.input, actual, c.expected)
}
}

View File

@@ -0,0 +1,17 @@
package metadata
import (
"github.com/stretchr/testify/require"
"seanime/internal/util"
"seanime/internal/util/filecache"
"testing"
)
func GetMockProvider(t *testing.T) Provider {
filecacher, err := filecache.NewCacher(t.TempDir())
require.NoError(t, err)
return NewProvider(&NewProviderImplOptions{
Logger: util.NewLogger(),
FileCacher: filecacher,
})
}

View File

@@ -1,18 +1,24 @@
package metadata
import (
"fmt"
"github.com/rs/zerolog"
"github.com/samber/mo"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/tvdb"
"seanime/internal/util/filecache"
"seanime/internal/util/result"
"strings"
"time"
)
type (
ProviderImpl struct {
logger *zerolog.Logger
fileCacher *filecache.Cacher
logger *zerolog.Logger
fileCacher *filecache.Cacher
animeMetadataCache *result.Cache[string, *AnimeMetadata]
anizipCache *anizip.Cache
}
NewProviderImplOptions struct {
@@ -21,31 +27,112 @@ type (
}
)
func GetAnimeMetadataCacheKey(platform Platform, mId int) string {
return fmt.Sprintf("%s$%d", platform, mId)
}
// NewProvider creates a new metadata provider.
func NewProvider(options *NewProviderImplOptions) Provider {
return &ProviderImpl{
logger: options.Logger,
fileCacher: options.FileCacher,
logger: options.Logger,
fileCacher: options.FileCacher,
animeMetadataCache: result.NewCache[string, *AnimeMetadata](),
anizipCache: anizip.NewCache(),
}
}
// GetCache returns the anime metadata cache.
func (p *ProviderImpl) GetCache() *result.Cache[string, *AnimeMetadata] {
return p.animeMetadataCache
}
// GetAnimeMetadata fetches anime metadata from api.ani.zip.
func (p *ProviderImpl) GetAnimeMetadata(platform Platform, mId int) (ret *AnimeMetadata, err error) {
ret, ok := p.animeMetadataCache.Get(GetAnimeMetadataCacheKey(platform, mId))
if ok {
return ret, nil
}
anizipMedia, err := anizip.FetchAniZipMediaC(string(platform), mId, p.anizipCache)
if err != nil || anizipMedia == nil {
return nil, err
}
ret = &AnimeMetadata{
Titles: make(map[string]string),
Episodes: make(map[string]*EpisodeMetadata),
EpisodeCount: 0,
SpecialCount: 0,
Mappings: &AnimeMappings{},
}
ret.Titles = anizipMedia.Titles
ret.EpisodeCount = anizipMedia.EpisodeCount
ret.SpecialCount = anizipMedia.SpecialCount
ret.Mappings.AnimeplanetId = anizipMedia.Mappings.AnimeplanetID
ret.Mappings.KitsuId = anizipMedia.Mappings.KitsuID
ret.Mappings.MalId = anizipMedia.Mappings.MalID
ret.Mappings.Type = anizipMedia.Mappings.Type
ret.Mappings.AnilistId = anizipMedia.Mappings.AnilistID
ret.Mappings.AnisearchId = anizipMedia.Mappings.AnisearchID
ret.Mappings.AnidbId = anizipMedia.Mappings.AnidbID
ret.Mappings.NotifymoeId = anizipMedia.Mappings.NotifymoeID
ret.Mappings.LivechartId = anizipMedia.Mappings.LivechartID
ret.Mappings.ThetvdbId = anizipMedia.Mappings.ThetvdbID
ret.Mappings.ImdbId = anizipMedia.Mappings.ImdbID
ret.Mappings.ThemoviedbId = anizipMedia.Mappings.ThemoviedbID
for key, anizipEp := range anizipMedia.Episodes {
em := &EpisodeMetadata{
AnidbId: anizipEp.AnidbEid,
TvdbId: anizipEp.TvdbEid,
Title: anizipEp.GetTitle(),
Image: anizipEp.Image,
AirDate: anizipEp.AirDate,
Length: anizipEp.Runtime,
Summary: strings.ReplaceAll(anizipEp.Summary, "`", "'"),
Overview: strings.ReplaceAll(anizipEp.Overview, "`", "'"),
EpisodeNumber: anizipEp.EpisodeNumber,
Episode: anizipEp.Episode,
SeasonNumber: anizipEp.SeasonNumber,
AbsoluteEpisodeNumber: anizipEp.AbsoluteEpisodeNumber,
AnidbEid: anizipEp.AnidbEid,
}
if em.Length == 0 && anizipEp.Length > 0 {
em.Length = anizipEp.Length
}
if em.Summary == "" && anizipEp.Overview != "" {
em.Summary = anizipEp.Overview
}
if em.Overview == "" && anizipEp.Summary != "" {
em.Overview = anizipEp.Summary
}
ret.Episodes[key] = em
}
p.animeMetadataCache.SetT(GetAnimeMetadataCacheKey(platform, mId), ret, 1*time.Hour)
return ret, nil
}
// GetAnimeMetadataWrapper creates a new anime wrapper.
//
// Example:
//
// metadataProvider.GetAnimeMetadataWrapper(media, anizipMedia)
// metadataProvider.GetAnimeMetadataWrapper(media, metadata)
// metadataProvider.GetAnimeMetadataWrapper(media, nil)
func (p *ProviderImpl) GetAnimeMetadataWrapper(media *anilist.BaseAnime, anizipMedia *anizip.Media) AnimeMetadataWrapper {
func (p *ProviderImpl) GetAnimeMetadataWrapper(media *anilist.BaseAnime, metadata *AnimeMetadata) AnimeMetadataWrapper {
aw := &AnimeWrapperImpl{
anizipMedia: mo.None[*anizip.Media](),
metadata: mo.None[*AnimeMetadata](),
baseAnime: media,
fileCacher: p.fileCacher,
logger: p.logger,
tvdbEpisodes: make([]*tvdb.Episode, 0),
}
if anizipMedia != nil {
aw.anizipMedia = mo.Some(anizipMedia)
if metadata != nil {
aw.metadata = mo.Some(metadata)
}
episodes, err := aw.GetTVDBEpisodes(false)

View File

@@ -1,22 +0,0 @@
package metadata
import (
"seanime/internal/util"
"seanime/internal/util/filecache"
"testing"
)
func TestGetMockProvider(t *testing.T) Provider {
tempDir := t.TempDir()
fileCacher, err := filecache.NewCacher(tempDir)
if err != nil {
t.Fatalf("could not create filecacher: %v", err)
}
metadataProvider := NewProvider(&NewProviderImplOptions{
Logger: util.NewLogger(),
FileCacher: fileCacher,
})
return metadataProvider
}

View File

@@ -2,25 +2,42 @@ package metadata
import (
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/tvdb"
"seanime/internal/util/result"
)
const (
AnilistPlatform Platform = "anilist"
MalPlatform Platform = "mal"
)
type (
Platform string
Provider interface {
GetAnimeMetadataWrapper(anime *anilist.BaseAnime, anizipMedia *anizip.Media) AnimeMetadataWrapper
// GetAnimeMetadata fetches anime metadata for the given platform from a source.
// In this case, the source is api.ani.zip.
GetAnimeMetadata(platform Platform, mId int) (*AnimeMetadata, error)
GetCache() *result.Cache[string, *AnimeMetadata]
// GetAnimeMetadataWrapper creates a wrapper for anime metadata.
GetAnimeMetadataWrapper(anime *anilist.BaseAnime, metadata *AnimeMetadata) AnimeMetadataWrapper
}
// AnimeMetadataWrapper is a container for anime metadata.
// This wrapper is used to get a more complete metadata object by getting data from multiple sources in the Provider.
// The user can request metadata to be fetched from TVDB as well, which will be stored in the cache.
AnimeMetadataWrapper interface {
// GetEpisodeMetadata combines metadata from multiple sources to create a single EpisodeMetadata object.
GetEpisodeMetadata(episodeNumber int) EpisodeMetadata
EmptyTVDBEpisodesBucket(mediaId int) error
GetTVDBEpisodes(populate bool) ([]*tvdb.Episode, error)
GetTVDBEpisodeByNumber(episodeNumber int) (*tvdb.Episode, bool)
}
)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type (
AnimeMetadata struct {
Titles map[string]string `json:"titles"`
@@ -47,7 +64,7 @@ type (
EpisodeMetadata struct {
AnidbId int `json:"anidbId"`
TvdbId int64 `json:"tvdbId"`
TvdbId int `json:"tvdbId"`
Title string `json:"title"`
Image string `json:"image"`
AirDate string `json:"airDate"`
@@ -55,8 +72,68 @@ type (
Summary string `json:"summary"`
Overview string `json:"overview"`
EpisodeNumber int `json:"episodeNumber"`
Episode string `json:"episode"`
SeasonNumber int `json:"seasonNumber"`
AbsoluteEpisodeNumber int `json:"absoluteEpisodeNumber"`
AnidbEid int `json:"anidbEid"`
}
)
func (m *AnimeMetadata) GetTitle() string {
if m == nil {
return ""
}
if len(m.Titles["en"]) > 0 {
return m.Titles["en"]
}
return m.Titles["ro"]
}
func (m *AnimeMetadata) GetMappings() *AnimeMappings {
if m == nil {
return &AnimeMappings{}
}
return m.Mappings
}
func (m *AnimeMetadata) FindEpisode(ep string) (*EpisodeMetadata, bool) {
if m.Episodes == nil {
return nil, false
}
episode, found := m.Episodes[ep]
if !found {
return nil, false
}
return episode, true
}
func (m *AnimeMetadata) GetMainEpisodeCount() int {
if m == nil {
return 0
}
return m.EpisodeCount
}
// GetOffset returns the offset of the first episode relative to the absolute episode number.
// e.g, if the first episode's absolute number is 13, then the offset is 12.
func (m *AnimeMetadata) GetOffset() int {
if m == nil {
return 0
}
firstEp, found := m.FindEpisode("1")
if !found {
return 0
}
if firstEp.AbsoluteEpisodeNumber == 0 {
return 0
}
return firstEp.AbsoluteEpisodeNumber - 1
}
func (e *EpisodeMetadata) GetTitle() string {
if e == nil {
return ""
}
return e.Title
}

View File

@@ -40,7 +40,7 @@ func TestTVDB_FetchSeriesEpisodes(t *testing.T) {
anizipMedia, err := anizip.FetchAniZipMedia("anilist", tt.anilistId)
if err != nil {
t.Fatalf("could not fetch anizip media for %s", tt.name)
t.Fatalf("could not fetch anime metadata for %s", tt.name)
}
tvdbId := anizipMedia.Mappings.ThetvdbID
@@ -124,7 +124,7 @@ func TestTVDB_FetchSeasons(t *testing.T) {
anizipMedia, err := anizip.FetchAniZipMedia("anilist", tt.anilistId)
if err != nil {
t.Fatalf("could not fetch anizip media for %s", tt.name)
t.Fatalf("could not fetch anime metadata for %s", tt.name)
}
tvdbId := anizipMedia.Mappings.ThetvdbID
@@ -174,14 +174,12 @@ func TestTVDB_fetchEpisodes(t *testing.T) {
anilistClient := anilist.TestGetMockAnilistClient()
tests := []struct {
name string
anilistId int
episodeNumber int
name string
anilistId int
}{
{
name: "Dungeon Meshi",
anilistId: 153518,
episodeNumber: 1,
name: "Dungeon Meshi",
anilistId: 153518,
},
{
name: "Boku no Kokoro no Yabai Yatsu 2nd Season",
@@ -226,7 +224,7 @@ func TestTVDB_fetchEpisodes(t *testing.T) {
anizipMedia, err := anizip.FetchAniZipMedia("anilist", tt.anilistId)
if err != nil {
t.Fatalf("could not fetch anizip media for %s", tt.name)
t.Fatalf("could not fetch anime metadata for %s", tt.name)
}
tvdbId := anizipMedia.Mappings.ThetvdbID
@@ -351,7 +349,7 @@ func TestTVDB_fetchEpisodesAbsolute(t *testing.T) {
anizipMedia, err := anizip.FetchAniZipMedia("anilist", tt.anilistId)
if err != nil {
t.Fatalf("could not fetch anizip media for %s", tt.name)
t.Fatalf("could not fetch anime metadata for %s", tt.name)
}
tvdbId := anizipMedia.Mappings.ThetvdbID

View File

@@ -4,7 +4,6 @@ import (
"github.com/rs/zerolog"
"runtime"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/constants"
"seanime/internal/continuity"
@@ -48,7 +47,6 @@ type (
TorrentClientRepository *torrent_client.Repository
TorrentRepository *torrent.Repository
Watcher *scanner.Watcher
AnizipCache *anizip.Cache // AnizipCache holds fetched AniZip media for 30 minutes. (used by route handlers)
AnilistClient anilist.AnilistClient
AnilistPlatform platform.Platform
FillerManager *fillermanager.FillerManager
@@ -155,29 +153,26 @@ func NewApp(configOpts *ConfigOptions, selfupdater *updater.SelfUpdater) *App {
// Anilist Platform
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistCW, logger)
// AniZip Cache
anizipCache := anizip.NewCache()
// File Cacher
fileCacher, err := filecache.NewCacher(cfg.Cache.Dir)
if err != nil {
logger.Fatal().Err(err).Msgf("app: Failed to initialize file cacher")
}
// Online Stream
onlinestreamRepository := onlinestream.NewRepository(&onlinestream.NewRepositoryOptions{
Logger: logger,
FileCacher: fileCacher,
AnizipCache: anizipCache,
Platform: anilistPlatform,
})
// Metadata Provider
metadataProvider := metadata.NewProvider(&metadata.NewProviderImplOptions{
Logger: logger,
FileCacher: fileCacher,
})
// Online Stream
onlinestreamRepository := onlinestream.NewRepository(&onlinestream.NewRepositoryOptions{
Logger: logger,
FileCacher: fileCacher,
MetadataProvider: metadataProvider,
Platform: anilistPlatform,
})
// Manga Repository
mangaRepository := manga.NewRepository(&manga.NewRepositoryOptions{
Logger: logger,
@@ -195,14 +190,13 @@ func NewApp(configOpts *ConfigOptions, selfupdater *updater.SelfUpdater) *App {
WSEventManager: wsEventManager,
})
extensionPlaygroundRepository := extension_playground.NewPlaygroundRepository(logger, anilistPlatform)
extensionPlaygroundRepository := extension_playground.NewPlaygroundRepository(logger, anilistPlatform, metadataProvider)
app := &App{
Config: cfg,
Database: database,
AnilistClient: anilistCW,
AnilistPlatform: anilistPlatform,
AnizipCache: anizipCache,
WSEventManager: wsEventManager,
Logger: logger,
Version: constants.Version,

View File

@@ -119,7 +119,7 @@ func (a *App) initModulesOnce() {
TorrentRepository: a.TorrentRepository,
Database: a.Database,
WSEventManager: a.WSEventManager,
AnizipCache: a.AnizipCache,
MetadataProvider: a.MetadataProvider,
})
if !a.IsOffline() {
@@ -132,12 +132,13 @@ func (a *App) initModulesOnce() {
// +---------------------+
a.AutoScanner = autoscanner.New(&autoscanner.NewAutoScannerOptions{
Database: a.Database,
Enabled: false, // Will be set in InitOrRefreshModules
AutoDownloader: a.AutoDownloader,
Platform: a.AnilistPlatform,
Logger: a.Logger,
WSEventManager: a.WSEventManager,
Database: a.Database,
Platform: a.AnilistPlatform,
Logger: a.Logger,
WSEventManager: a.WSEventManager,
Enabled: false, // Will be set in InitOrRefreshModules
AutoDownloader: a.AutoDownloader,
MetadataProvider: a.MetadataProvider,
})
// This is run in a goroutine
@@ -180,7 +181,6 @@ func (a *App) initModulesOnce() {
a.TorrentstreamRepository = torrentstream.NewRepository(&torrentstream.NewRepositoryOptions{
Logger: a.Logger,
AnizipCache: a.AnizipCache,
BaseAnimeCache: anilist.NewBaseAnimeCache(),
CompleteAnimeCache: anilist.NewCompleteAnimeCache(),
MetadataProvider: a.MetadataProvider,
@@ -332,6 +332,7 @@ func (a *App) InitOrRefreshModules() {
Transmission: trans,
TorrentRepository: a.TorrentRepository,
Provider: settings.Torrent.Default,
MetadataProvider: a.MetadataProvider,
})
a.TorrentClientRepository.InitActiveTorrentCount(settings.Torrent.ShowActiveTorrentCount, a.WSEventManager)

View File

@@ -11,7 +11,7 @@ import (
"github.com/rs/zerolog"
"runtime"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/extension"
"seanime/internal/extension_repo"
"seanime/internal/manga"
@@ -30,7 +30,7 @@ type (
platform platform.Platform
baseAnimeCache *result.Cache[int, *anilist.BaseAnime]
baseMangaCache *result.Cache[int, *anilist.BaseManga]
anizipMediaCache *result.Cache[int, *anizip.Media]
metadataProvider metadata.Provider
}
RunPlaygroundCodeResponse struct {
@@ -47,13 +47,13 @@ type (
}
)
func NewPlaygroundRepository(logger *zerolog.Logger, platform platform.Platform) *PlaygroundRepository {
func NewPlaygroundRepository(logger *zerolog.Logger, platform platform.Platform, metadataProvider metadata.Provider) *PlaygroundRepository {
return &PlaygroundRepository{
logger: logger,
platform: platform,
metadataProvider: metadataProvider,
baseAnimeCache: result.NewCache[int, *anilist.BaseAnime](),
baseMangaCache: result.NewCache[int, *anilist.BaseManga](),
anizipMediaCache: result.NewCache[int, *anizip.Media](),
}
}
@@ -148,7 +148,7 @@ func newPlaygroundResponse(logger *PlaygroundDebugLogger, value interface{}) *Ru
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (r *PlaygroundRepository) getAnime(mediaId int) (anime *anilist.BaseAnime, am *anizip.Media, err error) {
func (r *PlaygroundRepository) getAnime(mediaId int) (anime *anilist.BaseAnime, am *metadata.AnimeMetadata, err error) {
var ok bool
anime, ok = r.baseAnimeCache.Get(mediaId)
if !ok {
@@ -159,11 +159,7 @@ func (r *PlaygroundRepository) getAnime(mediaId int) (anime *anilist.BaseAnime,
r.baseAnimeCache.SetT(mediaId, anime, 24*time.Hour)
}
am, ok = r.anizipMediaCache.Get(mediaId)
if !ok {
am, _ = anizip.FetchAniZipMedia("anilist", mediaId)
r.anizipMediaCache.SetT(mediaId, am, 24*time.Hour)
}
am, _ = r.metadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, mediaId)
return anime, am, nil
}
@@ -196,7 +192,7 @@ func (r *PlaygroundRepository) runPlaygroundCodeAnimeTorrentProvider(ext *extens
}
// Fetch the anime
anime, anizipMedia, err := r.getAnime(int(mediaId))
anime, animeMetadata, err := r.getAnime(int(mediaId))
if err != nil {
return nil, err
}
@@ -256,15 +252,15 @@ func (r *PlaygroundRepository) runPlaygroundCodeAnimeTorrentProvider(ext *extens
anidbEID := 0
// Get the AniDB Anime ID and Episode ID
if anizipMedia != nil {
if animeMetadata != nil {
// Override absolute offset value of queryMedia
queryMedia.AbsoluteSeasonOffset = anizipMedia.GetOffset()
queryMedia.AbsoluteSeasonOffset = animeMetadata.GetOffset()
if anizipMedia.GetMappings() != nil {
if animeMetadata.GetMappings() != nil {
anidbAID = anizipMedia.GetMappings().AnidbID
anidbAID = animeMetadata.GetMappings().AnidbId
// Find Anizip Episode based on inputted episode number
anizipEpisode, found := anizipMedia.FindEpisode(strconv.Itoa(options.EpisodeNumber))
anizipEpisode, found := animeMetadata.FindEpisode(strconv.Itoa(options.EpisodeNumber))
if found {
anidbEID = anizipEpisode.AnidbEid
}

View File

@@ -4,6 +4,7 @@ import (
"github.com/stretchr/testify/require"
"os"
"seanime/internal/api/anilist"
"seanime/internal/api/metadata"
"seanime/internal/extension"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/test_utils"
@@ -19,8 +20,9 @@ func TestGojaAnimeTorrentProvider(t *testing.T) {
anilistClient := anilist.TestGetMockAnilistClient()
platform := anilist_platform.NewAnilistPlatform(anilistClient, logger)
metadataProvider := metadata.GetMockProvider(t)
repo := NewPlaygroundRepository(logger, platform)
repo := NewPlaygroundRepository(logger, platform, metadataProvider)
// Get the script
filepath := "../extension_repo/goja_torrent_test/my-torrent-provider.ts"

View File

@@ -6,11 +6,8 @@ import (
"seanime/internal/database/db_bridge"
"seanime/internal/library/anime"
"seanime/internal/torrentstream"
"seanime/internal/util/result"
)
var libraryCollectionCache = result.NewResultMap[int, *anime.LibraryCollection]()
// HandleGetLibraryCollection
//
// @summary returns the main local anime collection.
@@ -39,7 +36,6 @@ func HandleGetLibraryCollection(c *RouteCtx) error {
libraryCollection, err := anime.NewLibraryCollection(&anime.NewLibraryCollectionOptions{
AnimeCollection: animeCollection,
Platform: c.App.AnilistPlatform,
AnizipCache: c.App.AnizipCache,
LocalFiles: lfs,
MetadataProvider: c.App.MetadataProvider,
})
@@ -51,7 +47,7 @@ func HandleGetLibraryCollection(c *RouteCtx) error {
c.App.TorrentstreamRepository.HydrateStreamCollection(&torrentstream.HydrateStreamCollectionOptions{
AnimeCollection: animeCollection,
LibraryCollection: libraryCollection,
AnizipCache: c.App.AnizipCache,
MetadataProvider: c.App.MetadataProvider,
})
}

View File

@@ -10,8 +10,6 @@ import (
"path/filepath"
"runtime"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/mal"
"seanime/internal/database/db_bridge"
"seanime/internal/library/anime"
"seanime/internal/library/scanner"
@@ -59,7 +57,6 @@ func HandleGetAnimeEntry(c *RouteCtx) error {
entry, err := anime.NewAnimeEntry(&anime.NewAnimeEntryOptions{
MediaId: mId,
LocalFiles: lfs,
AnizipCache: c.App.AnizipCache,
AnimeCollection: animeCollection,
Platform: c.App.AnilistPlatform,
MetadataProvider: c.App.MetadataProvider,
@@ -202,7 +199,6 @@ func HandleOpenAnimeEntryInExplorer(c *RouteCtx) error {
//----------------------------------------------------------------------------------------------------------------------
var (
entriesMalCache = result.NewCache[string, []*mal.SearchResultAnime]()
entriesSuggestionsCache = result.NewCache[string, []*anilist.BaseAnime]()
)
@@ -353,8 +349,8 @@ func HandleAnimeEntryManualMatch(c *RouteCtx) error {
fh := scanner.FileHydrator{
LocalFiles: selectedLfs,
CompleteAnimeCache: anilist.NewCompleteAnimeCache(),
AnizipCache: anizip.NewCache(),
Platform: c.App.AnilistPlatform,
MetadataProvider: c.App.MetadataProvider,
AnilistRateLimiter: limiter.NewAnilistLimiter(),
Logger: c.App.Logger,
ScanLogger: scanLogger,
@@ -427,7 +423,6 @@ func HandleGetMissingEpisodes(c *RouteCtx) error {
missingEps := anime.NewMissingEpisodes(&anime.NewMissingEpisodesOptions{
AnimeCollection: animeCollection,
LocalFiles: lfs,
AnizipCache: c.App.AnizipCache,
SilencedMediaIds: silencedMediaIds,
MetadataProvider: c.App.MetadataProvider,
})

View File

@@ -2,8 +2,8 @@ package handlers
import (
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/library/scanner"
"seanime/internal/util/limiter"
)
// DUMMY HANDLER
@@ -32,15 +32,17 @@ func HandleTestDump(c *RouteCtx) error {
}
completeAnimeCache := anilist.NewCompleteAnimeCache()
anizipCache := anizip.NewCache()
mc, err := scanner.NewMediaFetcher(&scanner.MediaFetcherOptions{
Enhanced: false,
Platform: c.App.AnilistPlatform,
LocalFiles: localFiles,
CompleteAnimeCache: completeAnimeCache,
AnizipCache: anizipCache,
Logger: c.App.Logger,
Enhanced: false,
Platform: c.App.AnilistPlatform,
MetadataProvider: c.App.MetadataProvider,
LocalFiles: localFiles,
CompleteAnimeCache: completeAnimeCache,
Logger: c.App.Logger,
AnilistRateLimiter: limiter.NewAnilistLimiter(),
DisableAnimeCollection: false,
ScanLogger: nil,
})
if err != nil {

View File

@@ -1,7 +1,7 @@
package handlers
import (
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
)
// HandlePopulateTVDBEpisodes
@@ -20,7 +20,7 @@ func HandlePopulateTVDBEpisodes(c *RouteCtx) error {
return c.RespondWithError(err)
}
anizipMedia, err := anizip.FetchAniZipMedia("anilist", b.MediaId)
animeMetadata, err := c.App.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, b.MediaId)
if err != nil {
return c.RespondWithError(err)
}
@@ -31,7 +31,7 @@ func HandlePopulateTVDBEpisodes(c *RouteCtx) error {
}
// Create media wrapper
aw := c.App.MetadataProvider.GetAnimeMetadataWrapper(media, anizipMedia)
aw := c.App.MetadataProvider.GetAnimeMetadataWrapper(media, animeMetadata)
// Fetch episodes
episodes, err := aw.GetTVDBEpisodes(true)
@@ -59,7 +59,7 @@ func HandleEmptyTVDBEpisodes(c *RouteCtx) error {
return c.RespondWithError(err)
}
anizipMedia, err := anizip.FetchAniZipMedia("anilist", b.MediaId)
animeMetadata, err := c.App.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, b.MediaId)
if err != nil {
return c.RespondWithError(err)
}
@@ -70,7 +70,7 @@ func HandleEmptyTVDBEpisodes(c *RouteCtx) error {
}
// Create media wrapper
aw := c.App.MetadataProvider.GetAnimeMetadataWrapper(media, anizipMedia)
aw := c.App.MetadataProvider.GetAnimeMetadataWrapper(media, animeMetadata)
// Empty TVDB episodes bucket
err = aw.EmptyTVDBEpisodesBucket(b.MediaId)

View File

@@ -72,6 +72,7 @@ func HandleScanLocalFiles(c *RouteCtx) error {
SkipIgnoredFiles: b.SkipIgnoredFiles,
ScanSummaryLogger: scanSummaryLogger,
ScanLogger: scanLogger,
MetadataProvider: c.App.MetadataProvider,
}
// Scan the library

View File

@@ -5,7 +5,7 @@ import (
hibiketorrent "github.com/5rahim/hibike/pkg/extension/torrent"
lop "github.com/samber/lo/parallel"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/database/models"
"seanime/internal/library/anime"
"seanime/internal/torrentstream"
@@ -107,10 +107,10 @@ func HandleGetTorrentstreamTorrentFilePreviews(c *RouteCtx) error {
}
// Get the media
anizipMedia, _ := anizip.FetchAniZipMediaC("anilist", b.Media.GetID(), c.App.AnizipCache)
animeMetadata, _ := c.App.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, b.Media.ID)
absoluteOffset := 0
if anizipMedia != nil {
absoluteOffset = anizipMedia.GetOffset()
if animeMetadata != nil {
absoluteOffset = animeMetadata.GetOffset()
}
files, err := c.App.TorrentstreamRepository.GetTorrentFilePreviewsFromManualSelection(&torrentstream.GetTorrentFilePreviewsOptions{

View File

@@ -5,7 +5,6 @@ import (
"github.com/samber/lo"
"github.com/sourcegraph/conc/pool"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/platforms/platform"
"sort"
@@ -42,7 +41,6 @@ type (
NewAnimeEntryOptions struct {
MediaId int
LocalFiles []*LocalFile // All local files
AnizipCache *anizip.Cache
AnimeCollection *anilist.AnimeCollection
Platform platform.Platform
MetadataProvider metadata.Provider
@@ -117,7 +115,7 @@ func NewAnimeEntry(opts *NewAnimeEntryOptions) (*AnimeEntry, error) {
// +---------------------+
// Fetch AniDB data and cache it for 30 minutes
anizipData, err := anizip.FetchAniZipMediaC("anilist", opts.MediaId, opts.AnizipCache)
animeMetadata, err := opts.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, opts.MediaId)
if err != nil {
// +---------------- Start
@@ -151,7 +149,7 @@ func NewAnimeEntry(opts *NewAnimeEntryOptions) (*AnimeEntry, error) {
// +--------------- End
}
entry.AnidbId = anizipData.GetMappings().AnidbID
entry.AnidbId = animeMetadata.GetMappings().AnidbId
// Instantiate AnimeEntryListData
// If the media exist in the user's anime list, add the details
@@ -170,7 +168,7 @@ func NewAnimeEntry(opts *NewAnimeEntryOptions) (*AnimeEntry, error) {
// +---------------------+
// Create episode entities
entry.hydrateEntryEpisodeData(anilistEntry, anizipData, opts.MetadataProvider)
entry.hydrateEntryEpisodeData(anilistEntry, animeMetadata, opts.MetadataProvider)
return entry, nil
@@ -182,11 +180,11 @@ func NewAnimeEntry(opts *NewAnimeEntryOptions) (*AnimeEntry, error) {
// AniZipData, Media and LocalFiles should be defined
func (e *AnimeEntry) hydrateEntryEpisodeData(
anilistEntry *anilist.MediaListEntry,
anizipData *anizip.Media,
animeMetadata *metadata.AnimeMetadata,
metadataProvider metadata.Provider,
) {
if anizipData.Episodes == nil && len(anizipData.Episodes) == 0 {
if animeMetadata.Episodes == nil && len(animeMetadata.Episodes) == 0 {
return
}
@@ -196,7 +194,7 @@ func (e *AnimeEntry) hydrateEntryEpisodeData(
// We offset the progress number by 1 if there is a discrepancy
progressOffset := 0
if HasDiscrepancy(e.Media, anizipData) {
if HasDiscrepancy(e.Media, animeMetadata) {
progressOffset = 1
}
@@ -224,7 +222,7 @@ func (e *AnimeEntry) hydrateEntryEpisodeData(
return NewEpisode(&NewEpisodeOptions{
LocalFile: lf,
OptionalAniDBEpisode: "",
AnizipMedia: anizipData,
AnimeMetadata: animeMetadata,
Media: e.Media,
ProgressOffset: progressOffset,
IsDownloaded: true,
@@ -245,7 +243,7 @@ func (e *AnimeEntry) hydrateEntryEpisodeData(
info, err := NewAnimeEntryDownloadInfo(&NewAnimeEntryDownloadInfoOptions{
LocalFiles: e.LocalFiles,
AnizipMedia: anizipData,
AnimeMetadata: animeMetadata,
Progress: anilistEntry.Progress,
Status: anilistEntry.Status,
Media: e.Media,
@@ -270,10 +268,10 @@ func (e *AnimeEntry) hydrateEntryEpisodeData(
func detectDiscrepancy(
mediaLfs []*LocalFile, // Media's local files
media *anilist.BaseAnime,
anizipData *anizip.Media,
animeMetadata *metadata.AnimeMetadata,
) (possibleSpecialInclusion bool, hasDiscrepancy bool) {
if anizipData.Episodes == nil && len(anizipData.Episodes) == 0 {
if animeMetadata.Episodes == nil && len(animeMetadata.Episodes) == 0 {
return false, false
}
@@ -293,18 +291,18 @@ func detectDiscrepancy(
// e.g, epCeiling = 13 AND downloaded episodes = [0,...,13] //=> false
possibleSpecialInclusion = hasEpisodeZero && noEpisodeCeiling
_, aniDBHasS1 := anizipData.Episodes["S1"]
_, aniDBHasS1 := animeMetadata.Episodes["S1"]
// AniList episode count > Anizip episode count
// This means that there is a discrepancy and AniList is most likely including episode 0 as part of main episodes
hasDiscrepancy = media.GetCurrentEpisodeCount() > anizipData.GetMainEpisodeCount() && aniDBHasS1
hasDiscrepancy = media.GetCurrentEpisodeCount() > animeMetadata.GetMainEpisodeCount() && aniDBHasS1
return
}
func HasDiscrepancy(media *anilist.BaseAnime, anizipData *anizip.Media) bool {
if media == nil || anizipData == nil || anizipData.Episodes == nil {
func HasDiscrepancy(media *anilist.BaseAnime, animeMetadata *metadata.AnimeMetadata) bool {
if media == nil || animeMetadata == nil || animeMetadata.Episodes == nil {
return false
}
_, aniDBHasS1 := anizipData.Episodes["S1"]
return media.GetCurrentEpisodeCount() > anizipData.GetMainEpisodeCount() && aniDBHasS1
_, aniDBHasS1 := animeMetadata.Episodes["S1"]
return media.GetCurrentEpisodeCount() > animeMetadata.GetMainEpisodeCount() && aniDBHasS1
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/samber/lo"
"github.com/sourcegraph/conc/pool"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"slices"
"strconv"
@@ -33,7 +32,7 @@ type (
NewAnimeEntryDownloadInfoOptions struct {
// Media's local files
LocalFiles []*LocalFile
AnizipMedia *anizip.Media
AnimeMetadata *metadata.AnimeMetadata
Media *anilist.BaseAnime
Progress *int
Status *anilist.MediaListStatus
@@ -47,8 +46,8 @@ func NewAnimeEntryDownloadInfo(opts *NewAnimeEntryDownloadInfoOptions) (*AnimeEn
if *opts.Media.Status == anilist.MediaStatusNotYetReleased {
return &AnimeEntryDownloadInfo{}, nil
}
if opts.AnizipMedia == nil {
return nil, errors.New("could not get anizip media")
if opts.AnimeMetadata == nil {
return nil, errors.New("could not get anime metadata")
}
if opts.Media.GetCurrentEpisodeCount() == -1 {
return nil, errors.New("could not get current media episode count")
@@ -59,7 +58,7 @@ func NewAnimeEntryDownloadInfo(opts *NewAnimeEntryDownloadInfoOptions) (*AnimeEn
// +---------------------+
// Whether AniList includes episode 0 as part of main episodes, but Anizip does not, however Anizip has "S1"
_, hasDiscrepancy := detectDiscrepancy(opts.LocalFiles, opts.Media, opts.AnizipMedia)
_, hasDiscrepancy := detectDiscrepancy(opts.LocalFiles, opts.Media, opts.AnimeMetadata)
// I - Progress
// Get progress, if the media isn't in the user's list, progress is 0
@@ -79,8 +78,8 @@ func NewAnimeEntryDownloadInfo(opts *NewAnimeEntryDownloadInfoOptions) (*AnimeEn
mediaEpSlice := generateEpSlice(opts.Media.GetCurrentEpisodeCount()) // e.g, [1,2,3,4]
unwatchedEpSlice := lo.Filter(mediaEpSlice, func(i int, _ int) bool { return i > progress }) // e.g, progress = 1: [1,2,3,4] -> [2,3,4]
anizipEpSlice := generateEpSlice(opts.AnizipMedia.GetMainEpisodeCount()) // e.g, [1,2,3,4]
unwatchedAnizipEpSlice := lo.Filter(anizipEpSlice, func(i int, _ int) bool { return i > progress }) // e.g, progress = 1: [1,2,3,4] -> [2,3,4]
metadataEpSlice := generateEpSlice(opts.AnimeMetadata.GetMainEpisodeCount()) // e.g, [1,2,3,4]
unwatchedAnizipEpSlice := lo.Filter(metadataEpSlice, func(i int, _ int) bool { return i > progress }) // e.g, progress = 1: [1,2,3,4] -> [2,3,4]
// +---------------------+
// | Anizip has more |
@@ -88,11 +87,11 @@ func NewAnimeEntryDownloadInfo(opts *NewAnimeEntryDownloadInfoOptions) (*AnimeEn
// If Anizip has more episodes
// e.g, Anizip: 2, Anilist: 1
if opts.AnizipMedia.GetMainEpisodeCount() > opts.Media.GetCurrentEpisodeCount() {
diff := opts.AnizipMedia.GetMainEpisodeCount() - opts.Media.GetCurrentEpisodeCount()
if opts.AnimeMetadata.GetMainEpisodeCount() > opts.Media.GetCurrentEpisodeCount() {
diff := opts.AnimeMetadata.GetMainEpisodeCount() - opts.Media.GetCurrentEpisodeCount()
// Remove the extra episode number from the Anizip slice
anizipEpSlice = anizipEpSlice[:len(anizipEpSlice)-diff] // e.g, [1,2] -> [1]
unwatchedAnizipEpSlice = lo.Filter(anizipEpSlice, func(i int, _ int) bool { return i > progress }) // e.g, [1,2] -> [1]
metadataEpSlice = metadataEpSlice[:len(metadataEpSlice)-diff] // e.g, [1,2] -> [1]
unwatchedAnizipEpSlice = lo.Filter(metadataEpSlice, func(i int, _ int) bool { return i > progress }) // e.g, [1,2] -> [1]
}
// +---------------------+
@@ -100,14 +99,14 @@ func NewAnimeEntryDownloadInfo(opts *NewAnimeEntryDownloadInfoOptions) (*AnimeEn
// +---------------------+
// III - Handle discrepancy (inclusion of episode 0 by AniList)
// If there Anilist has more episodes than Anizip
// If Anilist has more episodes than Anizip
// e.g, Anilist: 13, Anizip: 12
if hasDiscrepancy {
// Add -1 to Anizip slice, -1 is "S1"
anizipEpSlice = append([]int{-1}, anizipEpSlice...) // e.g, [-1,1,2,...,12]
unwatchedAnizipEpSlice = anizipEpSlice // e.g, [-1,1,2,...,12]
metadataEpSlice = append([]int{-1}, metadataEpSlice...) // e.g, [-1,1,2,...,12]
unwatchedAnizipEpSlice = metadataEpSlice // e.g, [-1,1,2,...,12]
if progress > 0 {
unwatchedAnizipEpSlice = lo.Filter(anizipEpSlice, func(i int, _ int) bool { return i > progress-1 })
unwatchedAnizipEpSlice = lo.Filter(metadataEpSlice, func(i int, _ int) bool { return i > progress-1 })
}
}
@@ -125,7 +124,7 @@ func NewAnimeEntryDownloadInfo(opts *NewAnimeEntryDownloadInfoOptions) (*AnimeEn
hasInaccurateSchedule := false
if opts.Media.NextAiringEpisode == nil && *opts.Media.Status == anilist.MediaStatusReleasing {
if !hasDiscrepancy {
if progress+1 < opts.AnizipMedia.GetMainEpisodeCount() {
if progress+1 < opts.AnimeMetadata.GetMainEpisodeCount() {
unwatchedEpSlice = lo.Filter(unwatchedEpSlice, func(i int, _ int) bool { return i > progress && i <= progress+1 })
unwatchedAnizipEpSlice = lo.Filter(unwatchedAnizipEpSlice, func(i int, _ int) bool { return i > progress && i <= progress+1 })
} else {
@@ -133,7 +132,7 @@ func NewAnimeEntryDownloadInfo(opts *NewAnimeEntryDownloadInfoOptions) (*AnimeEn
unwatchedAnizipEpSlice = lo.Filter(unwatchedAnizipEpSlice, func(i int, _ int) bool { return i > progress && i <= progress })
}
} else {
if progress+1 < opts.AnizipMedia.GetMainEpisodeCount() {
if progress+1 < opts.AnimeMetadata.GetMainEpisodeCount() {
unwatchedEpSlice = lo.Filter(unwatchedEpSlice, func(i int, _ int) bool { return i > progress && i <= progress })
unwatchedAnizipEpSlice = lo.Filter(unwatchedAnizipEpSlice, func(i int, _ int) bool { return i > progress && i <= progress })
} else {
@@ -196,7 +195,7 @@ func NewAnimeEntryDownloadInfo(opts *NewAnimeEntryDownloadInfoOptions) (*AnimeEn
str.Episode = NewEpisode(&NewEpisodeOptions{
LocalFile: nil,
OptionalAniDBEpisode: str.AniDBEpisode,
AnizipMedia: opts.AnizipMedia,
AnimeMetadata: opts.AnimeMetadata,
Media: opts.Media,
ProgressOffset: 0,
IsDownloaded: false,
@@ -228,7 +227,7 @@ func NewAnimeEntryDownloadInfo(opts *NewAnimeEntryDownloadInfoOptions) (*AnimeEn
BatchAll: batchAll,
Rewatch: rewatch,
HasInaccurateSchedule: hasInaccurateSchedule,
AbsoluteOffset: opts.AnizipMedia.GetOffset(),
AbsoluteOffset: opts.AnimeMetadata.GetOffset(),
}, nil
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"github.com/stretchr/testify/assert"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/test_utils"
"testing"
@@ -13,7 +12,7 @@ import (
func TestNewAnimeEntryDownloadInfo(t *testing.T) {
test_utils.InitTestProvider(t, test_utils.Anilist())
metadataProvider := metadata.TestGetMockProvider(t)
metadataProvider := metadata.GetMockProvider(t)
anilistClient := anilist.TestGetMockAnilistClient()
animeCollection, err := anilistClient.AnimeCollection(context.Background(), nil)
@@ -86,22 +85,14 @@ func TestNewAnimeEntryDownloadInfo(t *testing.T) {
},
}
anizipCache := anizip.NewCache()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
anizipData, err := anizip.FetchAniZipMediaC("anilist", tt.mediaId, anizipCache)
if err != nil {
t.Fatal(err)
}
anilistEntry, _ := animeCollection.GetListEntryFromAnimeId(tt.mediaId)
info, err := NewAnimeEntryDownloadInfo(&NewAnimeEntryDownloadInfoOptions{
LocalFiles: tt.localFiles,
AnizipMedia: anizipData,
Progress: &tt.currentProgress,
Status: &tt.status,
Media: anilistEntry.Media,

View File

@@ -4,7 +4,6 @@ import (
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/test_utils"
@@ -17,7 +16,7 @@ import (
func TestNewAnimeEntry(t *testing.T) {
test_utils.InitTestProvider(t, test_utils.Anilist())
metadataProvider := metadata.TestGetMockProvider(t)
metadataProvider := metadata.GetMockProvider(t)
tests := []struct {
name string
@@ -76,8 +75,6 @@ func TestNewAnimeEntry(t *testing.T) {
t.Fatal(err)
}
aniZipCache := anizip.NewCache()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -89,7 +86,6 @@ func TestNewAnimeEntry(t *testing.T) {
entry, err := NewAnimeEntry(&NewAnimeEntryOptions{
MediaId: tt.mediaId,
LocalFiles: tt.localFiles,
AnizipCache: aniZipCache,
AnimeCollection: animeCollection,
Platform: anilistPlatform,
MetadataProvider: metadataProvider,

View File

@@ -7,7 +7,6 @@ import (
"github.com/sourcegraph/conc/pool"
"path/filepath"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/platforms/platform"
"seanime/internal/util"
@@ -86,7 +85,6 @@ type (
NewLibraryCollectionOptions struct {
AnimeCollection *anilist.AnimeCollection
LocalFiles []*LocalFile
AnizipCache *anizip.Cache
Platform platform.Platform
MetadataProvider metadata.Provider
}
@@ -114,7 +112,6 @@ func NewLibraryCollection(opts *NewLibraryCollectionOptions) (lc *LibraryCollect
lc.hydrateContinueWatchingList(
opts.LocalFiles,
opts.AnimeCollection,
opts.AnizipCache,
opts.Platform,
opts.MetadataProvider,
)
@@ -315,7 +312,6 @@ func (lc *LibraryCollection) hydrateStats(lfs []*LocalFile) {
func (lc *LibraryCollection) hydrateContinueWatchingList(
localFiles []*LocalFile,
animeCollection *anilist.AnimeCollection,
anizipCache *anizip.Cache,
platform platform.Platform,
metadataProvider metadata.Provider,
) {
@@ -344,7 +340,6 @@ func (lc *LibraryCollection) hydrateContinueWatchingList(
MediaId: mId,
LocalFiles: localFiles,
AnimeCollection: animeCollection,
AnizipCache: anizipCache,
Platform: platform,
MetadataProvider: metadataProvider,
})

View File

@@ -4,7 +4,6 @@ import (
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/test_utils"
@@ -15,7 +14,7 @@ import (
func TestNewLibraryCollection(t *testing.T) {
test_utils.InitTestProvider(t, test_utils.Anilist())
metadataProvider := metadata.TestGetMockProvider(t)
metadataProvider := metadata.GetMockProvider(t)
anilistClient := anilist.TestGetMockAnilistClient()
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClient, util.NewLogger())
@@ -79,7 +78,6 @@ func TestNewLibraryCollection(t *testing.T) {
libraryCollection, err := NewLibraryCollection(&NewLibraryCollectionOptions{
AnimeCollection: animeCollection,
LocalFiles: lfs,
AnizipCache: anizip.NewCache(),
Platform: anilistPlatform,
MetadataProvider: metadataProvider,
})

View File

@@ -2,7 +2,6 @@ package anime
import (
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"strconv"
"strings"
@@ -27,7 +26,7 @@ type (
BaseAnime *anilist.BaseAnime `json:"baseAnime,omitempty"`
}
// EpisodeMetadata represents the metadata of a Episode.
// EpisodeMetadata represents the metadata of an Episode.
// Metadata is fetched from AniZip (AniDB) and, optionally, AniList (if AniZip is not available).
EpisodeMetadata struct {
AnidbId int `json:"anidbId,omitempty"`
@@ -44,7 +43,7 @@ type (
// NewEpisodeOptions hold data used to create a new Episode.
NewEpisodeOptions struct {
LocalFile *LocalFile
AnizipMedia *anizip.Media // optional
AnimeMetadata *metadata.AnimeMetadata // optional
Media *anilist.BaseAnime
OptionalAniDBEpisode string
// ProgressOffset will offset the ProgressNumber for a specific MAIN file
@@ -69,7 +68,7 @@ type (
// It is used to list existing local files as episodes
// OR list non-downloaded episodes by passing the `OptionalAniDBEpisode` parameter.
//
// `AnizipMedia` should be defined, but this is not always the case.
// `AnimeMetadata` should be defined, but this is not always the case.
// `LocalFile` is optional.
func NewEpisode(opts *NewEpisodeOptions) *Episode {
entryEp := new(Episode)
@@ -94,16 +93,16 @@ func NewEpisode(opts *NewEpisodeOptions) *Episode {
opts.ProgressOffset = 0
} else {
// e.g, "1" -> "2" etc...
aniDBEp = anizip.OffsetEpisode(aniDBEp, opts.ProgressOffset)
aniDBEp = metadata.OffsetAnidbEpisode(aniDBEp, opts.ProgressOffset)
}
entryEp.MetadataIssue = "forced_remapping"
}
// Get the AniZip episode
foundAnizipEpisode := false
var anizipEpisode *anizip.Episode
if opts.AnizipMedia != nil {
anizipEpisode, foundAnizipEpisode = opts.AnizipMedia.FindEpisode(aniDBEp)
var episodeMetadata *metadata.EpisodeMetadata
if opts.AnimeMetadata != nil {
episodeMetadata, foundAnizipEpisode = opts.AnimeMetadata.FindEpisode(aniDBEp)
}
entryEp.IsDownloaded = true
@@ -118,7 +117,7 @@ func NewEpisode(opts *NewEpisodeOptions) *Episode {
entryEp.ProgressNumber = opts.LocalFile.GetEpisodeNumber() + opts.ProgressOffset
if foundAnizipEpisode {
entryEp.AniDBEpisode = aniDBEp
entryEp.AbsoluteEpisodeNumber = entryEp.EpisodeNumber + opts.AnizipMedia.GetOffset()
entryEp.AbsoluteEpisodeNumber = entryEp.EpisodeNumber + opts.AnimeMetadata.GetOffset()
}
case LocalFileTypeSpecial:
entryEp.EpisodeNumber = opts.LocalFile.GetEpisodeNumber()
@@ -139,7 +138,7 @@ func NewEpisode(opts *NewEpisodeOptions) *Episode {
entryEp.EpisodeTitle = "Complete Movie"
} else {
entryEp.DisplayTitle = "Episode " + strconv.Itoa(opts.LocalFile.GetEpisodeNumber())
entryEp.EpisodeTitle = anizipEpisode.GetTitle()
entryEp.EpisodeTitle = episodeMetadata.GetTitle()
}
} else {
if *opts.Media.GetFormat() == anilist.MediaFormatMovie {
@@ -154,13 +153,13 @@ func NewEpisode(opts *NewEpisodeOptions) *Episode {
case LocalFileTypeSpecial:
if foundAnizipEpisode {
entryEp.AniDBEpisode = aniDBEp
episodeInt, found := anizip.ExtractEpisodeInteger(aniDBEp)
episodeInt, found := metadata.ExtractEpisodeInteger(aniDBEp)
if found {
entryEp.DisplayTitle = "Special " + strconv.Itoa(episodeInt)
} else {
entryEp.DisplayTitle = "Special " + aniDBEp
}
entryEp.EpisodeTitle = anizipEpisode.GetTitle()
entryEp.EpisodeTitle = episodeMetadata.GetTitle()
} else {
entryEp.DisplayTitle = "Special " + strconv.Itoa(opts.LocalFile.GetEpisodeNumber())
}
@@ -168,7 +167,7 @@ func NewEpisode(opts *NewEpisodeOptions) *Episode {
case LocalFileTypeNC:
if foundAnizipEpisode {
entryEp.AniDBEpisode = aniDBEp
entryEp.DisplayTitle = anizipEpisode.GetTitle()
entryEp.DisplayTitle = episodeMetadata.GetTitle()
entryEp.EpisodeTitle = ""
} else {
entryEp.DisplayTitle = opts.LocalFile.GetParsedTitle()
@@ -181,13 +180,13 @@ func NewEpisode(opts *NewEpisodeOptions) *Episode {
}
// Set episode metadata
entryEp.EpisodeMetadata = NewEpisodeMetadata(opts.AnizipMedia, anizipEpisode, opts.Media, opts.MetadataProvider)
entryEp.EpisodeMetadata = NewEpisodeMetadata(opts.AnimeMetadata, episodeMetadata, opts.Media, opts.MetadataProvider)
} else if len(opts.OptionalAniDBEpisode) > 0 && opts.AnizipMedia != nil {
} else if len(opts.OptionalAniDBEpisode) > 0 && opts.AnimeMetadata != nil {
// No LocalFile, but AniDB episode is provided
// Get the AniZip episode
if anizipEpisode, foundAnizipEpisode := opts.AnizipMedia.FindEpisode(opts.OptionalAniDBEpisode); foundAnizipEpisode {
if episodeMetadata, foundAnizipEpisode := opts.AnimeMetadata.FindEpisode(opts.OptionalAniDBEpisode); foundAnizipEpisode {
entryEp.IsDownloaded = false
entryEp.Type = LocalFileTypeMain
@@ -199,12 +198,12 @@ func NewEpisode(opts *NewEpisodeOptions) *Episode {
entryEp.EpisodeNumber = 0
entryEp.ProgressNumber = 0
if episodeInt, ok := anizip.ExtractEpisodeInteger(opts.OptionalAniDBEpisode); ok {
if episodeInt, ok := metadata.ExtractEpisodeInteger(opts.OptionalAniDBEpisode); ok {
entryEp.EpisodeNumber = episodeInt
entryEp.ProgressNumber = episodeInt
if foundAnizipEpisode {
entryEp.AniDBEpisode = opts.OptionalAniDBEpisode
entryEp.AbsoluteEpisodeNumber = entryEp.EpisodeNumber + opts.AnizipMedia.GetOffset()
entryEp.AbsoluteEpisodeNumber = entryEp.EpisodeNumber + opts.AnimeMetadata.GetOffset()
}
switch entryEp.Type {
case LocalFileTypeMain:
@@ -213,11 +212,11 @@ func NewEpisode(opts *NewEpisodeOptions) *Episode {
entryEp.EpisodeTitle = "Complete Movie"
} else {
entryEp.DisplayTitle = "Episode " + strconv.Itoa(episodeInt)
entryEp.EpisodeTitle = anizipEpisode.GetTitle()
entryEp.EpisodeTitle = episodeMetadata.GetTitle()
}
case LocalFileTypeSpecial:
entryEp.DisplayTitle = "Special " + strconv.Itoa(episodeInt)
entryEp.EpisodeTitle = anizipEpisode.GetTitle()
entryEp.EpisodeTitle = episodeMetadata.GetTitle()
case LocalFileTypeNC:
entryEp.DisplayTitle = opts.OptionalAniDBEpisode
entryEp.EpisodeTitle = ""
@@ -226,7 +225,7 @@ func NewEpisode(opts *NewEpisodeOptions) *Episode {
}
// Set episode metadata
entryEp.EpisodeMetadata = NewEpisodeMetadata(opts.AnizipMedia, anizipEpisode, opts.Media, opts.MetadataProvider)
entryEp.EpisodeMetadata = NewEpisodeMetadata(opts.AnimeMetadata, episodeMetadata, opts.Media, opts.MetadataProvider)
} else {
// No Local file, no AniZip data
// DEVNOTE: Non-downloaded, without any AniDB data. Don't handle this case.
@@ -251,8 +250,8 @@ func NewEpisode(opts *NewEpisodeOptions) *Episode {
// NewEpisodeMetadata creates a new EpisodeMetadata from an AniZip episode and AniList media.
// If the AniZip episode is nil, it will just set the image from the media.
func NewEpisodeMetadata(
anizipMedia *anizip.Media,
episode *anizip.Episode,
animeMetadata *metadata.AnimeMetadata,
episode *metadata.EpisodeMetadata,
media *anilist.BaseAnime,
metadataProvider metadata.Provider,
) *EpisodeMetadata {
@@ -266,7 +265,7 @@ func NewEpisodeMetadata(
epInt, err := strconv.Atoi(episode.Episode)
if err == nil {
aw := metadataProvider.GetAnimeMetadataWrapper(media, anizipMedia)
aw := metadataProvider.GetAnimeMetadataWrapper(media, animeMetadata)
epMetadata := aw.GetEpisodeMetadata(epInt)
md.AnidbId = epMetadata.AnidbId
md.Image = epMetadata.Image

View File

@@ -6,7 +6,6 @@ import (
lop "github.com/samber/lo/parallel"
"github.com/sourcegraph/conc/pool"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/util/limiter"
"sort"
@@ -22,7 +21,6 @@ type (
NewMissingEpisodesOptions struct {
AnimeCollection *anilist.AnimeCollection
LocalFiles []*LocalFile
AnizipCache *anizip.Cache
SilencedMediaIds []int
MetadataProvider metadata.Provider
}
@@ -52,8 +50,8 @@ func NewMissingEpisodes(opts *NewMissingEpisodesOptions) *MissingEpisodes {
return nil
}
rateLimiter.Wait()
// Fetch anizip media
anizipMedia, err := anizip.FetchAniZipMediaC("anilist", entry.Media.ID, opts.AnizipCache)
// Fetch anime metadata
animeMetadata, err := opts.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, entry.Media.ID)
if err != nil {
return nil
}
@@ -61,10 +59,10 @@ func NewMissingEpisodes(opts *NewMissingEpisodesOptions) *MissingEpisodes {
// Get download info
downloadInfo, err := NewAnimeEntryDownloadInfo(&NewAnimeEntryDownloadInfoOptions{
LocalFiles: lfs,
AnizipMedia: anizipMedia,
AnimeMetadata: animeMetadata,
Media: entry.Media,
Progress: entry.Progress,
Status: entry.Status,
Media: entry.Media,
MetadataProvider: opts.MetadataProvider,
})
if err != nil {

View File

@@ -5,7 +5,6 @@ import (
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/test_utils"
"testing"
@@ -14,9 +13,10 @@ import (
// Test to retrieve accurate missing episodes
// DEPRECATED
func TestNewMissingEpisodes(t *testing.T) {
t.Skip("Outdated test")
test_utils.InitTestProvider(t, test_utils.Anilist())
metadataProvider := metadata.TestGetMockProvider(t)
metadataProvider := metadata.GetMockProvider(t)
anilistClient := anilist.TestGetMockAnilistClient()
animeCollection, err := anilistClient.AnimeCollection(context.Background(), nil)
@@ -73,7 +73,6 @@ func TestNewMissingEpisodes(t *testing.T) {
missingData := NewMissingEpisodes(&NewMissingEpisodesOptions{
AnimeCollection: animeCollection,
LocalFiles: tt.localFiles,
AnizipCache: anizip.NewCache(),
MetadataProvider: metadataProvider,
})

View File

@@ -8,7 +8,7 @@ import (
"github.com/samber/mo"
"github.com/sourcegraph/conc/pool"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/database/db"
"seanime/internal/database/db_bridge"
"seanime/internal/database/models"
@@ -38,7 +38,7 @@ type (
animeCollection mo.Option[*anilist.AnimeCollection]
wsEventManager events.WSEventManagerInterface
settings *models.AutoDownloaderSettings
anizipCache *anizip.Cache
metadataProvider metadata.Provider
settingsUpdatedCh chan struct{}
stopCh chan struct{}
startCh chan struct{}
@@ -52,7 +52,7 @@ type (
TorrentRepository *torrent.Repository
WSEventManager events.WSEventManagerInterface
Database *db.Database
AnizipCache *anizip.Cache
MetadataProvider metadata.Provider
}
tmpTorrentToDownload struct {
@@ -69,7 +69,7 @@ func New(opts *NewAutoDownloaderOptions) *AutoDownloader {
database: opts.Database,
wsEventManager: opts.WSEventManager,
animeCollection: mo.None[*anilist.AnimeCollection](),
anizipCache: opts.AnizipCache,
metadataProvider: opts.MetadataProvider,
settings: &models.AutoDownloaderSettings{
Provider: torrent.ProviderAnimeTosho, // Default provider, will be updated after the settings are fetched
Interval: 10,
@@ -480,8 +480,8 @@ func (ad *AutoDownloader) downloadTorrent(t *NormalizedTorrent, rule *anime.Auto
Episode: episode,
Link: t.Link,
Hash: t.InfoHash,
TorrentName: t.Name,
Magnet: magnet,
TorrentName: t.Name,
Downloaded: downloaded,
}
_ = ad.database.InsertAutoDownloaderItem(item)
@@ -668,10 +668,10 @@ func (ad *AutoDownloader) isEpisodeMatch(
if listEntry.GetMedia().GetCurrentEpisodeCount() != -1 && episode > listEntry.GetMedia().GetCurrentEpisodeCount() {
// Fetch the AniZip media in order to normalize the episode number
ad.mu.Lock()
anizipMedia, err := anizip.FetchAniZipMediaC("anilist", listEntry.GetMedia().GetID(), ad.anizipCache)
animeMetadata, err := ad.metadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, listEntry.GetMedia().GetID())
// If the media is found and the offset is greater than 0
if err == nil && anizipMedia.GetOffset() > 0 {
episode = episode - anizipMedia.GetOffset()
if err == nil && animeMetadata.GetOffset() > 0 {
episode = episode - animeMetadata.GetOffset()
}
ad.mu.Unlock()
}

View File

@@ -3,6 +3,7 @@ package autoscanner
import (
"errors"
"github.com/rs/zerolog"
"seanime/internal/api/metadata"
"seanime/internal/database/db"
"seanime/internal/database/db_bridge"
"seanime/internal/events"
@@ -18,27 +19,29 @@ import (
type (
AutoScanner struct {
fileActionCh chan struct{} // Used to notify the scanner that a file action has occurred.
waiting bool // Used to prevent multiple scans from occurring at the same time.
missedAction bool // Used to indicate that a file action was missed while scanning.
mu sync.Mutex
scannedCh chan struct{}
waitTime time.Duration // Wait time to listen to additional changes before triggering a scan.
enabled bool
platform platform.Platform
logger *zerolog.Logger
wsEventManager events.WSEventManagerInterface
db *db.Database // Database instance is required to update the local files.
autoDownloader *autodownloader.AutoDownloader // AutoDownloader instance is required to refresh queue.
fileActionCh chan struct{} // Used to notify the scanner that a file action has occurred.
waiting bool // Used to prevent multiple scans from occurring at the same time.
missedAction bool // Used to indicate that a file action was missed while scanning.
mu sync.Mutex
scannedCh chan struct{}
waitTime time.Duration // Wait time to listen to additional changes before triggering a scan.
enabled bool
platform platform.Platform
logger *zerolog.Logger
wsEventManager events.WSEventManagerInterface
db *db.Database // Database instance is required to update the local files.
autoDownloader *autodownloader.AutoDownloader // AutoDownloader instance is required to refresh queue.
metadataProvider metadata.Provider
}
NewAutoScannerOptions struct {
Database *db.Database
Platform platform.Platform
Logger *zerolog.Logger
WSEventManager events.WSEventManagerInterface
Enabled bool
AutoDownloader *autodownloader.AutoDownloader
WaitTime time.Duration
Database *db.Database
Platform platform.Platform
Logger *zerolog.Logger
WSEventManager events.WSEventManagerInterface
Enabled bool
AutoDownloader *autodownloader.AutoDownloader
WaitTime time.Duration
MetadataProvider metadata.Provider
}
)
@@ -49,17 +52,19 @@ func New(opts *NewAutoScannerOptions) *AutoScanner {
}
return &AutoScanner{
fileActionCh: make(chan struct{}, 1),
scannedCh: make(chan struct{}, 1),
waiting: false,
missedAction: false,
waitTime: wt,
enabled: opts.Enabled,
autoDownloader: opts.AutoDownloader,
platform: opts.Platform,
logger: opts.Logger,
wsEventManager: opts.WSEventManager,
db: opts.Database,
fileActionCh: make(chan struct{}, 1),
waiting: false,
missedAction: false,
mu: sync.Mutex{},
scannedCh: make(chan struct{}, 1),
waitTime: wt,
enabled: opts.Enabled,
platform: opts.Platform,
logger: opts.Logger,
wsEventManager: opts.WSEventManager,
db: opts.Database,
autoDownloader: opts.AutoDownloader,
metadataProvider: opts.MetadataProvider,
}
}
@@ -203,6 +208,7 @@ func (as *AutoScanner) scan() {
SkipLockedFiles: true, // Skip locked files by default.
SkipIgnoredFiles: true,
ScanSummaryLogger: scanSummaryLogger,
MetadataProvider: as.metadataProvider,
}
allLfs, err := sc.Scan()

View File

@@ -7,7 +7,7 @@ import (
lop "github.com/samber/lo/parallel"
"github.com/sourcegraph/conc/pool"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/library/anime"
"seanime/internal/library/summary"
"seanime/internal/platforms/platform"
@@ -24,8 +24,8 @@ type FileHydrator struct {
LocalFiles []*anime.LocalFile // Local files to hydrate
AllMedia []*anime.NormalizedMedia // All media used to hydrate local files
CompleteAnimeCache *anilist.CompleteAnimeCache
AnizipCache *anizip.Cache
Platform platform.Platform
MetadataProvider metadata.Provider
AnilistRateLimiter *limiter.Limiter
Logger *zerolog.Logger
ScanLogger *ScanLogger // optional
@@ -267,9 +267,9 @@ func (fh *FileHydrator) hydrateGroupMetadata(
if err := media.FetchMediaTree(anilist.FetchMediaTreeAll, fh.Platform.GetAnilistClient(), fh.AnilistRateLimiter, tree, fh.CompleteAnimeCache); err == nil {
// Create a new media tree analysis that will be used for episode normalization
mta, _ := NewMediaTreeAnalysis(&MediaTreeAnalysisOptions{
tree: tree,
anizipCache: fh.AnizipCache,
rateLimiter: rateLimiter,
tree: tree,
metadataProvider: fh.MetadataProvider,
rateLimiter: rateLimiter,
})
// Hoist the media tree analysis, so it will be used by other files
// We don't care if it's nil because [normalizeEpisodeNumberAndHydrate] will handle it
@@ -335,7 +335,7 @@ func (fh *FileHydrator) hydrateGroupMetadata(
// When we encounter a file with an episode number higher than the media's episode count
// we have a forced media ID, we will fetch the media from AniList and get the offset
azm, err := anizip.FetchAniZipMediaC("anilist", fh.ForceMediaId, fh.AnizipCache)
animeMetadata, err := fh.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, fh.ForceMediaId)
if err != nil {
/*Log */
if fh.ScanLogger != nil {
@@ -351,7 +351,7 @@ func (fh *FileHydrator) hydrateGroupMetadata(
}
// Get the first episode to calculate the offset
firstEp, ok := azm.Episodes["1"]
firstEp, ok := animeMetadata.Episodes["1"]
if !ok {
/*Log */
if fh.ScanLogger != nil {
@@ -371,7 +371,7 @@ func (fh *FileHydrator) hydrateGroupMetadata(
maxPartAbsoluteEpisodeNumber := 0
if usePartEpisodeNumber {
minPartAbsoluteEpisodeNumber = firstEp.EpisodeNumber
maxPartAbsoluteEpisodeNumber = minPartAbsoluteEpisodeNumber + azm.GetMainEpisodeCount() - 1
maxPartAbsoluteEpisodeNumber = minPartAbsoluteEpisodeNumber + animeMetadata.GetMainEpisodeCount() - 1
}
absoluteEpisodeNumber := firstEp.AbsoluteEpisodeNumber

View File

@@ -2,7 +2,7 @@ package scanner
import (
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/util"
@@ -13,10 +13,9 @@ import (
func TestFileHydrator_HydrateMetadata(t *testing.T) {
completeAnimeCache := anilist.NewCompleteAnimeCache()
anizipCache := anizip.NewCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
logger := util.NewLogger()
metadataProvider := metadata.GetMockProvider(t)
anilistClient := anilist.TestGetMockAnilistClient()
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClient, util.NewLogger())
animeCollection, err := anilistPlatform.GetAnimeCollectionWithRelations()
@@ -100,9 +99,9 @@ func TestFileHydrator_HydrateMetadata(t *testing.T) {
LocalFiles: lfs,
AllMedia: mc.NormalizedMedia,
CompleteAnimeCache: completeAnimeCache,
AnizipCache: anizipCache,
Platform: anilistPlatform,
AnilistRateLimiter: anilistRateLimiter,
MetadataProvider: metadataProvider,
Logger: logger,
ScanLogger: scanLogger,
}

View File

@@ -7,8 +7,8 @@ import (
"github.com/samber/lo"
lop "github.com/samber/lo/parallel"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/mal"
"seanime/internal/api/metadata"
"seanime/internal/library/anime"
"seanime/internal/platforms/platform"
"seanime/internal/util"
@@ -29,9 +29,9 @@ type MediaFetcher struct {
type MediaFetcherOptions struct {
Enhanced bool
Platform platform.Platform
MetadataProvider metadata.Provider
LocalFiles []*anime.LocalFile
CompleteAnimeCache *anilist.CompleteAnimeCache
AnizipCache *anizip.Cache
Logger *zerolog.Logger
AnilistRateLimiter *limiter.Limiter
DisableAnimeCollection bool
@@ -49,7 +49,7 @@ func NewMediaFetcher(opts *MediaFetcherOptions) (ret *MediaFetcher, retErr error
if opts.Platform == nil ||
opts.LocalFiles == nil ||
opts.CompleteAnimeCache == nil ||
opts.AnizipCache == nil ||
opts.MetadataProvider == nil ||
opts.Logger == nil ||
opts.AnilistRateLimiter == nil {
return nil, errors.New("missing options")
@@ -122,7 +122,7 @@ func NewMediaFetcher(opts *MediaFetcherOptions) (ret *MediaFetcher, retErr error
opts.Platform,
opts.LocalFiles,
opts.CompleteAnimeCache, // CompleteAnimeCache will be populated on success
opts.AnizipCache,
opts.MetadataProvider,
opts.AnilistRateLimiter,
mf.ScanLogger,
)
@@ -166,7 +166,7 @@ func NewMediaFetcher(opts *MediaFetcherOptions) (ret *MediaFetcher, retErr error
// FetchMediaFromLocalFiles gets media and their relations from local file titles.
// It retrieves unique titles from local files,
// fetches mal.SearchResultAnime from MAL,
// uses these search results to get AniList IDs using anizip.Media mappings,
// uses these search results to get AniList IDs using metadata.AnimeMetadata mappings,
// queries AniList to retrieve all anilist.BaseAnime using anilist.GetBaseAnimeById and their relations using anilist.FetchMediaTree.
// It does not return an error if one of the steps fails.
// It returns the scanned media and a boolean indicating whether the process was successful.
@@ -174,7 +174,7 @@ func FetchMediaFromLocalFiles(
platform platform.Platform,
localFiles []*anime.LocalFile,
completeAnime *anilist.CompleteAnimeCache,
anizipCache *anizip.Cache,
metadataProvider metadata.Provider,
anilistRateLimiter *limiter.Limiter,
scanLogger *ScanLogger,
) ([]*anilist.CompleteAnime, bool) {
@@ -231,12 +231,13 @@ func FetchMediaFromLocalFiles(
// | AniZip |
// +---------------------+
// Get AniZip mappings for each MAL ID and store them in `anizipCache`
// Get AniZip mappings for each MAL ID and store them in `metadataProvider`
// This step is necessary because MAL doesn't provide AniList IDs and some MAL media don't exist on AniList
lop.ForEach(malIds, func(id int, index int) {
rateLimiter2.Wait()
_, _ = anizipCache.GetOrSet(anizip.GetCacheKey("mal", id), func() (*anizip.Media, error) {
res, err := anizip.FetchAniZipMedia("mal", id)
//_, _ = metadataProvider.GetAnimeMetadata(metadata.MalPlatform, id)
_, _ = metadataProvider.GetCache().GetOrSet(metadata.GetAnimeMetadataCacheKey(metadata.MalPlatform, id), func() (*metadata.AnimeMetadata, error) {
res, err := metadataProvider.GetAnimeMetadata(metadata.MalPlatform, id)
return res, err
})
})
@@ -247,9 +248,9 @@ func FetchMediaFromLocalFiles(
// Retrieve the AniList IDs from the AniZip mappings stored in the cache
anilistIds := make([]int, 0)
anizipCache.Range(func(key string, value *anizip.Media) bool {
metadataProvider.GetCache().Range(func(key string, value *metadata.AnimeMetadata) bool {
if value != nil {
anilistIds = append(anilistIds, value.GetMappings().AnilistID)
anilistIds = append(anilistIds, value.GetMappings().AnilistId)
}
return true
})

View File

@@ -4,7 +4,7 @@ import (
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/test_utils"
@@ -18,7 +18,7 @@ func TestNewMediaFetcher(t *testing.T) {
anilistClient := anilist.TestGetMockAnilistClient()
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClient, util.NewLogger())
anizipCache := anizip.NewCache()
metadataProvider := metadata.GetMockProvider(t)
completeAnimeCache := anilist.NewCompleteAnimeCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
@@ -82,7 +82,7 @@ func TestNewMediaFetcher(t *testing.T) {
Platform: anilistPlatform,
LocalFiles: lfs,
CompleteAnimeCache: completeAnimeCache,
AnizipCache: anizipCache,
MetadataProvider: metadataProvider,
Logger: util.NewLogger(),
AnilistRateLimiter: anilistRateLimiter,
ScanLogger: scanLogger,
@@ -111,7 +111,7 @@ func TestNewEnhancedMediaFetcher(t *testing.T) {
anilistClient := anilist.TestGetMockAnilistClient()
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClient, util.NewLogger())
anizipCache := anizip.NewCache()
metaProvider := metadata.GetMockProvider(t)
completeAnimeCache := anilist.NewCompleteAnimeCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
@@ -162,7 +162,7 @@ func TestNewEnhancedMediaFetcher(t *testing.T) {
Platform: anilistPlatform,
LocalFiles: lfs,
CompleteAnimeCache: completeAnimeCache,
AnizipCache: anizipCache,
MetadataProvider: metaProvider,
Logger: util.NewLogger(),
AnilistRateLimiter: anilistRateLimiter,
ScanLogger: scanLogger,
@@ -190,7 +190,7 @@ func TestFetchMediaFromLocalFiles(t *testing.T) {
anilistClient := anilist.TestGetMockAnilistClient()
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClient, util.NewLogger())
anizipCache := anizip.NewCache()
metaProvider := metadata.GetMockProvider(t)
completeAnimeCache := anilist.NewCompleteAnimeCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
@@ -240,7 +240,7 @@ func TestFetchMediaFromLocalFiles(t *testing.T) {
anilistPlatform,
lfs,
completeAnimeCache,
anizipCache,
metaProvider,
anilistRateLimiter,
scanLogger,
)

View File

@@ -6,15 +6,15 @@ import (
"github.com/samber/lo"
"github.com/sourcegraph/conc/pool"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/util/limiter"
)
type (
MediaTreeAnalysisOptions struct {
tree *anilist.CompleteAnimeRelationTree
anizipCache *anizip.Cache
rateLimiter *limiter.Limiter
tree *anilist.CompleteAnimeRelationTree
metadataProvider metadata.Provider
rateLimiter *limiter.Limiter
}
MediaTreeAnalysis struct {
@@ -22,8 +22,8 @@ type (
}
MediaTreeAnalysisBranch struct {
media *anilist.CompleteAnime
anizipMedia *anizip.Media
media *anilist.CompleteAnime
animeMetadata *metadata.AnimeMetadata
// The second absolute episode number of the first episode
// Sometimes, the metadata provider may have a 'true' absolute episode number and a 'part' absolute episode number
// 'part' absolute episode numbers might be used for "Part 2s" of a season
@@ -53,12 +53,14 @@ func NewMediaTreeAnalysis(opts *MediaTreeAnalysisOptions) (*MediaTreeAnalysis, e
for _, rel := range relations {
p.Go(func() (*MediaTreeAnalysisBranch, error) {
opts.rateLimiter.Wait()
azm, err := anizip.FetchAniZipMediaC("anilist", rel.ID, opts.anizipCache)
animeMetadata, err := opts.metadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, rel.ID)
if err != nil {
return nil, err
}
// Get the first episode
firstEp, ok := azm.Episodes["1"]
firstEp, ok := animeMetadata.Episodes["1"]
if !ok {
return nil, errors.New("no first episode")
}
@@ -72,22 +74,22 @@ func NewMediaTreeAnalysis(opts *MediaTreeAnalysisOptions) (*MediaTreeAnalysis, e
maxPartAbsoluteEpisodeNumber := 0
if usePartEpisodeNumber {
partAbsoluteEpisodeNumber = firstEp.EpisodeNumber
maxPartAbsoluteEpisodeNumber = partAbsoluteEpisodeNumber + azm.GetMainEpisodeCount() - 1
maxPartAbsoluteEpisodeNumber = partAbsoluteEpisodeNumber + animeMetadata.GetMainEpisodeCount() - 1
}
// If the first episode exists and has a valid absolute episode number, create a new MediaTreeAnalysisBranch
if azm.Episodes != nil && firstEp.AbsoluteEpisodeNumber > 0 {
if animeMetadata.Episodes != nil && firstEp.AbsoluteEpisodeNumber > 0 {
return &MediaTreeAnalysisBranch{
media: rel,
anizipMedia: azm,
animeMetadata: animeMetadata,
minPartAbsoluteEpisodeNumber: partAbsoluteEpisodeNumber,
maxPartAbsoluteEpisodeNumber: maxPartAbsoluteEpisodeNumber,
minAbsoluteEpisode: firstEp.AbsoluteEpisodeNumber,
// The max absolute episode number is the first episode's absolute episode number plus the total episode count minus 1
// We subtract 1 because the first episode's absolute episode number is already included in the total episode count
// e.g, if the first episode's absolute episode number is 13 and the total episode count is 12, the max absolute episode number is 24
maxAbsoluteEpisode: firstEp.AbsoluteEpisodeNumber + (azm.GetMainEpisodeCount() - 1),
totalEpisodeCount: azm.GetMainEpisodeCount(),
maxAbsoluteEpisode: firstEp.AbsoluteEpisodeNumber + (animeMetadata.GetMainEpisodeCount() - 1),
totalEpisodeCount: animeMetadata.GetMainEpisodeCount(),
}, nil
}

View File

@@ -5,7 +5,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/test_utils"
"seanime/internal/util/limiter"
"testing"
@@ -20,6 +20,8 @@ func TestMediaTreeAnalysis(t *testing.T) {
anilistRateLimiter := limiter.NewAnilistLimiter()
tree := anilist.NewCompleteAnimeRelationTree()
metadataProvider := metadata.GetMockProvider(t)
tests := []struct {
name string
mediaId int
@@ -71,9 +73,9 @@ func TestMediaTreeAnalysis(t *testing.T) {
// +---------------------+
mta, err := NewMediaTreeAnalysis(&MediaTreeAnalysisOptions{
tree: tree,
anizipCache: anizip.NewCache(),
rateLimiter: limiter.NewLimiter(time.Minute, 25),
tree: tree,
metadataProvider: metadataProvider,
rateLimiter: limiter.NewLimiter(time.Minute, 25),
})
if err != nil {
t.Fatal("expected media tree analysis, got error:", err.Error())
@@ -103,6 +105,8 @@ func TestMediaTreeAnalysis2(t *testing.T) {
anilistRateLimiter := limiter.NewAnilistLimiter()
tree := anilist.NewCompleteAnimeRelationTree()
metadataProvider := metadata.GetMockProvider(t)
tests := []struct {
name string
mediaId int
@@ -143,9 +147,9 @@ func TestMediaTreeAnalysis2(t *testing.T) {
// +---------------------+
mta, err := NewMediaTreeAnalysis(&MediaTreeAnalysisOptions{
tree: tree,
anizipCache: anizip.NewCache(),
rateLimiter: limiter.NewLimiter(time.Minute, 25),
tree: tree,
metadataProvider: metadataProvider,
rateLimiter: limiter.NewLimiter(time.Minute, 25),
})
if err != nil {
t.Fatal("expected media tree analysis, got error:", err.Error())

View File

@@ -6,7 +6,7 @@ import (
"github.com/rs/zerolog"
"github.com/samber/lo"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/events"
"seanime/internal/library/anime"
"seanime/internal/library/filesystem"
@@ -30,6 +30,7 @@ type Scanner struct {
SkipIgnoredFiles bool
ScanSummaryLogger *summary.ScanSummaryLogger
ScanLogger *ScanLogger
MetadataProvider metadata.Provider
}
// Scan will scan the directory and return a list of anime.LocalFile.
@@ -38,7 +39,6 @@ func (scn *Scanner) Scan() (lfs []*anime.LocalFile, err error) {
defer util.HandlePanicWithError(&err)
completeAnimeCache := anilist.NewCompleteAnimeCache()
anizipCache := anizip.NewCache()
// Create a new Anilist rate limiter
anilistRateLimiter := limiter.NewAnilistLimiter()
@@ -177,14 +177,15 @@ func (scn *Scanner) Scan() (lfs []*anime.LocalFile, err error) {
// Fetch media needed for matching
mf, err := NewMediaFetcher(&MediaFetcherOptions{
Enhanced: scn.Enhanced,
Platform: scn.Platform,
LocalFiles: localFiles,
CompleteAnimeCache: completeAnimeCache,
AnizipCache: anizipCache,
Logger: scn.Logger,
AnilistRateLimiter: anilistRateLimiter,
ScanLogger: scn.ScanLogger,
Enhanced: scn.Enhanced,
Platform: scn.Platform,
MetadataProvider: scn.MetadataProvider,
LocalFiles: localFiles,
CompleteAnimeCache: completeAnimeCache,
Logger: scn.Logger,
AnilistRateLimiter: anilistRateLimiter,
DisableAnimeCollection: false,
ScanLogger: scn.ScanLogger,
})
if err != nil {
return nil, err
@@ -245,7 +246,7 @@ func (scn *Scanner) Scan() (lfs []*anime.LocalFile, err error) {
hydrator := &FileHydrator{
AllMedia: mc.NormalizedMedia,
LocalFiles: localFiles,
AnizipCache: anizipCache,
MetadataProvider: scn.MetadataProvider,
Platform: scn.Platform,
CompleteAnimeCache: completeAnimeCache,
AnilistRateLimiter: anilistRateLimiter,

View File

@@ -2,7 +2,7 @@ package scanner
import (
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/util"
@@ -19,9 +19,8 @@ func TestScanLogger(t *testing.T) {
t.Fatal(err.Error())
}
allMedia := animeCollection.GetAllAnime()
metadataProvider := metadata.GetMockProvider(t)
completeAnimeCache := anilist.NewCompleteAnimeCache()
anizipCache := anizip.NewCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
logger := util.NewLogger()
@@ -81,9 +80,10 @@ func TestScanLogger(t *testing.T) {
matcher := &Matcher{
LocalFiles: lfs,
MediaContainer: mc,
CompleteAnimeCache: nil,
CompleteAnimeCache: completeAnimeCache,
Logger: util.NewLogger(),
ScanLogger: scanLogger,
ScanSummaryLogger: nil,
}
err = matcher.MatchLocalFilesWithMedia()
@@ -99,11 +99,13 @@ func TestScanLogger(t *testing.T) {
LocalFiles: lfs,
AllMedia: mc.NormalizedMedia,
CompleteAnimeCache: completeAnimeCache,
AnizipCache: anizipCache,
Platform: anilistPlatform,
MetadataProvider: metadataProvider,
AnilistRateLimiter: anilistRateLimiter,
Logger: logger,
ScanLogger: scanLogger,
ScanSummaryLogger: nil,
ForceMediaId: 0,
}
fh.HydrateMetadata()

View File

@@ -45,7 +45,6 @@ type (
VLC *vlc2.VLC
MpcHc *mpchc2.MpcHc
Mpv *mpv.Mpv
MpvType string
WSEventManager events.WSEventManagerInterface
ContinuityManager *continuity.Manager
}

View File

@@ -4,7 +4,6 @@ import (
"github.com/goccy/go-json"
"github.com/samber/lo"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/database/db_bridge"
"seanime/internal/library/anime"
"seanime/internal/manga"
@@ -75,8 +74,6 @@ func (h *Hub) CreateSnapshot(opts *NewSnapshotOptions) error {
lfWrapper := anime.NewLocalFileWrapper(lfs)
lfEntries := lfWrapper.GetLocalEntries()
anizipCache := anizip.NewCache()
rateLimiter := limiter.NewLimiter(1*time.Second, 5)
for _, lfEntry := range lfEntries {
if !slices.Contains(opts.AnimeToDownload, lfEntry.GetMediaId()) {
@@ -100,7 +97,6 @@ func (h *Hub) CreateSnapshot(opts *NewSnapshotOptions) error {
_mediaEntry, err := anime.NewAnimeEntry(&anime.NewAnimeEntryOptions{
MediaId: lfEntry.GetMediaId(),
LocalFiles: lfs,
AnizipCache: anizipCache,
AnimeCollection: animeCollection,
Platform: h.platform,
MetadataProvider: h.metadataProvider,

View File

@@ -5,7 +5,7 @@ import (
"github.com/rs/zerolog"
"github.com/samber/lo"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/extension"
"seanime/internal/platforms/platform"
"seanime/internal/util/filecache"
@@ -19,7 +19,7 @@ type (
logger *zerolog.Logger
providerExtensionBank *extension.UnifiedBank
fileCacher *filecache.Cacher
anizipCache *anizip.Cache
metadataProvider metadata.Provider
platform platform.Platform
anilistBaseAnimeCache *anilist.BaseAnimeCache
}
@@ -63,17 +63,17 @@ type (
type (
NewRepositoryOptions struct {
Logger *zerolog.Logger
FileCacher *filecache.Cacher
AnizipCache *anizip.Cache
Platform platform.Platform
Logger *zerolog.Logger
FileCacher *filecache.Cacher
MetadataProvider metadata.Provider
Platform platform.Platform
}
)
func NewRepository(opts *NewRepositoryOptions) *Repository {
return &Repository{
logger: opts.Logger,
anizipCache: opts.AnizipCache,
metadataProvider: opts.MetadataProvider,
fileCacher: opts.FileCacher,
providerExtensionBank: extension.NewUnifiedBank(),
anilistBaseAnimeCache: anilist.NewBaseAnimeCache(),
@@ -145,8 +145,8 @@ func (r *Repository) GetMediaEpisodes(provider string, media *anilist.BaseAnime,
// | Anizip |
// +---------------------+
anizipMedia, err := anizip.FetchAniZipMediaC("anilist", mId, r.anizipCache)
foundAnizipMedia := err == nil && anizipMedia != nil
animeMetadata, err := r.metadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, mId)
foundAnimeMetadata := err == nil && animeMetadata != nil
// +---------------------+
// | Episode list |
@@ -160,18 +160,18 @@ func (r *Repository) GetMediaEpisodes(provider string, media *anilist.BaseAnime,
}
for _, episodeDetails := range ec.ProviderEpisodeList {
if foundAnizipMedia {
anizipEpisode, found := anizipMedia.Episodes[strconv.Itoa(episodeDetails.Number)]
if foundAnimeMetadata {
episodeMetadata, found := animeMetadata.Episodes[strconv.Itoa(episodeDetails.Number)]
if found {
img := anizipEpisode.Image
img := episodeMetadata.Image
if img == "" {
img = media.GetCoverImageSafe()
}
episodes = append(episodes, &Episode{
Number: episodeDetails.Number,
Title: anizipEpisode.GetTitle(),
Title: episodeMetadata.GetTitle(),
Image: img,
Description: anizipEpisode.Summary,
Description: episodeMetadata.Summary,
})
} else {
episodes = append(episodes, &Episode{

View File

@@ -5,6 +5,7 @@ import (
"errors"
"github.com/hekmon/transmissionrpc/v3"
"github.com/rs/zerolog"
"seanime/internal/api/metadata"
"seanime/internal/events"
"seanime/internal/torrent_clients/qbittorrent"
"seanime/internal/torrent_clients/qbittorrent/model"
@@ -21,12 +22,12 @@ const (
type (
Repository struct {
logger *zerolog.Logger
qBittorrentClient *qbittorrent.Client
transmission *transmission.Transmission
torrentRepository *torrent.Repository
provider string
logger *zerolog.Logger
qBittorrentClient *qbittorrent.Client
transmission *transmission.Transmission
torrentRepository *torrent.Repository
provider string
metadataProvider metadata.Provider
activeTorrentCountCtxCancel context.CancelFunc
activeTorrentCount *ActiveCount
}
@@ -37,6 +38,7 @@ type (
Transmission *transmission.Transmission
TorrentRepository *torrent.Repository
Provider string
MetadataProvider metadata.Provider
}
ActiveCount struct {
@@ -56,6 +58,7 @@ func NewRepository(opts *NewRepositoryOptions) *Repository {
transmission: opts.Transmission,
torrentRepository: opts.TorrentRepository,
provider: opts.Provider,
metadataProvider: opts.MetadataProvider,
activeTorrentCount: &ActiveCount{},
}
}

View File

@@ -77,10 +77,11 @@ func (r *Repository) SmartSelect(p *SmartSelectParams) error {
// AnalyzeTorrentFiles the torrent files
analyzer := torrent_analyzer.NewAnalyzer(&torrent_analyzer.NewAnalyzerOptions{
Logger: r.logger,
Filepaths: filepaths,
Media: p.Media,
Platform: p.Platform,
Logger: r.logger,
Filepaths: filepaths,
Media: p.Media,
Platform: p.Platform,
MetadataProvider: r.metadataProvider,
})
r.logger.Debug().Msg("torrent client: analyzing torrent files (smart select)")

View File

@@ -6,7 +6,7 @@ import (
lop "github.com/samber/lo/parallel"
"path/filepath"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/library/anime"
"seanime/internal/library/scanner"
"seanime/internal/platforms/platform"
@@ -18,10 +18,11 @@ type (
// Analyzer is a service similar to the scanner, but it is used to analyze torrent files.
// i.e. torrent files instead of local files.
Analyzer struct {
files []*File
media *anilist.CompleteAnime
platform platform.Platform
logger *zerolog.Logger
files []*File
media *anilist.CompleteAnime
platform platform.Platform
logger *zerolog.Logger
metadataProvider metadata.Provider
}
// Analysis contains the results of the analysis.
@@ -41,10 +42,11 @@ type (
type (
NewAnalyzerOptions struct {
Logger *zerolog.Logger
Filepaths []string // Filepath of the torrent files
Media *anilist.CompleteAnime // The media to compare the files with
Platform platform.Platform
Logger *zerolog.Logger
Filepaths []string // Filepath of the torrent files
Media *anilist.CompleteAnime // The media to compare the files with
Platform platform.Platform
MetadataProvider metadata.Provider
}
)
@@ -53,10 +55,11 @@ func NewAnalyzer(opts *NewAnalyzerOptions) *Analyzer {
return newFile(idx, filepath)
})
return &Analyzer{
files: files,
media: opts.Media,
platform: opts.Platform,
logger: opts.Logger,
files: files,
media: opts.Media,
platform: opts.Platform,
logger: opts.Logger,
metadataProvider: opts.MetadataProvider,
}
}
@@ -188,7 +191,6 @@ func (f *File) GetIndex() int {
func (a *Analyzer) scanFiles() error {
completeAnimeCache := anilist.NewCompleteAnimeCache()
anizipCache := anizip.NewCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
lfs := a.getLocalFiles() // Extract local files from the Files
@@ -232,10 +234,13 @@ func (a *Analyzer) scanFiles() error {
LocalFiles: lfs,
AllMedia: mc.NormalizedMedia,
CompleteAnimeCache: completeAnimeCache,
AnizipCache: anizipCache,
Platform: a.platform,
MetadataProvider: a.metadataProvider,
AnilistRateLimiter: anilistRateLimiter,
Logger: a.logger,
ScanLogger: nil,
ScanSummaryLogger: nil,
ForceMediaId: 0,
}
fh.HydrateMetadata()

View File

@@ -3,6 +3,7 @@ package torrent_analyzer
import (
"github.com/stretchr/testify/assert"
"seanime/internal/api/anilist"
"seanime/internal/api/metadata"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/test_utils"
"seanime/internal/util"
@@ -16,6 +17,7 @@ func TestSelectFilesFromSeason(t *testing.T) {
logger := util.NewLogger()
anilistClient := anilist.TestGetMockAnilistClient()
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClient, logger)
metadataProvider := metadata.GetMockProvider(t)
tests := []struct {
name string
@@ -80,10 +82,11 @@ func TestSelectFilesFromSeason(t *testing.T) {
}
analyzer := NewAnalyzer(&NewAnalyzerOptions{
Logger: logger,
Filepaths: tt.filepaths,
Media: media,
Platform: anilistPlatform,
Logger: logger,
Filepaths: tt.filepaths,
Media: media,
Platform: anilistPlatform,
MetadataProvider: metadataProvider,
})
// AnalyzeTorrentFiles

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/test_utils"
"seanime/internal/util"
@@ -21,6 +21,8 @@ func TestSmartSearch(t *testing.T) {
toshoPlatform := NewProvider(util.NewLogger())
metadataProvider := metadata.GetMockProvider(t)
tests := []struct {
name string
mId int
@@ -84,7 +86,7 @@ func TestSmartSearch(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
media, err := anilistPlatform.GetAnime(tt.mId)
anizipMedia, err := anizip.FetchAniZipMedia("anilist", media.GetID())
animeMetadata, err := metadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, tt.mId)
require.NoError(t, err)
queryMedia := hibiketorrent.Media{
@@ -107,7 +109,7 @@ func TestSmartSearch(t *testing.T) {
if assert.NoError(t, err) {
anizipEpisode, ok := anizipMedia.FindEpisode(strconv.Itoa(tt.episodeNumber))
episodeMetadata, ok := animeMetadata.FindEpisode(strconv.Itoa(tt.episodeNumber))
require.True(t, ok)
torrents, err := toshoPlatform.SmartSearch(hibiketorrent.AnimeSmartSearchOptions{
@@ -116,8 +118,8 @@ func TestSmartSearch(t *testing.T) {
Batch: tt.batch,
EpisodeNumber: tt.episodeNumber,
Resolution: tt.resolution,
AnidbAID: anizipMedia.Mappings.AnidbID,
AnidbEID: anizipEpisode.AnidbEid,
AnidbAID: animeMetadata.Mappings.AnidbId,
AnidbEID: episodeMetadata.AnidbEid,
BestReleases: false,
})

View File

@@ -2,7 +2,6 @@ package torrent
import (
"github.com/rs/zerolog"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/extension"
"seanime/internal/util/result"
@@ -15,7 +14,6 @@ type (
extensionBank *extension.UnifiedBank
animeProviderSearchCaches *result.Map[string, *result.Cache[string, *SearchData]]
animeProviderSmartSearchCaches *result.Map[string, *result.Cache[string, *SearchData]]
anizipCache *anizip.Cache
settings RepositorySettings
metadataProvider metadata.Provider
mu sync.Mutex
@@ -38,7 +36,6 @@ func NewRepository(opts *NewRepositoryOptions) *Repository {
extensionBank: extension.NewUnifiedBank(),
animeProviderSearchCaches: result.NewResultMap[string, *result.Cache[string, *SearchData]](),
animeProviderSmartSearchCaches: result.NewResultMap[string, *result.Cache[string, *SearchData]](),
anizipCache: anizip.NewCache(),
settings: RepositorySettings{},
mu: sync.Mutex{},
}

View File

@@ -12,7 +12,7 @@ import (
func getTestRepo(t *testing.T) *Repository {
logger := util.NewLogger()
metadataProvider := metadata.TestGetMockProvider(t)
metadataProvider := metadata.GetMockProvider(t)
extensionBank := extension.NewUnifiedBank()

View File

@@ -7,7 +7,7 @@ import (
"github.com/samber/lo"
"github.com/samber/mo"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/util"
@@ -76,10 +76,10 @@ func (r *Repository) SearchAnime(opts AnimeSearchOptions) (ret *SearchData, err
var torrents []*hibiketorrent.AnimeTorrent
// Fetch Anizip media
anizipMedia := mo.None[*anizip.Media]()
anizipMediaF, err := anizip.FetchAniZipMediaC("anilist", opts.Media.ID, r.anizipCache)
animeMetadata := mo.None[*metadata.AnimeMetadata]()
animeMetadataF, err := r.metadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, opts.Media.GetID())
if err == nil {
anizipMedia = mo.Some(anizipMediaF)
animeMetadata = mo.Some(animeMetadataF)
}
queryMedia := hibiketorrent.Media{
@@ -101,7 +101,7 @@ func (r *Repository) SearchAnime(opts AnimeSearchOptions) (ret *SearchData, err
}
//// Force simple search if AniZip media is absent
//if opts.Type == AnimeSearchTypeSmart && anizipMedia.IsAbsent() {
//if opts.Type == AnimeSearchTypeSmart && animeMetadata.IsAbsent() {
// opts.Type = AnimeSearchTypeSimple
//}
@@ -113,17 +113,17 @@ func (r *Repository) SearchAnime(opts AnimeSearchOptions) (ret *SearchData, err
anidbEID := 0
// Get the AniDB Anime ID and Episode ID
if anizipMedia.IsPresent() {
if animeMetadata.IsPresent() {
// Override absolute offset value of queryMedia
queryMedia.AbsoluteSeasonOffset = anizipMedia.MustGet().GetOffset()
queryMedia.AbsoluteSeasonOffset = animeMetadata.MustGet().GetOffset()
if anizipMedia.MustGet().GetMappings() != nil {
if animeMetadata.MustGet().GetMappings() != nil {
anidbAID = anizipMedia.MustGet().GetMappings().AnidbID
anidbAID = animeMetadata.MustGet().GetMappings().AnidbId
// Find Anizip Episode based on inputted episode number
anizipEpisode, found := anizipMedia.MustGet().FindEpisode(strconv.Itoa(opts.EpisodeNumber))
episodeMetadata, found := animeMetadata.MustGet().FindEpisode(strconv.Itoa(opts.EpisodeNumber))
if found {
anidbEID = anizipEpisode.AnidbEid
anidbEID = episodeMetadata.AnidbEid
}
}
}
@@ -182,10 +182,10 @@ func (r *Repository) SearchAnime(opts AnimeSearchOptions) (ret *SearchData, err
defer wg.Done()
preview := r.createAnimeTorrentPreview(createAnimeTorrentPreviewOptions{
torrent: t,
media: opts.Media,
anizipMedia: anizipMedia,
searchOpts: &opts,
torrent: t,
media: opts.Media,
animeMetadata: animeMetadata,
searchOpts: &opts,
})
if preview != nil {
previews = append(previews, preview)
@@ -228,10 +228,10 @@ func (r *Repository) SearchAnime(opts AnimeSearchOptions) (ret *SearchData, err
}
type createAnimeTorrentPreviewOptions struct {
torrent *hibiketorrent.AnimeTorrent
media *anilist.BaseAnime
anizipMedia mo.Option[*anizip.Media]
searchOpts *AnimeSearchOptions
torrent *hibiketorrent.AnimeTorrent
media *anilist.BaseAnime
animeMetadata mo.Option[*metadata.AnimeMetadata]
searchOpts *AnimeSearchOptions
}
func (r *Repository) createAnimeTorrentPreview(opts createAnimeTorrentPreviewOptions) *Preview {
@@ -280,26 +280,26 @@ func (r *Repository) createAnimeTorrentPreview(opts createAnimeTorrentPreviewOpt
opts.torrent.EpisodeNumber = 1
}
if opts.anizipMedia.IsPresent() {
if opts.animeMetadata.IsPresent() {
// normalize episode number
if opts.torrent.EpisodeNumber >= 0 && opts.torrent.EpisodeNumber > opts.media.GetCurrentEpisodeCount() {
opts.torrent.EpisodeNumber = opts.torrent.EpisodeNumber - opts.anizipMedia.MustGet().GetOffset()
opts.torrent.EpisodeNumber = opts.torrent.EpisodeNumber - opts.animeMetadata.MustGet().GetOffset()
}
anizipMedia := opts.anizipMedia.MustGet()
_, foundEp := anizipMedia.FindEpisode(strconv.Itoa(opts.searchOpts.EpisodeNumber))
animeMetadata := opts.animeMetadata.MustGet()
_, foundEp := animeMetadata.FindEpisode(strconv.Itoa(opts.searchOpts.EpisodeNumber))
if foundEp {
var episode *anime.Episode
// Remove the episode if the parsed episode number is not the same as the search option
if isProbablySameEpisode(parsedData.EpisodeNumber, opts.searchOpts.EpisodeNumber, opts.anizipMedia.MustGet().GetOffset()) {
if isProbablySameEpisode(parsedData.EpisodeNumber, opts.searchOpts.EpisodeNumber, opts.animeMetadata.MustGet().GetOffset()) {
ep := opts.searchOpts.EpisodeNumber
episode = anime.NewEpisode(&anime.NewEpisodeOptions{
LocalFile: nil,
OptionalAniDBEpisode: strconv.Itoa(ep),
AnizipMedia: anizipMedia,
AnimeMetadata: animeMetadata,
Media: opts.media,
ProgressOffset: 0,
IsDownloaded: false,
@@ -321,7 +321,7 @@ func (r *Repository) createAnimeTorrentPreview(opts createAnimeTorrentPreviewOpt
var episode *anime.Episode
// Remove the episode if the parsed episode number is not the same as the search option
if isProbablySameEpisode(parsedData.EpisodeNumber, opts.searchOpts.EpisodeNumber, opts.anizipMedia.MustGet().GetOffset()) {
if isProbablySameEpisode(parsedData.EpisodeNumber, opts.searchOpts.EpisodeNumber, opts.animeMetadata.MustGet().GetOffset()) {
displayTitle := ""
if len(parsedData.EpisodeNumber) == 1 && parsedData.EpisodeNumber[0] != strconv.Itoa(opts.searchOpts.EpisodeNumber) {
displayTitle = fmt.Sprintf("Episode %s", parsedData.EpisodeNumber[0])
@@ -337,7 +337,7 @@ func (r *Repository) createAnimeTorrentPreview(opts createAnimeTorrentPreviewOpt
AbsoluteEpisodeNumber: 0,
LocalFile: nil,
IsDownloaded: false,
EpisodeMetadata: anime.NewEpisodeMetadata(opts.anizipMedia.MustGet(), nil, opts.media, r.metadataProvider),
EpisodeMetadata: anime.NewEpisodeMetadata(opts.animeMetadata.MustGet(), nil, opts.media, r.metadataProvider),
FileMetadata: nil,
IsInvalid: false,
MetadataIssue: "",

View File

@@ -3,7 +3,7 @@ package torrentstream
import (
"fmt"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/library/anime"
"strconv"
"sync"
@@ -21,7 +21,7 @@ type (
HydrateStreamCollectionOptions struct {
AnimeCollection *anilist.AnimeCollection
LibraryCollection *anime.LibraryCollection
AnizipCache *anizip.Cache
MetadataProvider metadata.Provider
}
)
@@ -93,13 +93,13 @@ func (r *Repository) HydrateStreamCollection(opts *HydrateStreamCollectionOption
}
// Get the media info
anizipMedia, err := anizip.FetchAniZipMediaC("anilist", mediaId, r.anizipCache)
animeMetadata, err := opts.MetadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, mediaId)
if err != nil {
r.logger.Error().Err(err).Msg("torrentstream: could not fetch AniDB media")
return
}
_, found := anizipMedia.FindEpisode(strconv.Itoa(nextEpisodeToWatch))
_, found := animeMetadata.FindEpisode(strconv.Itoa(nextEpisodeToWatch))
//if !found {
// r.logger.Error().Msg("torrentstream: could not find episode in AniDB")
// return
@@ -107,7 +107,7 @@ func (r *Repository) HydrateStreamCollection(opts *HydrateStreamCollectionOption
progressOffset := 0
anidbEpisode := strconv.Itoa(nextEpisodeToWatch)
if anime.HasDiscrepancy(entry.GetMedia(), anizipMedia) {
if anime.HasDiscrepancy(entry.GetMedia(), animeMetadata) {
progressOffset = 1
if nextEpisodeToWatch == 1 {
anidbEpisode = "S1"
@@ -118,7 +118,7 @@ func (r *Repository) HydrateStreamCollection(opts *HydrateStreamCollectionOption
episode := anime.NewEpisode(&anime.NewEpisodeOptions{
LocalFile: nil,
OptionalAniDBEpisode: anidbEpisode,
AnizipMedia: anizipMedia,
AnimeMetadata: animeMetadata,
Media: entry.GetMedia(),
ProgressOffset: progressOffset,
IsDownloaded: false,

View File

@@ -5,28 +5,24 @@ import (
"github.com/samber/lo"
"github.com/stretchr/testify/require"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/events"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/test_utils"
"seanime/internal/util"
"seanime/internal/util/filecache"
"testing"
)
func TestStreamCollection(t *testing.T) {
t.Skip("Incomplete")
test_utils.SetTwoLevelDeep()
test_utils.InitTestProvider(t, test_utils.Anilist())
logger := util.NewLogger()
filecacher, err := filecache.NewCacher(t.TempDir())
require.NoError(t, err)
metadataProvider := metadata.TestGetMockProvider(t)
metadataProvider := metadata.GetMockProvider(t)
anilistClient := anilist.TestGetMockAnilistClient()
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClient, util.NewLogger())
anizipCache := anizip.NewCache()
anilistPlatform.SetUsername(test_utils.ConfigData.Provider.AnilistUsername)
animeCollection, err := anilistPlatform.GetAnimeCollection(false)
require.NoError(t, err)
@@ -34,16 +30,14 @@ func TestStreamCollection(t *testing.T) {
repo := NewRepository(&NewRepositoryOptions{
Logger: logger,
AnizipCache: anizip.NewCache(),
BaseAnimeCache: anilist.NewBaseAnimeCache(),
CompleteAnimeCache: anilist.NewCompleteAnimeCache(),
Platform: anilistPlatform,
MetadataProvider: metadata.NewProvider(&metadata.NewProviderImplOptions{
Logger: logger,
FileCacher: filecacher,
}),
PlaybackManager: nil,
WSEventManager: events.NewMockWSEventManager(logger),
MetadataProvider: metadataProvider,
WSEventManager: events.NewMockWSEventManager(logger),
TorrentRepository: nil,
PlaybackManager: nil,
Database: nil,
})
// Mock Anilist collection and local files
@@ -72,7 +66,6 @@ func TestStreamCollection(t *testing.T) {
libraryCollection, err := anime.NewLibraryCollection(&anime.NewLibraryCollectionOptions{
AnimeCollection: animeCollection,
LocalFiles: lfs,
AnizipCache: anizipCache,
Platform: anilistPlatform,
MetadataProvider: metadataProvider,
})
@@ -82,7 +75,6 @@ func TestStreamCollection(t *testing.T) {
repo.HydrateStreamCollection(&HydrateStreamCollectionOptions{
AnimeCollection: animeCollection,
LibraryCollection: libraryCollection,
AnizipCache: anizipCache,
})
spew.Dump(libraryCollection)

View File

@@ -172,10 +172,11 @@ searchLoop:
// Create a new Torrent Analyzer
analyzer := torrentanalyzer.NewAnalyzer(&torrentanalyzer.NewAnalyzerOptions{
Logger: r.logger,
Filepaths: filepaths,
Media: media,
Platform: r.platform,
Logger: r.logger,
Filepaths: filepaths,
Media: media,
Platform: r.platform,
MetadataProvider: r.metadataProvider,
})
r.logger.Debug().Msgf("torrentstream: Analyzing torrent %s", searchT.Link)
@@ -296,10 +297,11 @@ func (r *Repository) findBestTorrentFromManualSelection(t *hibiketorrent.AnimeTo
// Create a new Torrent Analyzer
analyzer := torrentanalyzer.NewAnalyzer(&torrentanalyzer.NewAnalyzerOptions{
Logger: r.logger,
Filepaths: filepaths,
Media: media,
Platform: r.platform,
Logger: r.logger,
Filepaths: filepaths,
Media: media,
Platform: r.platform,
MetadataProvider: r.metadataProvider,
})
// Analyze torrent files

View File

@@ -24,7 +24,7 @@ func (r *Repository) NewEpisodeCollection(mId int) (ec *EpisodeCollection, err e
}
// Get the media info, this is cached
completeAnime, anizipMedia, err := r.getMediaInfo(mId)
completeAnime, animeMetadata, err := r.getMediaInfo(mId)
if err != nil {
return nil, err
}
@@ -40,7 +40,7 @@ func (r *Repository) NewEpisodeCollection(mId int) (ec *EpisodeCollection, err e
info, err := anime.NewAnimeEntryDownloadInfo(&anime.NewAnimeEntryDownloadInfoOptions{
LocalFiles: nil,
AnizipMedia: anizipMedia,
AnimeMetadata: animeMetadata,
Progress: lo.ToPtr(0), // Progress is 0 because we want the entire list
Status: lo.ToPtr(anilist.MediaListStatusCurrent),
Media: completeAnime.ToBaseAnime(),

View File

@@ -8,7 +8,6 @@ import (
"os"
"path/filepath"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/database/db"
"seanime/internal/database/models"
@@ -33,7 +32,6 @@ type (
// Injected dependencies
torrentRepository *torrent.Repository
anizipCache *anizip.Cache
baseAnimeCache *anilist.BaseAnimeCache
completeAnimeCache *anilist.CompleteAnimeCache
platform platform.Platform
@@ -52,7 +50,6 @@ type (
NewRepositoryOptions struct {
Logger *zerolog.Logger
AnizipCache *anizip.Cache
TorrentRepository *torrent.Repository
BaseAnimeCache *anilist.BaseAnimeCache
CompleteAnimeCache *anilist.CompleteAnimeCache
@@ -67,17 +64,22 @@ type (
// NewRepository creates a new injectable Repository instance
func NewRepository(opts *NewRepositoryOptions) *Repository {
ret := &Repository{
logger: opts.Logger,
anizipCache: opts.AnizipCache,
baseAnimeCache: opts.BaseAnimeCache,
completeAnimeCache: opts.CompleteAnimeCache,
platform: opts.Platform,
metadataProvider: opts.MetadataProvider,
playbackManager: opts.PlaybackManager,
wsEventManager: opts.WSEventManager,
torrentRepository: opts.TorrentRepository,
selectionHistoryMap: result.NewResultMap[int, *hibiketorrent.AnimeTorrent](),
db: opts.Database,
client: nil,
serverManager: nil,
settings: mo.Option[Settings]{},
currentEpisodeCollection: mo.Option[*EpisodeCollection]{},
selectionHistoryMap: result.NewResultMap[int, *hibiketorrent.AnimeTorrent](),
torrentRepository: opts.TorrentRepository,
baseAnimeCache: opts.BaseAnimeCache,
completeAnimeCache: opts.CompleteAnimeCache,
platform: opts.Platform,
wsEventManager: opts.WSEventManager,
metadataProvider: opts.MetadataProvider,
playbackManager: opts.PlaybackManager,
mediaPlayerRepository: nil,
mediaPlayerRepositorySubscriber: nil,
logger: opts.Logger,
db: opts.Database,
}
ret.client = NewClient(ret)
ret.serverManager = newServerManager(ret)

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/samber/lo"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/continuity"
"seanime/internal/database/db"
@@ -21,7 +20,7 @@ import (
)
func TestTorrentstream(t *testing.T) {
t.Skip()
t.Skip("Incomplete")
test_utils.SetTwoLevelDeep()
test_utils.InitTestProvider(t, test_utils.Anilist(), test_utils.MediaPlayer(), test_utils.Torrentstream())
@@ -74,7 +73,6 @@ func TestTorrentstream(t *testing.T) {
repo := NewRepository(&NewRepositoryOptions{
Logger: logger,
AnizipCache: anizip.NewCache(),
BaseAnimeCache: anilist.NewBaseAnimeCache(),
CompleteAnimeCache: anilist.NewCompleteAnimeCache(),
Platform: anilistPlatform,
@@ -82,9 +80,10 @@ func TestTorrentstream(t *testing.T) {
Logger: logger,
FileCacher: filecacher,
}),
PlaybackManager: playbackManager,
WSEventManager: wsEventManager,
Database: database,
PlaybackManager: playbackManager,
WSEventManager: wsEventManager,
Database: database,
TorrentRepository: nil,
})
repo.SetMediaPlayerRepository(mediaPlayerRepo)
defer repo.Shutdown()

View File

@@ -6,7 +6,7 @@ import (
"github.com/anacrolix/torrent"
"github.com/samber/mo"
"seanime/internal/api/anilist"
"seanime/internal/api/anizip"
"seanime/internal/api/metadata"
"seanime/internal/events"
"seanime/internal/library/playbackmanager"
"strconv"
@@ -213,8 +213,8 @@ func (r *Repository) DropTorrent() error {
return nil
}
for _, torrent := range r.client.torrentClient.MustGet().Torrents() {
torrent.Drop()
for _, t := range r.client.torrentClient.MustGet().Torrents() {
t.Drop()
}
// Also stop the server, since it's dropped
@@ -228,7 +228,7 @@ func (r *Repository) DropTorrent() error {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (r *Repository) getMediaInfo(mediaId int) (media *anilist.CompleteAnime, anizipMedia *anizip.Media, err error) {
func (r *Repository) getMediaInfo(mediaId int) (media *anilist.CompleteAnime, animeMetadata *metadata.AnimeMetadata, err error) {
// Get the media
var found bool
media, found = r.completeAnimeCache.Get(mediaId)
@@ -241,7 +241,7 @@ func (r *Repository) getMediaInfo(mediaId int) (media *anilist.CompleteAnime, an
}
// Get the media
anizipMedia, err = anizip.FetchAniZipMediaC("anilist", mediaId, r.anizipCache)
animeMetadata, err = r.metadataProvider.GetAnimeMetadata(metadata.AnilistPlatform, mediaId)
if err != nil {
return nil, nil, fmt.Errorf("torrentstream: Could not fetch AniDB media: %w", err)
}
@@ -249,14 +249,14 @@ func (r *Repository) getMediaInfo(mediaId int) (media *anilist.CompleteAnime, an
return
}
func (r *Repository) getEpisodeInfo(anizipMedia *anizip.Media, aniDBEpisode string) (episode *anizip.Episode, err error) {
if anizipMedia == nil {
func (r *Repository) getEpisodeInfo(animeMetadata *metadata.AnimeMetadata, aniDBEpisode string) (episode *metadata.EpisodeMetadata, err error) {
if animeMetadata == nil {
return nil, fmt.Errorf("torrentstream: Anizip media is nil")
}
// Get the episode
var found bool
episode, found = anizipMedia.FindEpisode(aniDBEpisode)
episode, found = animeMetadata.FindEpisode(aniDBEpisode)
if !found {
return nil, fmt.Errorf("torrentstream: Episode not found in the Anizip media")
}

View File

@@ -7,7 +7,6 @@ import (
"io"
"net/http"
"runtime"
"strconv"
"strings"
)
@@ -82,50 +81,3 @@ func (u *Updater) fetchLatestRelease() (*Release, error) {
return &res.Release, nil
}
// compareVersion compares current and latest version is returns true if the latest version is newer than the current version.
// It also returns the update type (patch, minor, major) if the latest version is newer than the current version.
func (u *Updater) compareVersion(currVersion string, tagName string) (string, bool) {
tagName = strings.TrimPrefix(tagName, "v")
currVParts := strings.Split(currVersion, ".")
latestVParts := strings.Split(tagName, ".")
if len(currVParts) != 3 || len(latestVParts) != 3 {
return "", false
}
currMajor, _ := strconv.Atoi(currVParts[0])
currMinor, _ := strconv.Atoi(currVParts[1])
currPatch, _ := strconv.Atoi(currVParts[2])
latestMajor, _ := strconv.Atoi(latestVParts[0])
latestMinor, _ := strconv.Atoi(latestVParts[1])
latestPatch, _ := strconv.Atoi(latestVParts[2])
if currMajor > latestMajor {
return "", false
}
if currMajor < latestMajor {
return MajorRelease, true
}
if currMinor > latestMinor {
return "", false
}
if currMinor < latestMinor {
return MinorRelease, true
}
if currPatch > latestPatch {
return "", false
}
if currPatch < latestPatch {
return PatchRelease, true
}
return "", false
}

View File

@@ -4,6 +4,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"
"seanime/internal/constants"
"seanime/internal/util"
"testing"
)
@@ -30,8 +31,6 @@ func TestUpdater_FetchLatestRelease(t *testing.T) {
func TestUpdater_CompareVersion(t *testing.T) {
updater := Updater{}
tests := []struct {
currVersion string
latestVersion string
@@ -62,11 +61,16 @@ func TestUpdater_CompareVersion(t *testing.T) {
latestVersion: "0.2.1",
shouldUpdate: false,
},
{
currVersion: "1.0.0",
latestVersion: "0.2.1",
shouldUpdate: false,
},
}
for _, tt := range tests {
t.Run(tt.latestVersion, func(t *testing.T) {
updateType, shouldUpdate := updater.compareVersion(tt.currVersion, tt.latestVersion)
updateType, shouldUpdate := util.CompareVersion(tt.currVersion, tt.latestVersion)
assert.Equal(t, tt.shouldUpdate, shouldUpdate)
t.Log(tt.latestVersion, updateType)
})

View File

@@ -1,6 +1,10 @@
package updater
import "github.com/rs/zerolog"
import (
"github.com/rs/zerolog"
"seanime/internal/util"
"strings"
)
const (
PatchRelease = "patch"
@@ -68,11 +72,21 @@ func (u *Updater) GetLatestUpdate() (*Update, error) {
return nil, err
}
updateType, shouldUpdate := u.compareVersion(u.CurrentVersion, rl.TagName)
newV := strings.TrimPrefix(rl.TagName, "v")
updateTypeI, shouldUpdate := util.CompareVersion(u.CurrentVersion, newV)
if !shouldUpdate {
return nil, nil
}
updateType := ""
if updateTypeI == -1 {
updateType = MinorRelease
} else if updateTypeI == -2 {
updateType = PatchRelease
} else if updateTypeI == -3 {
updateType = MajorRelease
}
return &Update{
Release: rl,
Type: updateType,

View File

@@ -46,25 +46,26 @@ func CompareVersion(current string, b string) (comp int, shouldUpdate bool) {
return 0, false
}
if currV.Major() > otherV.Major() {
comp *= 3
} else if currV.Minor() > otherV.Minor() {
comp *= 2
} else if currV.Patch() > otherV.Patch() {
comp *= 1
} else if currV.GreaterThan(otherV) {
if currV.GreaterThan(otherV) {
shouldUpdate = false
} else if currV.Major() < otherV.Major() {
comp *= 3
shouldUpdate = true
} else if currV.Minor() < otherV.Minor() {
comp *= 2
shouldUpdate = true
} else if currV.Patch() < otherV.Patch() {
comp *= 1
shouldUpdate = true
if currV.Major() > otherV.Major() {
comp *= 3
} else if currV.Minor() > otherV.Minor() {
comp *= 2
} else if currV.Patch() > otherV.Patch() {
comp *= 1
}
} else if currV.LessThan(otherV) {
shouldUpdate = true
if currV.Major() < otherV.Major() {
comp *= 3
} else if currV.Minor() < otherV.Minor() {
comp *= 2
} else if currV.Patch() < otherV.Patch() {
comp *= 1
}
}
return comp, shouldUpdate
@@ -73,5 +74,5 @@ func CompareVersion(current string, b string) (comp int, shouldUpdate bool) {
func VersionIsOlderThan(version string, compare string) bool {
comp, shouldUpdate := CompareVersion(version, compare)
// shouldUpdate is false means the current version is newer
return comp < 0 && !shouldUpdate
return comp < 0 && shouldUpdate
}

View File

@@ -28,6 +28,27 @@ func TestCompareVersion(t *testing.T) {
expectedOutput: -3,
shouldUpdate: true,
},
{
name: "Current version is older by minor version",
currVersion: "0.2.2",
otherVersion: "0.3.0",
expectedOutput: -2,
shouldUpdate: true,
},
{
name: "Current version is older by major version",
currVersion: "0.2.2",
otherVersion: "3.0.0",
expectedOutput: -3,
shouldUpdate: true,
},
{
name: "Current version is older by minor version",
currVersion: "0.2.2",
otherVersion: "0.2.3",
expectedOutput: -1,
shouldUpdate: true,
},
{
name: "Current version is newer by minor version",
currVersion: "1.2.0",