Skip to content

Commit

Permalink
CouchTrackerSyncTests green. We can sync and get back WatchedShows
Browse files Browse the repository at this point in the history
  • Loading branch information
Pietro Caselani committed Jan 7, 2020
1 parent f9feef0 commit ed6a099
Show file tree
Hide file tree
Showing 20 changed files with 8,448 additions and 263 deletions.
34 changes: 34 additions & 0 deletions CommonSources/DataStruct.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
protocol DataStruct {}

extension DataStruct {
func setValueOptional<T>(_ value: OptionalCopyValue<T>, _ defaultValue: T?) -> T? {
switch value {
case let .new(content):
return content
case .same:
return defaultValue
default:
return nil
}
}

func setValue<T>(_ value: CopyValue<T>, _ defaultValue: T) -> T {
switch value {
case let .new(content):
return content
case .same:
return defaultValue
}
}
}

enum OptionalCopyValue<T> {
case new(T)
case same
case `nil`
}

enum CopyValue<T> {
case new(T)
case same
}
31 changes: 31 additions & 0 deletions CommonSources/EnumClosures.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Given the following enum

```
enum ViewState<T>: EnumClosures {
case start(data: T)
case loading
case completed(count: Int, message: String)
}
```

will generate the following code

```
extension ViewState {
internal func onStart(_ fn: (T) -> Void) {
guard case let .start(data) = self else { return }
fn(data)
}
internal func onLoading(_ fn: () -> Void) {
guard case .loading = self else { return }
fn()
}
internal func onCompleted(_ fn: (Int, String) -> Void) {
guard case let .completed(count, message) = self else { return }
fn(count, message)
}
}
```
*/
public protocol EnumClosures {}
2 changes: 2 additions & 0 deletions CommonSources/EnumPoetry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// See `EnumClosures` and `EnumProperties`
public protocol EnumPoetry: EnumClosures, EnumProperties {}
40 changes: 40 additions & 0 deletions CommonSources/EnumProperties.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Given the following enum

```
enum ViewState<T>: EnumProperties {
case start(T)
case loading
case completed(Int, String)
}
```

will generate the following code

```
extension ViewState {
internal var isStart: Bool {
guard case .start = self else { return false }
return true
}
internal var isLoading: Bool {
guard case .loading = self else { return false }
return true
}
internal var isCompleted: Bool {
guard case .completed = self else { return false }
return true
}

internal var start: T? {
guard case let .start(data) = self else { return nil }
return (data)
}
internal var completed: (count: Int, message: String)? {
guard case let .completed(count, message) = self else { return nil }
return (count, message)
}
}
```
*/
public protocol EnumProperties {}
146 changes: 124 additions & 22 deletions CouchTrackerSync/CouchTrackerSync.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@ import TraktSwift
import RxSwift
import Moya

