Updated for 2017

This commit is contained in:
Guilherme Rambo
2017-05-30 17:20:27 -03:00
parent 24a7fcf432
commit 50d0c5d81e
23 changed files with 302 additions and 293 deletions

View File

@@ -45,10 +45,10 @@ public final class AppleAPIClient {
return try self?.failableAdaptCollection(newsItemsJson, using: NewsItemsJSONAdapter())
}
service.configureTransformer(environment.sessionsPath) { [weak self] (entity: Entity<JSON>) throws -> ScheduleResponse? in
service.configureTransformer(environment.sessionsPath) { [weak self] (entity: Entity<JSON>) throws -> ContentsResponse? in
let json = entity.content as JSON
return try self?.failableAdapt(json, using: ScheduleResponseAdapter())
return try self?.failableAdapt(json, using: ContentsResponseAdapter())
}
service.configureTransformer(environment.videosPath) { [weak self] (entity: Entity<JSON>) throws -> SessionsResponse? in
@@ -122,7 +122,7 @@ public final class AppleAPIClient {
// MARK: - Standard API requests
private var liveVideoAssetsResource: Resource!
private var scheduleResource: Resource!
private var contentsResource: Resource!
private var sessionsResource: Resource!
private var newsItemsResource: Resource!
@@ -142,26 +142,15 @@ public final class AppleAPIClient {
currentLiveVideosRequest = liveVideoAssetsResource.loadIfNeeded()
}
public func fetchSchedule(completion: @escaping (Result<ScheduleResponse, APIError>) -> Void) {
if scheduleResource == nil {
scheduleResource = schedule.addObserver(owner: self) { [weak self] resource, event in
public func fetchContent(completion: @escaping (Result<ContentsResponse, APIError>) -> Void) {
if contentsResource == nil {
contentsResource = schedule.addObserver(owner: self) { [weak self] resource, event in
self?.process(resource, event: event, with: completion)
}
}
currentScheduleRequest?.cancel()
currentScheduleRequest = scheduleResource.loadIfNeeded()
}
public func fetchSessions(completion: @escaping (Result<SessionsResponse, APIError>) -> Void) {
if sessionsResource == nil {
sessionsResource = sessions.addObserver(owner: self) { [weak self] resource, event in
self?.process(resource, event: event, with: completion)
}
}
currentSessionsRequest?.cancel()
currentSessionsRequest = sessionsResource.loadIfNeeded()
currentScheduleRequest = contentsResource.loadIfNeeded()
}
public func fetchNewsItems(completion: @escaping (Result<[NewsItem], APIError>) -> Void) {

View File

@@ -77,9 +77,9 @@ extension Environment {
newsPath: "/news.json",
liveVideosPath: "/videos_live.json")
public static let production = Environment(baseURL: "https://api.wwdc.io",
public static let production = Environment(baseURL: "https://api2017.wwdc.io",
videosPath: "/videos.json",
sessionsPath: "/sessions.json",
sessionsPath: "/contents.json",
newsPath: "/news.json",
liveVideosPath: "/videos_live.json")

View File

@@ -30,6 +30,8 @@ public class Event: Object {
/// Sessions held at this event
public let sessions = List<Session>()
public dynamic var imagesPath = ""
/// Session instances for schedule
public var sessionInstances = List<SessionInstance>()
@@ -47,7 +49,7 @@ public class Event: Object {
]
}
public static func make(identifier: String, name: String, startDate: Date, endDate: Date, isCurrent: Bool) -> Event {
public static func make(identifier: String, name: String, startDate: Date, endDate: Date, isCurrent: Bool, imagesPath: String) -> Event {
let event = Event()
event.identifier = identifier
@@ -55,6 +57,7 @@ public class Event: Object {
event.startDate = startDate
event.endDate = endDate
event.isCurrent = isCurrent
event.imagesPath = imagesPath
return event
}

View File

@@ -10,7 +10,11 @@ import Foundation
import SwiftyJSON
private enum EventKeys: String, JSONSubscriptType {
case name, current, identifier, start, end
case name, current
case start = "startTime"
case end = "endTime"
case identifier = "id"
case imagesPath
var jsonKey: JSONKey {
return JSONKey.key(rawValue)
@@ -34,6 +38,10 @@ final class EventsJSONAdapter: Adapter {
return .error(.missingKey(EventKeys.current))
}
guard let imagesPath = input[EventKeys.imagesPath].string else {
return .error(.missingKey(EventKeys.imagesPath))
}
guard let rawStart = input[EventKeys.start].string else {
return .error(.missingKey(EventKeys.start))
}
@@ -42,11 +50,11 @@ final class EventsJSONAdapter: Adapter {
return .error(.missingKey(EventKeys.end))
}
guard case .success(let startDate) = DateAdapter().adapt(rawStart) else {
guard case .success(let startDate) = DateTimeAdapter().adapt(rawStart) else {
return .error(.invalidData)
}
guard case .success(let endDate) = DateAdapter().adapt(rawEnd) else {
guard case .success(let endDate) = DateTimeAdapter().adapt(rawEnd) else {
return .error(.invalidData)
}
@@ -54,7 +62,8 @@ final class EventsJSONAdapter: Adapter {
name: name,
startDate: startDate,
endDate: endDate,
isCurrent: current)
isCurrent: current,
imagesPath: imagesPath)
return .success(event)
}

View File

@@ -12,6 +12,8 @@ import RealmSwift
/// Represents a room or venue where sessions are held
public class Room: Object {
public dynamic var identifier = ""
/// Name of the map file (maps are not present in the macOS app because they are embedded in the iOS app's binary, not given by the API)
public dynamic var mapName = ""
@@ -34,9 +36,10 @@ public class Room: Object {
]
}
public static func make(name: String, mapName: String, floor: String) -> Room {
public static func make(identifier: String, name: String, mapName: String, floor: String) -> Room {
let room = Room()
room.identifier = identifier
room.name = name
room.mapName = mapName
room.floor = floor

View File

@@ -11,6 +11,7 @@ import SwiftyJSON
private enum RoomKeys: String, JSONSubscriptType {
case name, mapName, floor
case identifier = "id"
var jsonKey: JSONKey {
return JSONKey.key(rawValue)
@@ -22,19 +23,15 @@ final class RoomsJSONAdapter: Adapter {
typealias OutputType = Room
func adapt(_ input: JSON) -> Result<Room, AdapterError> {
guard let identifier = input[RoomKeys.identifier].int else {
return .error(.missingKey(RoomKeys.identifier))
}
guard let name = input[RoomKeys.name].string else {
return .error(.missingKey(RoomKeys.name))
}
guard let mapName = input[RoomKeys.mapName].string else {
return .error(.missingKey(RoomKeys.mapName))
}
guard let floor = input[RoomKeys.floor].string else {
return .error(.missingKey(RoomKeys.floor))
}
let room = Room.make(name: name, mapName: mapName, floor: floor)
let room = Room.make(identifier: "\(identifier)", name: name, mapName: "", floor: "")
return .success(room)
}

View File

@@ -8,10 +8,12 @@
import Foundation
public struct ScheduleResponse {
public struct ContentsResponse {
public let events: [Event]
public let rooms: [Room]
public let tracks: [Track]
public let instances: [SessionInstance]
public let sessions: [Session]
}

View File

@@ -9,52 +9,64 @@
import Foundation
import SwiftyJSON
private enum ScheduleKeys: String, JSONSubscriptType {
case response, rooms, tracks, sessions
private enum ContentKeys: String, JSONSubscriptType {
case response, rooms, tracks, sessions, events, contents
var jsonKey: JSONKey {
return JSONKey.key(rawValue)
}
}
final class ScheduleResponseAdapter: Adapter {
final class ContentsResponseAdapter: Adapter {
typealias InputType = JSON
typealias OutputType = ScheduleResponse
typealias OutputType = ContentsResponse
func adapt(_ input: JSON) -> Result<ScheduleResponse, AdapterError> {
guard let roomsJson = input[ScheduleKeys.response][ScheduleKeys.rooms].array else {
return .error(.missingKey(ScheduleKeys.rooms))
func adapt(_ input: JSON) -> Result<ContentsResponse, AdapterError> {
guard let eventsJson = input[ContentKeys.events].array else {
return .error(.missingKey(ContentKeys.events))
}
guard let tracksJson = input[ScheduleKeys.response][ScheduleKeys.tracks].array else {
return .error(.missingKey(ScheduleKeys.rooms))
guard case .success(let events) = EventsJSONAdapter().adapt(eventsJson) else {
return .error(.invalidData)
}
guard let instancesJson = input[ScheduleKeys.response][ScheduleKeys.sessions].array else {
return .error(.missingKey(ScheduleKeys.rooms))
guard let roomsJson = input[ContentKeys.rooms].array else {
return .error(.missingKey(ContentKeys.rooms))
}
guard case .success(let rooms) = RoomsJSONAdapter().adapt(roomsJson) else {
return .error(.invalidData)
}
guard let tracksJson = input[ContentKeys.tracks].array else {
return .error(.missingKey(ContentKeys.rooms))
}
guard case .success(var tracks) = TracksJSONAdapter().adapt(tracksJson) else {
guard case .success(let tracks) = TracksJSONAdapter().adapt(tracksJson) else {
return .error(.missingKey(ContentKeys.tracks))
}
guard let sessionsJson = input[ContentKeys.contents].array else {
return .error(.missingKey(ContentKeys.contents))
}
guard case .success(var sessions) = SessionsJSONAdapter().adapt(sessionsJson) else {
return .error(.invalidData)
}
// add order to tracks using the order from the server
for i in 0..<tracks.count {
tracks[i].order = i
}
guard case .success(let instances) = SessionInstancesJSONAdapter().adapt(instancesJson) else {
guard case .success(let instances) = SessionInstancesJSONAdapter().adapt(sessionsJson) else {
return .error(.invalidData)
}
let response = ScheduleResponse(rooms: rooms,
tracks: tracks,
instances: instances)
// remove duplicated sessions
instances.forEach { instance in
guard let index = sessions.index(where: { $0.identifier == instance.session?.identifier }) else { return }
sessions.remove(at: index)
}
let response = ContentsResponse(events: events, rooms: rooms, tracks: tracks, instances: instances, sessions: sessions)
return .success(response)
}

View File

@@ -24,6 +24,8 @@ public class Session: Object {
/// Title
public dynamic var title = ""
public dynamic var staticContentId = ""
/// Description
public dynamic var summary = ""
@@ -31,7 +33,7 @@ public class Session: Object {
public dynamic var eventIdentifier = ""
/// Track name
public dynamic var trackName = ""
public dynamic var trackIdentifier = ""
/// The session's focuses
public let focuses = List<Focus>()
@@ -115,7 +117,8 @@ public class Session: Object {
self.number = other.number
self.summary = other.summary
self.eventIdentifier = other.eventIdentifier
self.trackName = other.trackName
self.trackIdentifier = other.trackIdentifier
self.staticContentId = other.staticContentId
let otherFocuses = other.focuses.map { newFocus -> (Focus) in
if newFocus.realm == nil,

View File

@@ -9,100 +9,13 @@
import Foundation
import SwiftyJSON
private enum AssetKeys: String, JSONSubscriptType {
case id, year, title, download_hd, download_sd, slides, webpageURL, url, images, shelf
var jsonKey: JSONKey {
return JSONKey.key(rawValue)
}
}
final class SessionAssetsJSONAdapter: Adapter {
typealias InputType = JSON
typealias OutputType = [SessionAsset]
func adapt(_ input: JSON) -> Result<[SessionAsset], AdapterError> {
guard let title = input[AssetKeys.title].string else {
return .error(.missingKey(AssetKeys.title))
}
guard let sessionId = input[AssetKeys.id].string else {
return .error(.missingKey(AssetKeys.id))
}
guard let year = input[AssetKeys.year].int else {
return .error(.missingKey(AssetKeys.year))
}
var output = [SessionAsset]()
if let url = input[AssetKeys.url].string {
let streaming = SessionAsset()
streaming.rawAssetType = SessionAssetType.streamingVideo.rawValue
streaming.remoteURL = url
streaming.year = year
streaming.sessionId = sessionId
output.append(streaming)
}
if let hd = input[AssetKeys.download_hd].string {
let hdVideo = SessionAsset()
hdVideo.rawAssetType = SessionAssetType.hdVideo.rawValue
hdVideo.remoteURL = hd
hdVideo.year = year
hdVideo.sessionId = sessionId
let filename = URL(string: hd)?.lastPathComponent ?? "\(title).mp4"
hdVideo.relativeLocalURL = "\(year)/\(filename)"
output.append(hdVideo)
}
if let sd = input[AssetKeys.download_sd].string {
let sdVideo = SessionAsset()
sdVideo.rawAssetType = SessionAssetType.sdVideo.rawValue
sdVideo.remoteURL = sd
sdVideo.year = year
sdVideo.sessionId = sessionId
let filename = URL(string: sd)?.lastPathComponent ?? "\(title).mp4"
sdVideo.relativeLocalURL = "\(year)/\(filename)"
output.append(sdVideo)
}
if let slides = input[AssetKeys.slides].string {
let slidesAsset = SessionAsset()
slidesAsset.rawAssetType = SessionAssetType.slides.rawValue
slidesAsset.remoteURL = slides
slidesAsset.year = year
slidesAsset.sessionId = sessionId
output.append(slidesAsset)
}
if let webpage = input[AssetKeys.webpageURL].string {
let webpageAsset = SessionAsset()
webpageAsset.rawAssetType = SessionAssetType.webpage.rawValue
webpageAsset.remoteURL = webpage
webpageAsset.year = year
webpageAsset.sessionId = sessionId
output.append(webpageAsset)
}
if let image = input[AssetKeys.images][AssetKeys.shelf].string {
let imageAsset = SessionAsset()
imageAsset.rawAssetType = SessionAssetType.image.rawValue
imageAsset.remoteURL = image
imageAsset.year = year
imageAsset.sessionId = sessionId
output.append(imageAsset)
}
return .success(output)
return .error(.invalidData)
}
}

View File

@@ -75,9 +75,14 @@ public class SessionInstance: Object {
/// Room name
public dynamic var roomName = ""
/// Room unique identifier
public dynamic var roomIdentifier = ""
// Track name
public dynamic var trackName = ""
public dynamic var trackIdentifier = ""
/// The track associated with the instance
public let track = LinkingObjects(fromType: Track.self, property: "instances")
@@ -123,8 +128,10 @@ public class SessionInstance: Object {
self.sessionType = other.sessionType
self.startTime = other.startTime
self.endTime = other.endTime
self.roomName = other.roomName
self.roomIdentifier = other.roomIdentifier
self.trackName = other.trackName
self.trackIdentifier = other.trackIdentifier
self.eventIdentifier = other.eventIdentifier
if let otherSession = other.session {
self.session = realm.object(ofType: Session.self, forPrimaryKey: otherSession.identifier)

View File

@@ -9,9 +9,11 @@
import Foundation
import SwiftyJSON
private enum SessionInstanceKeys: String, JSONSubscriptType {
case id, track, room, keywords, startGMT, endGMT, type
enum SessionInstanceKeys: String, JSONSubscriptType {
case id, keywords, startTime, endTime, type
case favId = "fav_id"
case room = "roomId"
case track = "trackId"
var jsonKey: JSONKey {
return JSONKey.key(rawValue)
@@ -28,36 +30,33 @@ final class SessionInstancesJSONAdapter: Adapter {
return .error(.invalidData)
}
guard let id = input[SessionInstanceKeys.id].string else {
return .error(.missingKey(SessionInstanceKeys.id))
// not an instance
guard session.year == Calendar.current.component(.year, from: Date()) else {
return .error(.invalidData)
}
guard let trackName = input[SessionInstanceKeys.track].string else {
return .error(.missingKey(SessionInstanceKeys.track))
guard let startGMT = input[SessionInstanceKeys.startTime].string else {
return .error(.missingKey(SessionInstanceKeys.startTime))
}
guard let roomName = input[SessionInstanceKeys.room].string else {
return .error(.missingKey(SessionInstanceKeys.room))
}
guard let startGMT = input[SessionInstanceKeys.startGMT].string else {
return .error(.missingKey(SessionInstanceKeys.startGMT))
}
guard let endGMT = input[SessionInstanceKeys.endGMT].string else {
return .error(.missingKey(SessionInstanceKeys.endGMT))
guard let endGMT = input[SessionInstanceKeys.endTime].string else {
return .error(.missingKey(SessionInstanceKeys.startTime))
}
guard let rawType = input[SessionInstanceKeys.type].string else {
return .error(.missingKey(SessionInstanceKeys.type))
}
guard let keywordsJson = input[SessionInstanceKeys.keywords].array else {
return .error(.missingKey(SessionInstanceKeys.keywords))
guard let id = input[SessionInstanceKeys.id].string else {
return .error(.missingKey(SessionInstanceKeys.id))
}
guard case .success(let keywords) = KeywordsJSONAdapter().adapt(keywordsJson) else {
return .error(.invalidData)
guard let roomIdentifier = input[SessionInstanceKeys.room].int else {
return .error(.missingKey(SessionInstanceKeys.room))
}
guard let trackIdentifier = input[SessionInstanceKeys.track].int else {
return .error(.missingKey(SessionInstanceKeys.track))
}
guard case .success(let startDate) = DateTimeAdapter().adapt(startGMT) else {
@@ -70,17 +69,22 @@ final class SessionInstancesJSONAdapter: Adapter {
let instance = SessionInstance()
if let keywordsJson = input[SessionInstanceKeys.keywords].array {
if case .success(let keywords) = KeywordsJSONAdapter().adapt(keywordsJson) {
instance.keywords.append(objectsIn: keywords)
}
}
instance.identifier = session.identifier
instance.eventIdentifier = Event.identifier(from: startDate)
instance.number = id
instance.session = session
instance.trackName = trackName
instance.roomName = roomName
instance.trackIdentifier = "\(trackIdentifier)"
instance.roomIdentifier = "\(roomIdentifier)"
instance.rawSessionType = rawType
instance.sessionType = SessionInstanceType(rawSessionType: rawType)?.rawValue ?? 0
instance.startTime = startDate
instance.endTime = endDate
instance.keywords.append(objectsIn: keywords)
return .success(instance)
}

View File

@@ -9,8 +9,18 @@
import Foundation
import SwiftyJSON
enum AssetKeys: String, JSONSubscriptType {
case id, year, title, downloadHD, downloadSD, slides, hls, images, shelf
var jsonKey: JSONKey {
return JSONKey.key(rawValue)
}
}
private enum SessionKeys: String, JSONSubscriptType {
case id, year, title, track, focus, description, startGMT
case id, year, title, platforms, description, startTime, eventContentId, eventId, media, webPermalink, staticContentId
case track = "trackId"
var jsonKey: JSONKey {
return JSONKey.key(rawValue)
@@ -23,24 +33,15 @@ final class SessionsJSONAdapter: Adapter {
typealias OutputType = Session
func adapt(_ input: JSON) -> Result<Session, AdapterError> {
guard let id = input[SessionKeys.id].string else {
guard let id = input[SessionKeys.id].string?.replacingOccurrences(of: "wwdc", with: "") else {
return .error(.missingKey(SessionKeys.id))
}
var eventYear = ""
if let year = input[SessionKeys.year].int {
eventYear = "\(year)"
} else if let startGMT = input[SessionKeys.startGMT].string {
guard let year = startGMT.components(separatedBy: "-").first else {
return .error(.missingKey(SessionKeys.year))
}
eventYear = year
guard let eventIdentifier = input[SessionKeys.eventId].string else {
return .error(.missingKey(SessionKeys.eventId))
}
let identifier = "\(eventYear)-\(id)"
let eventIdentifier = "wwdc\(eventYear)"
let eventYear = eventIdentifier.replacingOccurrences(of: "wwdc", with: "")
guard let title = input[SessionKeys.title].string else {
return .error(.missingKey(SessionKeys.title))
@@ -50,27 +51,87 @@ final class SessionsJSONAdapter: Adapter {
return .error(.missingKey(SessionKeys.description))
}
guard let trackName = input[SessionKeys.track].string else {
guard let trackIdentifier = input[SessionKeys.track].int else {
return .error(.missingKey(SessionKeys.track))
}
guard let focusesJson = input[SessionKeys.focus].array else {
return .error(.missingKey(SessionKeys.focus))
}
guard case .success(let focuses) = FocusesJSONAdapter().adapt(focusesJson) else {
return .error(.invalidData)
guard let eventContentId = input[SessionKeys.eventContentId].int else {
return .error(.missingKey(SessionKeys.eventContentId))
}
let session = Session()
session.identifier = identifier
if let focusesJson = input[SessionKeys.platforms].array {
if case .success(let focuses) = FocusesJSONAdapter().adapt(focusesJson) {
session.focuses.append(objectsIn: focuses)
}
}
if let url = input[SessionKeys.media][AssetKeys.hls].string {
let streaming = SessionAsset()
streaming.rawAssetType = SessionAssetType.streamingVideo.rawValue
streaming.remoteURL = url
streaming.year = Int(eventYear) ?? -1
streaming.sessionId = id
session.assets.append(streaming)
}
if let hd = input[SessionKeys.media][AssetKeys.downloadHD].string {
let hdVideo = SessionAsset()
hdVideo.rawAssetType = SessionAssetType.hdVideo.rawValue
hdVideo.remoteURL = hd
hdVideo.year = Int(eventYear) ?? -1
hdVideo.sessionId = id
let filename = URL(string: hd)?.lastPathComponent ?? "\(title).mp4"
hdVideo.relativeLocalURL = "\(eventYear)/\(filename)"
session.assets.append(hdVideo)
}
if let sd = input[SessionKeys.media][AssetKeys.downloadSD].string {
let sdVideo = SessionAsset()
sdVideo.rawAssetType = SessionAssetType.sdVideo.rawValue
sdVideo.remoteURL = sd
sdVideo.year = Int(eventYear) ?? -1
sdVideo.sessionId = id
let filename = URL(string: sd)?.lastPathComponent ?? "\(title).mp4"
sdVideo.relativeLocalURL = "\(eventYear)/\(filename)"
session.assets.append(sdVideo)
}
if let slides = input[SessionKeys.media][AssetKeys.slides].string {
let slidesAsset = SessionAsset()
slidesAsset.rawAssetType = SessionAssetType.slides.rawValue
slidesAsset.remoteURL = slides
slidesAsset.year = Int(eventYear) ?? -1
slidesAsset.sessionId = id
session.assets.append(slidesAsset)
}
if let permalink = input[SessionKeys.webPermalink].string {
let webPageAsset = SessionAsset()
webPageAsset.rawAssetType = SessionAssetType.webpage.rawValue
webPageAsset.remoteURL = permalink
webPageAsset.year = Int(eventYear) ?? -1
webPageAsset.sessionId = id
session.assets.append(webPageAsset)
}
session.staticContentId = "\(input[SessionKeys.staticContentId].intValue)"
session.identifier = id
session.year = Int(eventYear) ?? -1
session.number = id
session.number = "\(eventContentId)"
session.title = title
session.summary = summary
session.trackName = trackName
session.focuses.append(objectsIn: focuses)
session.trackIdentifier = "\(trackIdentifier)"
session.eventIdentifier = eventIdentifier
return .success(session)

View File

@@ -23,44 +23,45 @@ final class SessionsResponseAdapter: Adapter {
typealias OutputType = SessionsResponse
func adapt(_ input: JSON) -> Result<SessionsResponse, AdapterError> {
guard let eventsJson = input[SessionResponseKeys.events].array else {
return .error(.missingKey(SessionResponseKeys.events))
}
guard let sessionsJson = input[SessionResponseKeys.sessions].array else {
return .error(.missingKey(SessionResponseKeys.sessions))
}
guard case .success(let events) = EventsJSONAdapter().adapt(eventsJson) else {
return .error(.invalidData)
}
guard case .success(let sessions) = SessionsJSONAdapter().adapt(sessionsJson) else {
return .error(.invalidData)
}
guard case .success(let assets) = SessionAssetsJSONAdapter().adapt(sessionsJson) else {
return .error(.invalidData)
}
var trackNames = Set<String>()
sessions.forEach { session in
guard !trackNames.contains(session.trackName) else { return }
trackNames.insert(session.trackName)
}
let tracks: [Track] = trackNames.map { name in
let track = Track()
track.name = name
return track
}
let response = SessionsResponse(events: events, sessions: sessions, assets: assets.flatMap({$0}), tracks: tracks)
return .success(response)
return .error(.invalidData)
// guard let eventsJson = input[SessionResponseKeys.events].array else {
// return .error(.missingKey(SessionResponseKeys.events))
// }
//
// guard let sessionsJson = input[SessionResponseKeys.sessions].array else {
// return .error(.missingKey(SessionResponseKeys.sessions))
// }
//
// guard case .success(let events) = EventsJSONAdapter().adapt(eventsJson) else {
// return .error(.invalidData)
// }
//
// guard case .success(let sessions) = SessionsJSONAdapter().adapt(sessionsJson) else {
// return .error(.invalidData)
// }
//
// guard case .success(let assets) = SessionAssetsJSONAdapter().adapt(sessionsJson) else {
// return .error(.invalidData)
// }
//
// var trackNames = Set<String>()
//
// sessions.forEach { session in
// guard !trackNames.contains(session.trackName) else { return }
// trackNames.insert(session.trackName)
// }
//
// let tracks: [Track] = trackNames.map { name in
// let track = Track()
//
// track.name = name
//
// return track
// }
//
// let response = SessionsResponse(events: events, sessions: sessions, assets: assets.flatMap({$0}), tracks: tracks)
//
// return .success(response)
}
}

View File

@@ -103,15 +103,12 @@ public final class Storage {
}
}
func store(sessionsResult: Result<SessionsResponse, APIError>, scheduleResult: Result<ScheduleResponse, APIError>, completion: @escaping () -> Void) {
if case let .error(sessionsError) = sessionsResult {
print("Error downloading sessions: \(sessionsError)")
}
if case let .error(scheduleError) = scheduleResult {
print("Error downloading schedule: \(scheduleError)")
func store(contentResult: Result<ContentsResponse, APIError>, completion: @escaping () -> Void) {
if case let .error(error) = contentResult {
print("Error downloading sessions: \(error)")
}
guard case let .success(sessionsResponse) = sessionsResult, case let .success(scheduleResponse) = scheduleResult else {
guard case let .success(sessionsResponse) = contentResult else {
return
}
@@ -139,32 +136,8 @@ public final class Storage {
}
}
// Associate assets with sessions
consolidatedSessions.forEach { session in
autoreleasepool {
let components = session.identifier.components(separatedBy: "-")
guard components.count == 2 else { return }
guard let year = Int(components[0]) else { return }
session.assets.removeAll()
// Merge assets, preserving user-defined data
let assets = sessionsResponse.assets.filter({ $0.year == year && $0.sessionId == components[1] }).map { newAsset -> (SessionAsset) in
if let existingAsset = self.realm.object(ofType: SessionAsset.self, forPrimaryKey: newAsset.identifier) {
existingAsset.merge(with: newAsset, in: self.realm)
return existingAsset
} else {
return newAsset
}
}
session.assets.append(objectsIn: assets)
}
}
// Merge existing instance data, preserving user-defined data
scheduleResponse.instances.forEach { newInstance in
sessionsResponse.instances.forEach { newInstance in
return autoreleasepool {
if let existingInstance = self.realm.object(ofType: SessionInstance.self, forPrimaryKey: newInstance.identifier) {
existingInstance.merge(with: newInstance, in: self.realm)
@@ -177,8 +150,8 @@ public final class Storage {
}
// Save everything
self.realm.add(scheduleResponse.rooms, update: true)
self.realm.add(scheduleResponse.tracks, update: true)
self.realm.add(sessionsResponse.rooms, update: true)
self.realm.add(sessionsResponse.tracks, update: true)
self.realm.add(sessionsResponse.events, update: true)
do {
@@ -195,8 +168,9 @@ public final class Storage {
// add instances to rooms
targetRealm.objects(Room.self).forEach { room in
let instances = targetRealm.objects(SessionInstance.self).filter("roomName == %@", room.name)
let instances = targetRealm.objects(SessionInstance.self).filter("roomIdentifier == %@", room.identifier)
instances.forEach({ $0.roomName = room.name })
room.instances.append(objectsIn: instances)
}
@@ -211,11 +185,13 @@ public final class Storage {
// add instances and sessions to tracks
targetRealm.objects(Track.self).forEach { track in
let instances = targetRealm.objects(SessionInstance.self).filter("trackName == %@", track.name)
let sessions = targetRealm.objects(Session.self).filter("trackName == %@", track.name)
let instances = targetRealm.objects(SessionInstance.self).filter("trackIdentifier == %@", track.identifier)
let sessions = targetRealm.objects(Session.self).filter("trackIdentifier == %@", track.identifier)
track.instances.append(objectsIn: instances)
track.sessions.append(objectsIn: sessions)
instances.forEach({ $0.trackName = track.name })
}
// add live video assets to sessions

View File

@@ -41,15 +41,11 @@ public final class SyncEngine {
}
}
public func syncSessionsAndSchedule() {
client.fetchSessions { [weak self] sessionsResult in
public func syncContent() {
client.fetchContent { [unowned self] scheduleResult in
DispatchQueue.main.async {
self?.client.fetchSchedule { scheduleResult in
DispatchQueue.main.async {
self?.storage.store(sessionsResult: sessionsResult, scheduleResult: scheduleResult) {
NotificationCenter.default.post(name: .SyncEngineDidSyncSessionsAndSchedule, object: self)
}
}
self.storage.store(contentResult: scheduleResult) {
NotificationCenter.default.post(name: .SyncEngineDidSyncSessionsAndSchedule, object: self)
}
}
}

View File

@@ -12,6 +12,9 @@ import RealmSwift
/// Tracks represent a specific are of interest (ex: "System Frameworks", "Graphics and Games")
public class Track: Object {
/// Unique identifier
public dynamic var identifier = ""
/// The name of the track
public dynamic var name = ""
@@ -47,13 +50,15 @@ public class Track: Object {
]
}
public static func make(name: String,
darkColor: String,
lightBackgroundColor: String,
lightColor: String,
titleColor: String) -> Track {
public static func make(identifier: String,
name: String,
darkColor: String,
lightBackgroundColor: String,
lightColor: String,
titleColor: String) -> Track {
let track = Track()
track.identifier = identifier
track.name = name
track.darkColor = darkColor
track.lightBackgroundColor = lightBackgroundColor

View File

@@ -10,7 +10,8 @@ import Foundation
import SwiftyJSON
private enum TrackKeys: String, JSONSubscriptType {
case name, color, darkColor, titleColor, lightBGColor
case name, color, darkColor, titleColor, lightBGColor, ordinal
case identifier = "id"
var jsonKey: JSONKey {
return JSONKey.key(rawValue)
@@ -23,6 +24,10 @@ final class TracksJSONAdapter: Adapter {
typealias OutputType = Track
func adapt(_ input: JSON) -> Result<Track, AdapterError> {
guard let identifier = input[TrackKeys.identifier].int else {
return .error(.missingKey(TrackKeys.identifier))
}
guard let name = input[TrackKeys.name].string else {
return .error(.missingKey(TrackKeys.name))
}
@@ -43,12 +48,15 @@ final class TracksJSONAdapter: Adapter {
return .error(.missingKey(TrackKeys.lightBGColor))
}
let track = Track.make(name: name,
let track = Track.make(identifier: "\(identifier)",
name: name,
darkColor: darkColor,
lightBackgroundColor: lightBGColor,
lightColor: color,
titleColor: titleColor)
track.order = input[TrackKeys.ordinal].intValue
return .success(track)
}