Skip to content

Commit

Permalink
Merge pull request #96 from contentful/improvement/batch-update-sync
Browse files Browse the repository at this point in the history
Send entire sync page to persistence delegate
  • Loading branch information
loudmouth authored Jul 18, 2017
2 parents 00d6e56 + 9b98668 commit 80fd768
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CONTENTFUL_SDK_VERSION=0.7.4
CONTENTFUL_SDK_VERSION=0.7.5
2 changes: 1 addition & 1 deletion .envrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export CONTENFUL_SDK_VERSION=0.7.4
export CONTENFUL_SDK_VERSION=0.7.5
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ This project adheres to [Semantic Versioning](http://semver.org/) starting from
## Table of contents

#### 0.x Releases
- `0.7.x` Releases - [0.7.0](#070) | [0.7.1](#071) | [0.7.2](#072)) | [0.7.3](#073) | [0.7.4](#074)
- `0.7.x` Releases - [0.7.0](#070) | [0.7.1](#071) | [0.7.2](#072)) | [0.7.3](#073) | [0.7.4](#074) | [0.7.5](#075)
- `0.6.x` Releases - [0.6.0](#060) | [0.6.1](#061)
- `0.5.x` Releases - [0.5.0](#050)
- `0.4.x` Releases - [0.4.0](#040) | [0.4.1](#041)
- `0.3.x` Releases - [0.3.0](#030) | [0.3.1](#031)


---

## [`0.7.5`](https://github.com/contentful/contentful.swift/releases/tag/0.7.5)
Released on 2017-07-18

#### Changed
- Simplified and optimized sending messages to types conforming to `PersistenceIntegration`.

---

## [`0.7.4`](https://github.com/contentful/contentful.swift/releases/tag/0.7.4)
Expand Down
2 changes: 1 addition & 1 deletion Config.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CONTENTFUL_SDK_VERSION=0.7.4
CONTENTFUL_SDK_VERSION=0.7.5
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pod 'Contentful'
You can specify a specific version of Contentful depending on your needs. To learn more about operators for dependency versioning within a Podfile, see the [CocoaPods doc on the Podfile][7].

```ruby
pod 'Contentful', '~> 0.7.4'
pod 'Contentful', '~> 0.7.5'
```

Note that for Swift 2.3 support (contentful.swift `v0.2.3`) you will need to add a post-install script to your Podfile if installing with Cocoapods:
Expand All @@ -100,7 +100,7 @@ end
You can also use [Carthage][8] for integration by adding the following to your `Cartfile`:

```
github "contentful/contentful.swift" ~> 0.7.4
github "contentful/contentful.swift" ~> 0.7.5
```

## License
Expand Down
21 changes: 15 additions & 6 deletions Sources/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,8 @@ extension Client {

- Returns: The data task being used, enables cancellation of requests.
*/
@discardableResult public func fetchContentType(id: String, completion: @escaping ResultsHandler<ContentType>) -> URLSessionDataTask? {
@discardableResult public func fetchContentType(id: String,
completion: @escaping ResultsHandler<ContentType>) -> URLSessionDataTask? {
return fetch(url: URL(forComponent: "content_types/\(id)"), then: completion)
}

Expand Down Expand Up @@ -542,7 +543,8 @@ extension Client {

- Returns: The data task being used, enables cancellation of requests.
*/
@discardableResult public func fetchEntry(id: String, completion: @escaping ResultsHandler<Entry>) -> URLSessionDataTask? {
@discardableResult public func fetchEntry(id: String,
completion: @escaping ResultsHandler<Entry>) -> URLSessionDataTask? {
let fetchEntriesCompletion: (Result<ArrayResponse<Entry>>) -> Void = { result in
switch result {
case .success(let entries) where entries.items.first != nil:
Expand Down Expand Up @@ -651,7 +653,8 @@ extension Client {

- Returns: An `Observable` which will be fired when the `SyncSpace` is fully synchronized with Contentful.
*/
@discardableResult func nextSync(for syncSpace: SyncSpace, matching: [String: Any] = [:]) -> Observable<Result<SyncSpace>> {
@discardableResult func nextSync(for syncSpace: SyncSpace,
matching: [String: Any] = [:]) -> Observable<Result<SyncSpace>> {

let observable = Observable<Result<SyncSpace>>()
self.nextSync(for: syncSpace) { result in
Expand Down Expand Up @@ -698,7 +701,8 @@ extension Client {
return task
}

fileprivate func sync(matching: [String: Any] = [:], completion: @escaping ResultsHandler<SyncSpace>) -> URLSessionDataTask? {
fileprivate func sync(matching: [String: Any] = [:],
completion: @escaping ResultsHandler<SyncSpace>) -> URLSessionDataTask? {

return fetch(url: URL(forComponent: "sync", parameters: matching)) { (result: Result<SyncSpace>) in

Expand All @@ -710,11 +714,16 @@ extension Client {
}
}

fileprivate func finishSync(for syncSpace: SyncSpace, newestSyncResults: Result<SyncSpace>, completion: ResultsHandler<SyncSpace>) {
fileprivate func finishSync(for syncSpace: SyncSpace,
newestSyncResults: Result<SyncSpace>,
completion: ResultsHandler<SyncSpace>) {

switch newestSyncResults {
case .success(let newSyncSpace):
syncSpace.updateWithDiffs(from: newSyncSpace, persistenceIntegration: self.persistenceIntegration)
syncSpace.updateWithDiffs(from: newSyncSpace)
persistenceIntegration?.update(with: newSyncSpace)

// Send fully merged syncSpace to completion handler.
completion(Result.success(syncSpace))
case .error(let error):
completion(Result.error(error))
Expand Down
30 changes: 30 additions & 0 deletions Sources/Persistence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,51 @@ import Foundation
*/
public protocol PersistenceIntegration: Integration {

/**
Update the local datastore with the latest delta messages from the most recently fetched SyncSpace response.

There is no guarantee which thread this will be called from, so it is your responsibility when implementing
this method, to execute on whatever thread your local datastore may require operations to be executed on.
*/
func update(with syncSpace: SyncSpace)


/**
This is called whenever a new Asset was created or an existing one was updated.

There is no guarantee which thread this will be called from, so it is your responsibility when implementing
this method, to execute on whatever thread your local datastore may require operations to be executed on.

- parameter asset: The created/updated Asset
*/
func create(asset: Asset)

/**
This is called whenever an Asset was deleted.

There is no guarantee which thread this will be called from, so it is your responsibility when implementing
this method, to execute on whatever thread your local datastore may require operations to be executed on.

- parameter assetId: Identifier of the Asset that was deleted.
*/
func delete(assetWithId: String)

/**
This is called whenever a new Entry was created or an existing one was updated.

There is no guarantee which thread this will be called from, so it is your responsibility when implementing
this method, to execute on whatever thread your local datastore may require operations to be executed on.

- parameter entry: The created/updated Entry
*/
func create(entry: Entry)

/**
This is called whenever an Entry was deleted.

There is no guarantee which thread this will be called from, so it is your responsibility when implementing
this method, to execute on whatever thread your local datastore may require operations to be executed on.

- parameter entryId: Identifier of the Entry that was deleted.
*/
func delete(entryWithId: String)
Expand All @@ -49,19 +70,28 @@ public protocol PersistenceIntegration: Integration {
This method is called on completion of a successful `Client.initialSync` and `Client.nextSync`
calls so that the sync token can be cached and future launches of your application can synchronize
without doing an `initialSync`.

There is no guarantee which thread this will be called from, so it is your responsibility when implementing
this method, to execute on whatever thread your local datastore may require operations to be executed on.
*/
func update(syncToken: String)

/**
This method is called after all `Entry`s have been created and all links have been resolved.
The implementation should map `Entry` fields that are `Link`s to persistent relationships in
the underlying persistent data store.

There is no guarantee which thread this will be called from, so it is your responsibility when implementing
this method, to execute on whatever thread your local datastore may require operations to be executed on.
*/
func resolveRelationships()

/**
This method is called after all `Asset`s and `Entry`s have been tranformed to persistable data
structures. The implementation should actually perform the save operation to the persisent database.

There is no guarantee which thread this will be called from, so it is your responsibility when implementing
this method, to execute on whatever thread your local datastore may require operations to be executed on.
*/
func save()
}
28 changes: 8 additions & 20 deletions Sources/SyncSpace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public final class SyncSpace: ImmutableMappable {
internal var assetsMap = [String: Asset]()
internal var entriesMap = [String: Entry]()

var deletedAssets = [String]()
var deletedEntries = [String]()
public var deletedAssetIds = [String]()
public var deletedEntryIds = [String]()

var hasMorePages: Bool

Expand Down Expand Up @@ -101,43 +101,31 @@ public final class SyncSpace: ImmutableMappable {
}
}

internal func updateWithDiffs(from syncSpace: SyncSpace, persistenceIntegration: PersistenceIntegration?) {
internal func updateWithDiffs(from syncSpace: SyncSpace) {

for asset in syncSpace.assets {
assetsMap[asset.sys.id] = asset
persistenceIntegration?.create(asset: asset)
}

// Update and deduplicate all entries.
for entry in syncSpace.entries {
entriesMap[entry.sys.id] = entry
}

// resolve all entries.
// Resolve all entries in-memory.
for entry in entries {
entry.resolveLinks(against: entries, and: assets)
persistenceIntegration?.create(entry: entry)
}

for deletedAssetId in syncSpace.deletedAssets {
for deletedAssetId in syncSpace.deletedAssetIds {
assetsMap.removeValue(forKey: deletedAssetId)
persistenceIntegration?.delete(assetWithId: deletedAssetId)
}

for deletedEntryId in syncSpace.deletedEntries {
for deletedEntryId in syncSpace.deletedEntryIds {
entriesMap.removeValue(forKey: deletedEntryId)
persistenceIntegration?.delete(entryWithId: deletedEntryId)
}

// Reset so that we can't send same deletion messages twice.
self.deletedEntries = [String]()
self.deletedAssets = [String]()

syncToken = syncSpace.syncToken

persistenceIntegration?.update(syncToken: syncToken)
persistenceIntegration?.resolveRelationships()
persistenceIntegration?.save()
}

internal func cache(resources: [Resource]) {
Expand All @@ -151,8 +139,8 @@ public final class SyncSpace: ImmutableMappable {

case let deletedResource as DeletedResource:
switch deletedResource.sys.type {
case "DeletedAsset": self.deletedAssets.append(deletedResource.sys.id)
case "DeletedEntry": self.deletedEntries.append(deletedResource.sys.id)
case "DeletedAsset": self.deletedAssetIds.append(deletedResource.sys.id)
case "DeletedEntry": self.deletedEntryIds.append(deletedResource.sys.id)
default: break
}
default: break
Expand Down
12 changes: 6 additions & 6 deletions Tests/ObjectMappingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,31 +109,31 @@ class ObjectMappingTests: XCTestCase {
}
}

func testDecodeSyncResponsesWithDeletedAssets() {
func testDecodeSyncResponsesWithdeletedAssetIds() {
do {
let map = Map(mappingType: .fromJSON, JSON: ObjectMappingTests.jsonData("deleted-asset"))

let syncSpace = try SyncSpace(map: map)

expect(syncSpace.assets.count).to(equal(0))
expect(syncSpace.entries.count).to(equal(0))
expect(syncSpace.deletedAssets.count).to(equal(1))
expect(syncSpace.deletedEntries.count).to(equal(0))
expect(syncSpace.deletedAssetIds.count).to(equal(1))
expect(syncSpace.deletedEntryIds.count).to(equal(0))
} catch _ {
fail("Decoding sync responses with deleted Assets should not throw an error")
}
}

func testDecodeSyncResponsesWithDeletedEntries() {
func testDecodeSyncResponsesWithdeletedEntryIds() {
do {

let map = Map(mappingType: .fromJSON, JSON: ObjectMappingTests.jsonData("deleted"))
let syncSpace = try SyncSpace(map: map)

expect(syncSpace.assets.count).to(equal(0))
expect(syncSpace.entries.count).to(equal(0))
expect(syncSpace.deletedAssets.count).to(equal(0))
expect(syncSpace.deletedEntries.count).to(equal(1))
expect(syncSpace.deletedAssetIds.count).to(equal(0))
expect(syncSpace.deletedEntryIds.count).to(equal(1))
} catch _ {
fail("Decoding sync responses with deleted Entries should not throw an error")
}
Expand Down

0 comments on commit 80fd768

Please sign in to comment.