enum SyncError: Error {
typealias TraktShow = TraktSwift.Show
typealias DomainShow = CouchTrackerSync.Show

struct ShowDataForSyncing: DataStruct {
let progressShow: BaseShow
let show: TraktShow
let seasons: [Season]

var showIds: ShowIds {
return show.ids
}
}

public enum SyncError: Error, EnumPoetry {
case showIsNil
case missingEpisodes(showIds: ShowIds, baseSeason: BaseSeason, season: Season)
}

struct WatchedProgressOptions {
struct WatchedProgressOptions: Hashable {
let hidden: Bool
let specials: Bool
let countSpecials: Bool
Expand All @@ -18,39 +32,127 @@ struct WatchedProgressOptions {
}
}

struct SyncOptions {
struct SyncOptions: Hashable {
let watchedProgress: WatchedProgressOptions

init(watchedProgress: WatchedProgressOptions = WatchedProgressOptions()) {
self.watchedProgress = watchedProgress
}
}

func startSync(options: SyncOptions) -> Observable<BaseShow> {
func startSync(options: SyncOptions) -> Observable<WatchedShow> {
return syncMain(options)
}

private func syncMain(_ options: SyncOptions) -> Observable<BaseShow> {
return Current.syncWatchedShows([.full, .noSeasons])
private func syncMain(_ options: SyncOptions) -> Observable<WatchedShow> {
let genresObservable = Current.genres()

let showAndSeasonsObservable = Current.syncWatchedShows([.full, .noSeasons])
.flatMap { Observable.from($0) }
.flatMap { watchedProgress(options: options.watchedProgress, baseShow: $0) }
.flatMap { seasonsForShow(showData: $0) }

return Observable.zip(showAndSeasonsObservable, genresObservable).map(createWatchedShow(showData:allGenres:))
}

private func watchedProgress(options: WatchedProgressOptions, baseShow: BaseShow) -> Observable<BaseShow> {
private func watchedProgress(options: WatchedProgressOptions, baseShow: BaseShow) -> Observable<ShowDataForSyncing> {
guard let show = baseShow.show else { return Observable.error(SyncError.showIsNil) }
return Current.watchedProgress(options, show.ids).map { merge(syncBaseShow: baseShow, progressBaseShow: $0) }
}

private func merge(syncBaseShow: BaseShow, progressBaseShow: BaseShow) -> BaseShow {
BaseShow(show: syncBaseShow.show,
seasons: progressBaseShow.seasons,
lastCollectedAt: nil,
listedAt: nil,
plays: syncBaseShow.plays,
lastWatchedAt: progressBaseShow.lastWatchedAt,
aired: progressBaseShow.aired,
completed: progressBaseShow.completed,
hiddenSeasons: progressBaseShow.hiddenSeasons,
nextEpisode: progressBaseShow.nextEpisode,
lastEpisode: progressBaseShow.lastEpisode)
return Current.watchedProgress(options, show.ids)
.map { ShowDataForSyncing(progressShow: $0, show: show, seasons: []) }
}

private func seasonsForShow(showData: ShowDataForSyncing) -> Observable<ShowDataForSyncing> {
return Current.seasonsForShow(showData.showIds, [.full, .episodes])
.map { ShowDataForSyncing(progressShow: showData.progressShow, show: showData.show, seasons: $0) }
}

private func genresFromSlugs(allGenres: Set<Genre>, slugs: [String]) -> [Genre] {
return slugs.compactMap { slug in
allGenres.first { $0.slug == slug }
}
}

private func createWatchedShow(showData: ShowDataForSyncing, allGenres: Set<Genre>) throws -> WatchedShow {
let showGenres = genresFromSlugs(allGenres: allGenres, slugs: showData.show.genres ?? [])

let watchedSeasons = try createWatchedSeasons(showIds: showData.showIds,
baseSeasons: showData.progressShow.seasons ?? [],
seasons: showData.seasons)

let show = mapTraktShowToDomainShow(traktShow: showData.show, genres: showGenres, seasons: watchedSeasons)

return createWatchedShow(show: show, progressShow: showData.progressShow)
}

private func createWatchedSeasons(showIds: ShowIds, baseSeasons: [BaseSeason], seasons: [Season]) throws -> [WatchedSeason] {
return try seasons.compactMap { season -> WatchedSeason? in
guard let baseSeason = baseSeasons.first(where: { season.number == $0.number }) else { return nil }
return try createWatchedSeason(showIds: showIds, baseSeason: baseSeason, season: season)
}
}

private func createWatchedSeason(showIds: ShowIds, baseSeason: BaseSeason, season: Season) throws -> WatchedSeason {
let episodes = season.episodes?.compactMap { episode -> WatchedEpisode? in
guard let baseEpisode = baseSeason.episodes.first(where: { episode.number == $0.number }) else { return nil }
return createWatchedEpisode(showIds: showIds, baseEpisode: baseEpisode, episode: episode)
}

guard let validEpisodes = episodes else {
throw SyncError.missingEpisodes(showIds: showIds, baseSeason: baseSeason, season: season)
}

return WatchedSeason(showIds: showIds,
seasonIds: season.ids,
number: season.number,
aired: season.airedEpisodes,
completed: baseSeason.completed,
episodes: validEpisodes,
overview: season.overview,
title: season.title,
firstAired: season.firstAired,
network: season.network)
}

private func createWatchedEpisode(showIds: ShowIds, baseEpisode: BaseEpisode, episode: TraktSwift.Episode) -> WatchedEpisode {
let episode = Episode(ids: episode.ids,
showIds: showIds,
title: episode.title,
overview: episode.overview,
number: episode.number,
season: episode.season,
firstAired: episode.firstAired,
absoluteNumber: episode.absoluteNumber,
runtime: episode.runtime,
rating: episode.rating,
votes: episode.votes)

return WatchedEpisode(episode: episode, lastWatched: baseEpisode.lastWatchedAt)
}

private func mapTraktShowToDomainShow(traktShow: TraktShow, genres: [Genre], seasons: [WatchedSeason]) -> DomainShow {
return DomainShow(ids: traktShow.ids,
title: traktShow.title,
overview: traktShow.overview,
network: traktShow.network,
genres: genres,
status: traktShow.status,
firstAired: traktShow.firstAired,
seasons: seasons)
}

private func createWatchedShow(show: DomainShow, progressShow: BaseShow) -> WatchedShow {
let nextEpisode = progressShow.nextEpisode.flatMap { findEpisodeOnShow(show: show, episode: $0) }
let lastEpisode = progressShow.lastEpisode.flatMap { findEpisodeOnShow(show: show, episode: $0) }

return WatchedShow(show: show,
aired: progressShow.aired,
completed: progressShow.completed,
nextEpisode: nextEpisode,
lastEpisode: lastEpisode,
lastWatched: progressShow.lastWatchedAt)
}

private func findEpisodeOnShow(show: DomainShow, episode: TraktSwift.Episode) -> WatchedEpisode? {
let season = show.seasons.first { $0.number == episode.season }
return season?.episodes.first { $0.episode.number == episode.number }
}
31 changes: 31 additions & 0 deletions CouchTrackerSync/Models/Episode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import TraktSwift

public struct Episode: Hashable, Codable {
public let ids: EpisodeIds
public let showIds: ShowIds
public let title: String?
public let overview: String?
public let number: Int
public let season: Int
public let firstAired: Date?
public let absoluteNumber: Int?
public let runtime: Int?
public let rating: Double?
public let votes: Int?

public init(ids: EpisodeIds, showIds: ShowIds, title: String?, overview: String?,
number: Int, season: Int, firstAired: Date?, absoluteNumber: Int?,
runtime: Int?, rating: Double?, votes: Int?) {
self.ids = ids
self.showIds = showIds
self.title = title
self.overview = overview
self.number = number
self.season = season
self.firstAired = firstAired
self.absoluteNumber = absoluteNumber
self.runtime = runtime
self.rating = rating
self.votes = votes
}
}
24 changes: 24 additions & 0 deletions CouchTrackerSync/Models/Show.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import TraktSwift

public struct Show: Hashable, Codable {
public let ids: ShowIds
public let title: String?
public let overview: String?
public let network: String?
public let genres: [Genre]
public let status: Status?
public let firstAired: Date?
public let seasons: [WatchedSeason]

public init(ids: ShowIds, title: String?, overview: String?, network: String?,
genres: [Genre], status: Status?, firstAired: Date?, seasons: [WatchedSeason]) {
self.ids = ids
self.title = title
self.overview = overview
self.network = network
self.genres = genres
self.status = status
self.firstAired = firstAired
self.seasons = seasons
}
}
9 changes: 9 additions & 0 deletions CouchTrackerSync/Models/WatchedEpisode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public struct WatchedEpisode: Hashable, Codable {
public let episode: Episode
public let lastWatched: Date?

public init(episode: Episode, lastWatched: Date?) {
self.episode = episode
self.lastWatched = lastWatched
}
}
29 changes: 29 additions & 0 deletions CouchTrackerSync/Models/WatchedSeason.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import TraktSwift

public struct WatchedSeason: Hashable, Codable {
public let showIds: ShowIds
public let seasonIds: SeasonIds
public let number: Int
public let aired: Int?
public let completed: Int?
public let episodes: [WatchedEpisode]
public let overview: String?
public let title: String?
public let firstAired: Date?
public let network: String?

public init(showIds: ShowIds, seasonIds: SeasonIds, number: Int,
aired: Int?, completed: Int?, episodes: [WatchedEpisode],
overview: String?, title: String?, firstAired: Date?, network: String?) {
self.showIds = showIds
self.seasonIds = seasonIds
self.number = number
self.aired = aired
self.completed = completed
self.episodes = episodes
self.overview = overview
self.title = title
self.firstAired = firstAired
self.network = network
}
}
Loading

0 comments on commit ed6a099

Please sign in to comment.