Skip to content

Commit

Permalink
Merge pull request #185 from pietrocaselani/episode_image_fix
Browse files Browse the repository at this point in the history
Split ImageRepository into Movie, Show and Episode Repositories
  • Loading branch information
pietrocaselani authored Jan 25, 2019
2 parents ee84ff8 + 8d8d27f commit fd23d6c
Show file tree
Hide file tree
Showing 31 changed files with 1,003 additions and 374 deletions.
74 changes: 60 additions & 14 deletions CouchTracker.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

25 changes: 22 additions & 3 deletions CouchTrackerApp/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ final class Environment {
let centralSynchronizer: CentralSynchronizer
let userDefaults: UserDefaults
let genreRepository: GenreRepository
let imageRepository: ImageRepository
let configurationRepository: ConfigurationCachedRepository
let movieImageRepository: MovieImageCachedRepository
let showImageRepository: ShowImageCachedRepository
let episodeImageRepository: EpisodeImageCachedRepository

var currentAppState: AppConfigurationsState {
return Environment.getAppState(userDefaults: userDefaults)
Expand All @@ -41,7 +46,7 @@ final class Environment {

let debug: Bool

var plugins = [PluginType]()
let plugins = [PluginType]()

#if DEBUG
let traktClientId = Secrets.Trakt.clientId.isEmpty
Expand All @@ -53,8 +58,6 @@ final class Environment {
}

debug = true

// plugins.append(NetworkLoggerPlugin(verbose: false))
#else
debug = false
#endif
Expand Down Expand Up @@ -130,5 +133,21 @@ final class Environment {

centralSynchronizer = CentralSynchronizer.initialize(watchedShowsSynchronizer: showsSynchronizer,
appConfigObservable: appConfigurationsObservable)

configurationRepository = ConfigurationCachedRepository(tmdbProvider: tmdb)

movieImageRepository = MovieImageCachedRepository(tmdb: tmdb,
configurationRepository: configurationRepository)

showImageRepository = ShowImageCachedRepository(tmdb: tmdb,
configurationRepository: configurationRepository)

episodeImageRepository = EpisodeImageCachedRepository(tmdb: tmdb,
tvdb: tvdb,
configurationRepository: configurationRepository)

imageRepository = DefaultImageRepository(movieImageRepository: movieImageRepository,
showImageRepository: showImageRepository,
episodeImageRepository: episodeImageRepository)
}
}
9 changes: 1 addition & 8 deletions CouchTrackerApp/MovieDetails/MovieDetailsModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,13 @@ public final class MovieDetailsModule {

public static func setupModule(movieIds: MovieIds) -> BaseView {
let trakt = Environment.instance.trakt
let tmdb = Environment.instance.tmdb
let tvdb = Environment.instance.tvdb
let schedulers = Environment.instance.schedulers
let appConfigObservable = Environment.instance.appConfigurationsObservable
let genreRepository = Environment.instance.genreRepository
let imageRespository = Environment.instance.imageRepository

let repository = MovieDetailsAPIRepository(traktProvider: trakt, schedulers: schedulers)

let configurationRepository = ConfigurationCachedRepository(tmdbProvider: tmdb)
let imageRespository = ImageCachedRepository(tmdb: tmdb,
tvdb: tvdb,
cofigurationRepository: configurationRepository,
schedulers: schedulers)

let interactor = MovieDetailsService(repository: repository, genreRepository: genreRepository,
imageRepository: imageRespository, movieIds: movieIds)

Expand Down
11 changes: 1 addition & 10 deletions CouchTrackerApp/Search/SearchModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,8 @@ protocol SearchDataSource: UICollectionViewDataSource {
enum SearchModule {
static func setupModule(searchTypes: [SearchType]) -> BaseView {
let environment = Environment.instance
let tmdb = environment.tmdb
let tvdb = environment.tvdb
let schedulers = environment.schedulers
let trakt = environment.trakt

let configurationRepository = ConfigurationCachedRepository(tmdbProvider: tmdb)

let imageRepository = ImageCachedRepository(tmdb: tmdb,
tvdb: tvdb,
cofigurationRepository: configurationRepository,
schedulers: schedulers)
let imageRepository = Environment.instance.imageRepository

let posterCellInteractor = PosterCellService(imageRepository: imageRepository)

Expand Down
10 changes: 1 addition & 9 deletions CouchTrackerApp/Show/Episode/ShowEpisodeModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,11 @@ import CouchTrackerCore
final class ShowEpisodeModule {
static func setupModule(for show: WatchedShowEntity) -> BaseView {
let trakt = Environment.instance.trakt
let tmdb = Environment.instance.tmdb
let tvdb = Environment.instance.tvdb
let schedulers = Environment.instance.schedulers
let appConfigsObservable = Environment.instance.appConfigurationsObservable
let hideSpecials = Environment.instance.currentAppState.hideSpecials
let showSynchronizer = Environment.instance.showSynchronizer

let configurationRepository = ConfigurationCachedRepository(tmdbProvider: tmdb)

let imageRepository = ImageCachedRepository(tmdb: tmdb,
tvdb: tvdb,
cofigurationRepository: configurationRepository,
schedulers: schedulers)
let imageRepository = Environment.instance.imageRepository

let showEpisodeNetwork = ShowEpisodeMoyaNetwork(trakt: trakt, schedulers: schedulers)

Expand Down
9 changes: 1 addition & 8 deletions CouchTrackerApp/Show/Overview/ShowOverviewModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,12 @@ final class ShowOverviewModule {

static func setupModule(showIds: ShowIds) -> BaseView {
let trakt = Environment.instance.trakt
let tmdb = Environment.instance.tmdb
let tvdb = Environment.instance.tvdb
let schedulers = Environment.instance.schedulers
let genreRepository = Environment.instance.genreRepository
let imageRepository = Environment.instance.imageRepository

let repository = ShowOverviewAPIRepository(traktProvider: trakt, schedulers: schedulers)

let configurationRepository = ConfigurationCachedRepository(tmdbProvider: tmdb)
let imageRepository = ImageCachedRepository(tmdb: tmdb,
tvdb: tvdb,
cofigurationRepository: configurationRepository,
schedulers: schedulers)

let interactor = ShowOverviewService(showIds: showIds, repository: repository,
genreRepository: genreRepository, imageRepository: imageRepository)

Expand Down
9 changes: 1 addition & 8 deletions CouchTrackerApp/Shows/Progress/ShowsProgressModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,10 @@ enum ShowsProgressModule {
}

static func setupModule() -> BaseView {
let tmdb = Environment.instance.tmdb
let tvdb = Environment.instance.tvdb
let schedulers = Environment.instance.schedulers
let traktLoginObservable = Environment.instance.loginObservable
let userDefaults = Environment.instance.userDefaults

let configurationRepository = ConfigurationCachedRepository(tmdbProvider: tmdb)
let imageRepository = ImageCachedRepository(tmdb: tmdb,
tvdb: tvdb,
cofigurationRepository: configurationRepository,
schedulers: schedulers)
let imageRepository = Environment.instance.imageRepository

let listStateDataSource = ShowsProgressListStateDefaultDataSource(userDefaults: userDefaults)

Expand Down
8 changes: 1 addition & 7 deletions CouchTrackerApp/Trending/TrendingModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,11 @@ final class TrendingModule {

static func setupModule(for trendingType: TrendingType) -> BaseView {
let traktProvider = Environment.instance.trakt
let tmdb = Environment.instance.tmdb
let tvdb = Environment.instance.tvdb
let schedulers = Environment.instance.schedulers
let genreRepository = Environment.instance.genreRepository
let imageRepository = Environment.instance.imageRepository

let repository = TrendingCacheRepository(traktProvider: traktProvider, schedulers: schedulers)
let configurationRepository = ConfigurationCachedRepository(tmdbProvider: tmdb)
let imageRepository = ImageCachedRepository(tmdb: tmdb,
tvdb: tvdb,
cofigurationRepository: configurationRepository,
schedulers: schedulers)

let view = TrendingViewController()

Expand Down
13 changes: 13 additions & 0 deletions CouchTrackerCore/Configuration/ConfigurationCachedRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@ import TMDBSwift

public final class ConfigurationCachedRepository: ConfigurationRepository {
private let tmdbProvider: TMDBProvider
private var cachedConfigurations: Configuration?

public init(tmdbProvider: TMDBProvider) {
self.tmdbProvider = tmdbProvider
}

public func fetchConfiguration() -> Observable<Configuration> {
guard let configuration = cachedConfigurations else {
return fetchConfigrationsFromAPI()
}

return Observable.just(configuration)
}

private func fetchConfigrationsFromAPI() -> Observable<Configuration> {
return tmdbProvider.configuration.rx.request(.configuration)
.filterSuccessfulStatusAndRedirectCodes()
.map(Configuration.self)
.do(onSuccess: { [weak self] config in
self?.cachedConfigurations = config
})
.asObservable()
}
}
31 changes: 31 additions & 0 deletions CouchTrackerCore/Images/DefaultImageRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import RxSwift

public final class DefaultImageRepository: ImageRepository {
private let movieImageRepository: MovieImageRepository
private let showImageRepository: ShowImageRepository
private let episodeImageRepository: EpisodeImageRepository

public init(movieImageRepository: MovieImageRepository,
showImageRepository: ShowImageRepository,
episodeImageRepository: EpisodeImageRepository) {
self.movieImageRepository = movieImageRepository
self.showImageRepository = showImageRepository
self.episodeImageRepository = episodeImageRepository
}

public func fetchMovieImages(for movieId: Int,
posterSize: PosterImageSize?,
backdropSize: BackdropImageSize?) -> Maybe<ImagesEntity> {
return movieImageRepository.fetchMovieImages(for: movieId, posterSize: posterSize, backdropSize: backdropSize)
}

public func fetchShowImages(for showId: Int,
posterSize: PosterImageSize?,
backdropSize: BackdropImageSize?) -> Maybe<ImagesEntity> {
return showImageRepository.fetchShowImages(for: showId, posterSize: posterSize, backdropSize: backdropSize)
}

public func fetchEpisodeImages(for episode: EpisodeImageInput, size: EpisodeImageSizes?) -> Maybe<URL> {
return episodeImageRepository.fetchEpisodeImages(for: episode, size: size)
}
}
89 changes: 89 additions & 0 deletions CouchTrackerCore/Images/EpisodeImageCachedRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import Moya
import RxSwift
import TMDBSwift
import TVDBSwift

public final class EpisodeImageCachedRepository: EpisodeImageRepository {
typealias TMDBEpisodes = TMDBSwift.Episodes
typealias TVDBEpisodes = TVDBSwift.Episodes

private let configurationRepository: ConfigurationRepository
private let tmdb: TMDBProvider
private let tvdb: TVDBProvider
private var cache: [Int: URL]

public init(tmdb: TMDBProvider,
tvdb: TVDBProvider,
configurationRepository: ConfigurationRepository,
cache: [Int: URL] = [Int: URL]()) {
self.configurationRepository = configurationRepository
self.tmdb = tmdb
self.tvdb = tvdb
self.cache = cache
}

public func fetchEpisodeImages(for episode: EpisodeImageInput, size: EpisodeImageSizes? = nil) -> Maybe<URL> {
let cacheKey = EpisodeImageUtils.cacheKey(episode: episode, size: size)

guard let imageURL = cache[cacheKey] else {
return fetchImageFromAPIs(episode: episode, size: size)
.do(onNext: { [weak self] url in
self?.cache[cacheKey] = url
})
}

return Maybe.just(imageURL)
}

private func fetchImageFromAPIs(episode: EpisodeImageInput, size: EpisodeImageSizes?) -> Maybe<URL> {
guard let tvdbId = episode.tvdb else {
guard let tmdbId = episode.tmdb else {
return Maybe.empty()
}

return tmdbObservable(tmdbId, episode.season, episode.number, size?.tmdb ?? .w300)
}

let tvdbObservable = fetchEpisodeImageFromTVDB(tvdbId, size?.tvdb ?? .normal)

guard let tmdbId = episode.tmdb else {
return tvdbObservable
}

return tmdbObservable(tmdbId, episode.season, episode.number, size?.tmdb ?? .w300)
.catchError { _ in tvdbObservable }
.ifEmpty(switchTo: tvdbObservable)
}

private func tmdbObservable(_ showId: Int, _ season: Int,
_ number: Int, _ size: StillImageSize) -> Maybe<URL> {
return fetchEpisodeImageFromTMDB(showId, season, number, size)
}

private func fetchEpisodeImageFromTMDB(_ showId: Int, _ season: Int,
_ number: Int, _ size: StillImageSize) -> Maybe<URL> {
let target = TMDBEpisodes.images(showId: showId, season: season, episode: number)

let apiObservable = TMDBImageUtils.imagesFromAPI(using: tmdb.episodes, with: target)
let maybe = TMDBImageUtils.createImagesEntities(configurationRepository, apiObservable, stillSize: size).asMaybe()

return maybe.flatMap { entity -> Maybe<URL> in
guard let link = entity.stillImage()?.link, let url = URL(string: link) else {
return Maybe.empty()
}
return Maybe.just(url)
}
}

private func fetchEpisodeImageFromTVDB(_ tvdbId: Int, _ size: TVDBEpisodeImageSize) -> Maybe<URL> {
let target = TVDBEpisodes.episode(id: tvdbId)

let api = tvdb.episodes.rx.request(target).filterSuccessfulStatusAndRedirectCodes().map(EpisodeResponse.self)

return api.flatMapMaybe { episodeResponse -> Maybe<URL> in
guard let filename = episodeResponse.episode.filename else { return Maybe.empty() }
let url = EpisodeImageUtils.tvdbBaseURLFor(size: size).appendingPathComponent(filename)
return Maybe.just(url)
}
}
}
36 changes: 36 additions & 0 deletions CouchTrackerCore/Images/EpisodeImageInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,39 @@ public protocol EpisodeImageInput {
var season: Int { get }
var number: Int { get }
}

struct HashableEpisodeImageInput: EpisodeImageInput, Hashable {
private let episodeImageInput: EpisodeImageInput

var tmdb: Int? {
return episodeImageInput.tmdb
}

var tvdb: Int? {
return episodeImageInput.tvdb
}

var season: Int {
return episodeImageInput.season
}

var number: Int {
return episodeImageInput.number
}

init(_ episodeImageInput: EpisodeImageInput) {
self.episodeImageInput = episodeImageInput
}

func hash(into hasher: inout Hasher) {
hasher.combine(season)
hasher.combine(number)

tmdb.run { hasher.combine($0) }
tvdb.run { hasher.combine($0) }
}

static func == (lhs: HashableEpisodeImageInput, rhs: HashableEpisodeImageInput) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}
14 changes: 14 additions & 0 deletions CouchTrackerCore/Images/EpisodeImageUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import TVDBSwift

enum EpisodeImageUtils {
static func tvdbBaseURLFor(size: TVDBEpisodeImageSize?) -> URL {
return (size ?? .normal) == .normal ? TVDB.bannersImageURL : TVDB.smallBannersImageURL
}

static func cacheKey(episode: EpisodeImageInput, size: EpisodeImageSizes?) -> Int {
var hasher = Hasher()
hasher.combine(HashableEpisodeImageInput(episode))
size.run { hasher.combine($0) }
return hasher.finalize()
}
}
Loading

0 comments on commit fd23d6c

Please sign in to comment.