Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved validation of creators during sync #747

Merged
merged 2 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion ZShare/DbRequests/CreateBackendItemDbRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@ struct CreateBackendItemDbRequest: DbResponseRequest {
throw DbError.objectNotFound
}

_ = try StoreItemsDbResponseRequest(responses: [self.item], schemaController: self.schemaController, dateParser: self.dateParser, preferResponseData: true).process(in: database)
_ = try StoreItemsDbResponseRequest(
responses: [self.item],
schemaController: self.schemaController,
dateParser: self.dateParser,
preferResponseData: true,
denyIncorrectCreator: false
)
.process(in: database)

guard let item = database.objects(RItem.self).filter(.key(self.item.key, in: libraryId)).first else {
throw DbError.objectNotFound
Expand Down
27 changes: 17 additions & 10 deletions ZShare/DbRequests/CreateItemWithAttachmentDbRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ struct CreateItemWithAttachmentDbRequest: DbResponseRequest {
var needsWrite: Bool { return true }

func process(in database: Realm) throws -> (RItem, RItem) {
_ = try StoreItemsDbResponseRequest(responses: [self.item],
schemaController: self.schemaController,
dateParser: self.dateParser,
preferResponseData: true).process(in: database)
_ = try StoreItemsDbResponseRequest(
responses: [self.item],
schemaController: self.schemaController,
dateParser: self.dateParser,
preferResponseData: true,
denyIncorrectCreator: false
)
.process(in: database)

guard let item = database.objects(RItem.self).filter(.key(self.item.key, in: self.attachment.libraryId)).first else {
throw DbError.objectNotFound
Expand All @@ -35,12 +39,15 @@ struct CreateItemWithAttachmentDbRequest: DbResponseRequest {
item.fields.forEach({ $0.changed = true })

let localizedType = self.schemaController.localized(itemType: ItemTypes.attachment) ?? ""
let attachment = try CreateAttachmentDbRequest(attachment: self.attachment,
parentKey: nil,
localizedType: localizedType,
includeAccessDate: self.attachment.hasUrl,
collections: [],
tags: []).process(in: database)
let attachment = try CreateAttachmentDbRequest(
attachment: self.attachment,
parentKey: nil,
localizedType: localizedType,
includeAccessDate: self.attachment.hasUrl,
collections: [],
tags: []
)
.process(in: database)

attachment.parent = item
attachment.changes.append(RObjectChange.create(changes: RItemChanges.parent))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ struct CreateTranslatedItemsDbRequest: DbRequest {

func process(in database: Realm) throws {
for response in self.responses {
let (item, _) = try StoreItemDbRequest(response: response, schemaController: self.schemaController, dateParser: self.dateParser, preferRemoteData: true).process(in: database)
let (item, _) = try StoreItemDbRequest(
response: response,
schemaController: self.schemaController,
dateParser: self.dateParser,
preferRemoteData: true,
denyIncorrectCreator: false
)
.process(in: database)

item.changeType = .user
for field in item.fields {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,27 @@ struct MarkItemAsSyncedAndUpdateDbRequest: DbRequest {
guard let item = database.objects(RItem.self).filter(.key(self.response.key, in: self.libraryId)).first else { return }

item.deleteChanges(uuids: self.changeUuids, database: database)
self.updateUnchangedData(of: item, with: self.response, database: database)
try self.updateUnchangedData(of: item, with: self.response, database: database)

if let parent = item.parent {
// This is to mitigate the issue in item detail screen (ItemDetailActionHandler.shouldReloadData) where observing of `children` doesn't report changes between `oldValue` and `newValue`.
parent.version = parent.version
}
}

private func updateUnchangedData(of item: RItem, with response: ItemResponse, database: Realm) {
private func updateUnchangedData(of item: RItem, with response: ItemResponse, database: Realm) throws {
let localChanges = item.changedFields

if localChanges.isEmpty {
_ = StoreItemDbRequest.update(item: item, libraryId: self.libraryId, with: response, schemaController: self.schemaController, dateParser: self.dateParser, database: database)
_ = try StoreItemDbRequest.update(
item: item,
libraryId: self.libraryId,
with: response,
denyIncorrectCreator: false,
schemaController: self.schemaController,
dateParser: self.dateParser,
database: database
)
item.changeType = .syncResponse
return
}
Expand Down Expand Up @@ -168,7 +176,7 @@ struct MarkItemAsSyncedAndUpdateDbRequest: DbRequest {
}

if !localChanges.contains(.creators) {
StoreItemDbRequest.sync(creators: response.creators, item: item, schemaController: self.schemaController, database: database)
try StoreItemDbRequest.sync(creators: response.creators, item: item, denyIncorrectCreator: false, schemaController: self.schemaController, database: database)
}

if !localChanges.contains(.relations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ struct StoreItemsResponse {
enum Error: Swift.Error {
case itemDeleted(ItemResponse)
case itemChanged(ItemResponse)
case noValidCreators(key: String, itemType: String)
case invalidCreator(key: String, creatorType: String)
}

let changedFilenames: [FilenameChange]
Expand All @@ -35,6 +37,7 @@ struct StoreItemsDbResponseRequest: DbResponseRequest {
unowned let schemaController: SchemaController
unowned let dateParser: DateParser
let preferResponseData: Bool
let denyIncorrectCreator: Bool

var needsWrite: Bool { return true }

Expand All @@ -44,7 +47,14 @@ struct StoreItemsDbResponseRequest: DbResponseRequest {

for response in self.responses {
do {
let (_, change) = try StoreItemDbRequest(response: response, schemaController: self.schemaController, dateParser: self.dateParser, preferRemoteData: self.preferResponseData).process(in: database)
let (_, change) = try StoreItemDbRequest(
response: response,
schemaController: self.schemaController,
dateParser: self.dateParser,
preferRemoteData: self.preferResponseData,
denyIncorrectCreator: self.denyIncorrectCreator
)
.process(in: database)
if let change = change {
filenameChanges.append(change)
}
Expand All @@ -66,6 +76,7 @@ struct StoreItemDbRequest: DbResponseRequest {
unowned let schemaController: SchemaController
unowned let dateParser: DateParser
let preferRemoteData: Bool
let denyIncorrectCreator: Bool

var needsWrite: Bool { return true }

Expand Down Expand Up @@ -96,10 +107,26 @@ struct StoreItemDbRequest: DbResponseRequest {
item.attachmentNeedsSync = false
}

return StoreItemDbRequest.update(item: item, libraryId: libraryId, with: self.response, schemaController: self.schemaController, dateParser: self.dateParser, database: database)
return try StoreItemDbRequest.update(
item: item,
libraryId: libraryId,
with: self.response,
denyIncorrectCreator: self.denyIncorrectCreator,
schemaController: self.schemaController,
dateParser: self.dateParser,
database: database
)
}

static func update(item: RItem, libraryId: LibraryIdentifier, with response: ItemResponse, schemaController: SchemaController, dateParser: DateParser, database: Realm) -> (RItem, StoreItemsResponse.FilenameChange?) {
static func update(
item: RItem,
libraryId: LibraryIdentifier,
with response: ItemResponse,
denyIncorrectCreator: Bool,
schemaController: SchemaController,
dateParser: DateParser,
database: Realm
) throws -> (RItem, StoreItemsResponse.FilenameChange?) {
item.key = response.key
item.rawType = response.rawType
item.localizedType = schemaController.localized(itemType: response.rawType) ?? ""
Expand All @@ -118,7 +145,7 @@ struct StoreItemDbRequest: DbResponseRequest {
self.syncParent(key: response.parentKey, libraryId: libraryId, item: item, database: database)
self.syncCollections(keys: response.collectionKeys, libraryId: libraryId, item: item, database: database)
self.sync(tags: response.tags, libraryId: libraryId, item: item, database: database)
self.sync(creators: response.creators, item: item, schemaController: schemaController, database: database)
try self.sync(creators: response.creators, item: item, denyIncorrectCreator: denyIncorrectCreator, schemaController: schemaController, database: database)
self.sync(relations: response.relations, item: item, database: database)
self.syncLinks(data: response, item: item, database: database)
self.syncUsers(createdBy: response.createdBy, lastModifiedBy: response.lastModifiedBy, item: item, database: database)
Expand Down Expand Up @@ -406,12 +433,21 @@ struct StoreItemDbRequest: DbResponseRequest {
}
}

static func sync(creators: [CreatorResponse], item: RItem, schemaController: SchemaController, database: Realm) {
static func sync(creators: [CreatorResponse], item: RItem, denyIncorrectCreator: Bool, schemaController: SchemaController, database: Realm) throws {
switch item.rawType {
case ItemTypes.annotation, ItemTypes.attachment, ItemTypes.note:
// These item types don't support creators, so `validCreators` would always be empty.
return

default:
break
}

database.delete(item.creators)

guard let validCreators = schemaController.creators(for: item.rawType), !validCreators.isEmpty else {
DDLogError("StoreItemsDbResponseRequest: can't find valid creators for item type \(item.rawType). Skipping creators.")
return
throw StoreItemsResponse.Error.noValidCreators(key: item.key, itemType: item.rawType)
}

for (idx, object) in creators.enumerated() {
Expand All @@ -423,6 +459,8 @@ struct StoreItemDbRequest: DbResponseRequest {

if validCreators.contains(where: { $0.creatorType == object.creatorType }) {
creator.rawType = object.creatorType
} else if denyIncorrectCreator {
throw StoreItemsResponse.Error.invalidCreator(key: item.key, creatorType: object.creatorType)
} else if let primaryCreator = validCreators.first(where: { $0.primary }) {
DDLogError("StoreItemsDbResponseRquest: creator type '\(object.creatorType)' isn't valid for \(item.rawType) - changing to primary creator")
creator.rawType = primaryCreator.creatorType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ struct RevertLibraryFilesSyncAction: SyncAction {
try coordinator.perform(request: DeleteObjectsDbRequest<RItem>(keys: failedKeys, libraryId: self.libraryId))
// Store cached objects from backend to local database to get rid of local changes.
DDLogError("RevertLibraryFilesSyncAction: restore cached objects")
let request = StoreItemsDbResponseRequest(responses: cachedResponses, schemaController: self.schemaController, dateParser: self.dateParser, preferResponseData: true)
let request = StoreItemsDbResponseRequest(
responses: cachedResponses,
schemaController: self.schemaController,
dateParser: self.dateParser,
preferResponseData: true,
denyIncorrectCreator: true
)
changedFilenames = try coordinator.perform(request: request).changedFilenames
coordinator.invalidate()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ struct RevertLibraryUpdatesSyncAction: SyncAction {
try coordinator.perform(writeRequests: [storeCollectionsRequest, storeSearchesRequest])

// Force response data here, since we're reverting
let storeItemsRequest = StoreItemsDbResponseRequest(responses: items.responses, schemaController: self.schemaController, dateParser: self.dateParser, preferResponseData: true)
let storeItemsRequest = StoreItemsDbResponseRequest(
responses: items.responses,
schemaController: self.schemaController,
dateParser: self.dateParser,
preferResponseData: true,
denyIncorrectCreator: true
)
changes = try coordinator.perform(request: storeItemsRequest).changedFilenames

failedCollections = collections.failed
Expand Down
2 changes: 1 addition & 1 deletion Zotero/Controllers/Sync/SyncBatchProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ final class SyncBatchProcessor {
self.storeIndividualObjects(from: objects, type: .item, libraryId: libraryId)

// BETA: - forcing preferResponseData to true for beta, it should be false here so that we report conflicts
let request = StoreItemsDbResponseRequest(responses: items, schemaController: self.schemaController, dateParser: self.dateParser, preferResponseData: true)
let request = StoreItemsDbResponseRequest(responses: items, schemaController: self.schemaController, dateParser: self.dateParser, preferResponseData: true, denyIncorrectCreator: true)
let response = try self.dbStorage.perform(request: request, on: self.storageQueue, invalidateRealm: true)
let failedKeys = self.failedKeys(from: expectedKeys, parsedKeys: items.map({ $0.key }), errors: errors)

Expand Down
9 changes: 6 additions & 3 deletions ZoteroTests/SyncActionsSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ final class SyncActionsSpec: QuickSpec {
responses: [itemResponse],
schemaController: TestControllers.schemaController,
dateParser: TestControllers.dateParser,
preferResponseData: true
preferResponseData: true,
denyIncorrectCreator: true
),
on: .main
)
Expand Down Expand Up @@ -229,7 +230,8 @@ final class SyncActionsSpec: QuickSpec {
responses: [itemResponse],
schemaController: TestControllers.schemaController,
dateParser: TestControllers.dateParser,
preferResponseData: true
preferResponseData: true,
denyIncorrectCreator: true
),
on: .main
)
Expand Down Expand Up @@ -346,7 +348,8 @@ final class SyncActionsSpec: QuickSpec {
responses: [itemResponse],
schemaController: TestControllers.schemaController,
dateParser: TestControllers.dateParser,
preferResponseData: true
preferResponseData: true,
denyIncorrectCreator: false
),
on: .main
)
Expand Down
2 changes: 1 addition & 1 deletion ZoteroTests/WebDavControllerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ final class WebDavControllerSpec: QuickSpec {
switch error {
case .sessionTaskFailed(let error):
let nsError = error as NSError
if nsError.code == NSURLErrorCannotConnectToHost {
if nsError.code == NSURLErrorCannotConnectToHost || nsError.code == NSURLErrorAppTransportSecurityRequiresSecureConnection {
finished()
return
}
Expand Down