Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
memri
iOS client for Memri
Commits
13149b10
Commit
13149b10
authored
Aug 03, 2021
by
Chaitanya Pandit
Committed by
Amirjanyan
Aug 03, 2021
Browse files
Create Auth UI flow
parent
40592674
Changes
12
Hide whitespace changes
Inline
Side-by-side
Memri.xcodeproj/project.pbxproj
View file @
13149b10
...
...
@@ -30,6 +30,7 @@
545A24AE2639ECAB00E0CC12
/* Authentication.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
545A24AD2639ECAA00E0CC12
/* Authentication.swift */
;
};
545A24B62639EE2F00E0CC12
/* Data.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
545A24B52639EE2F00E0CC12
/* Data.swift */
;
};
545FD29726205F1200F23F46
/* Dictionary.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
545FD29626205F1200F23F46
/* Dictionary.swift */
;
};
547780D5267A6545009DC45B
/* PluginHandler.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
547780D4267A6545009DC45B
/* PluginHandler.swift */
;
};
54663245267B767F009D9B46
/* CustomRenderer.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
54663244267B767F009D9B46
/* CustomRenderer.swift */
;
};
54663275267BDE56009D9B46
/* SettingsPane.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
54663274267BDE56009D9B46
/* SettingsPane.swift */
;
};
5478EDD92641A3FC000EB938
/* ItemRecord+Sync.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
5478EDD82641A3FC000EB938
/* ItemRecord+Sync.swift */
;
};
...
...
@@ -268,6 +269,7 @@
545A24AD2639ECAA00E0CC12
/* Authentication.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
Authentication.swift
;
sourceTree
=
"<group>"
;
};
545A24B52639EE2F00E0CC12
/* Data.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
Data.swift
;
sourceTree
=
"<group>"
;
};
545FD29626205F1200F23F46
/* Dictionary.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
Dictionary.swift
;
sourceTree
=
"<group>"
;
};
547780D4267A6545009DC45B
/* PluginHandler.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
PluginHandler.swift
;
sourceTree
=
"<group>"
;
};
54663244267B767F009D9B46
/* CustomRenderer.swift */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.swift
;
path
=
CustomRenderer.swift
;
sourceTree
=
"<group>"
;
};
54663274267BDE56009D9B46
/* SettingsPane.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
SettingsPane.swift
;
sourceTree
=
"<group>"
;
};
5478EDD82641A3FC000EB938
/* ItemRecord+Sync.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
"ItemRecord+Sync.swift"
;
sourceTree
=
"<group>"
;
};
...
...
@@ -530,6 +532,14 @@
path
=
views
;
sourceTree
=
"<group>"
;
};
547780D3267A64E6009DC45B
/* Plugins */
=
{
isa
=
PBXGroup
;
children
=
(
547780D4267A6545009DC45B
/* PluginHandler.swift */
,
);
path
=
Plugins
;
sourceTree
=
"<group>"
;
};
54D36B90266E6B97003788F5
/* PubSub */
=
{
isa
=
PBXGroup
;
children
=
(
...
...
@@ -918,6 +928,7 @@
D744F73C25BACB730002C8B7
/* NoteEditorRenderer.swift */
,
D7CC3A0E25CE208B001EF34A
/* GeneralEditorRenderer.swift */
,
D744F74525BADE4A0002C8B7
/* LabelAnnotationRenderer.swift */
,
547780DC267A734D009DC45B
/* CustomRenderer.swift */
,
);
path
=
Renderers
;
sourceTree
=
"<group>"
;
...
...
@@ -1071,6 +1082,7 @@
D7EBBBDE2586CD0800A343E6
/* Controllers */
=
{
isa
=
PBXGroup
;
children
=
(
547780D3267A64E6009DC45B
/* Plugins */
,
D7EBBBDF2586CD1C00A343E6
/* AppController.swift */
,
D7EBBC2A2586D1D800A343E6
/* SceneController.swift */
,
D7E89C6725A729D9003AEFD8
/* FileStorageController.swift */
,
...
...
@@ -1496,6 +1508,7 @@
5430C73826283DFD001F49F1
/* RealRecord.swift in Sources */
,
D7E89CF125A867F3003AEFD8
/* CVUAction.swift in Sources */
,
D7EBBC212586D05F00A343E6
/* UIHostingControllerNoSafeArea.swift in Sources */
,
547780D5267A6545009DC45B
/* PluginHandler.swift in Sources */
,
D7EBBC162586CFD200A343E6
/* KeyboardToolbar.swift in Sources */
,
D7EBBBF92586CE8C00A343E6
/* ViewContext.swift in Sources */
,
D749E32025BC40B000E51A83
/* CVU_Button.swift in Sources */
,
...
...
MemriApp/Assets/defaultCVU/named/auth-view.cvu
0 → 100644
View file @
13149b10
.auth-view {
title: "Login"
defaultRenderer: generalEditor
showContextualBottomBar: false
showBottomBar: false
showDefaultLayout: false
[renderer = generalEditor] {
layout: [
{ section: username, fields: identifier, exclude: labels }
{ section: password, fields: secret, exclude: labels }
{ section: oAuthCode, fields: code, exclude: labels }
{ section: login }
]
}
login {
showTitle: false
HStack {
alignment: center
Spacer
Button {
padding: 20 0 20 0
onPress: [sync back]
VStack {
background: #218721
cornerRadius: 5
Text {
text: "Login"
font: 16 semibold
color: #fff
padding: 5 8 5 8
}
}
}
Spacer
}
}
}
MemriApp/Assets/demo_database.json
View file @
13149b10
...
...
@@ -1415,6 +1415,7 @@
}
]
},
{
"_type"
:
"Label"
,
"id"
:
1000
,
...
...
@@ -3195,6 +3196,26 @@
}
]
},
{
"_type"
:
"Plugin"
,
"name"
:
"Template"
,
"container"
:
"plugin_template"
,
"icon"
:
"message"
,
"itemDescription"
:
"Template Plugin by Alp"
,
"allEdges"
:[
{
"_type"
:
"view"
,
"targetType"
:
"CVUStoredDefinition"
,
"id"
:
202106162000
}
]
},
{
"_type"
:
"CVUStoredDefinition"
,
"id"
:
202106162000
,
"definition"
:
"auth-view"
},
{
"_type"
:
"Post"
,
"message"
:
"OMG. I love @TheSuffers so much! When I need some inspiration I just listen. https://www.youtube.com/watch?v=RM3hj-6F1pk"
,
...
...
MemriApp/Assets/schema.json
View file @
13149b10
...
...
@@ -45,6 +45,21 @@
"property"
:
"service"
,
"value_type"
:
"string"
},
{
"item_type"
:
"Account"
,
"property"
:
"identifier"
,
"value_type"
:
"string"
},
{
"item_type"
:
"Account"
,
"property"
:
"secret"
,
"value_type"
:
"string"
},
{
"item_type"
:
"Account"
,
"property"
:
"code"
,
"value_type"
:
"string"
},
{
"item_type"
:
"Account"
,
"property"
:
"itemType"
,
...
...
@@ -1684,6 +1699,16 @@
"property"
:
"container"
,
"value_type"
:
"string"
},
{
"item_type"
:
"StartPlugin"
,
"property"
:
"state"
,
"value_type"
:
"string"
},
{
"item_type"
:
"StartPlugin"
,
"property"
:
"oAuthUrl"
,
"value_type"
:
"string"
},
{
"item_type"
:
"StartPlugin"
,
"property"
:
"targetItemId"
,
...
...
MemriApp/CVU/actions/CVUAction.swift
View file @
13149b10
...
...
@@ -126,6 +126,8 @@ func cvuAction(named: String) -> CVUAction.Type? {
return
CVUAction_ToPreviousItem
.
self
case
"selectall"
:
return
CVUAction_SelectAll
.
self
case
"sync"
:
return
CVUAction_Sync
.
self
case
"deselectall"
:
return
CVUAction_DeselectAll
.
self
default
:
...
...
@@ -400,7 +402,8 @@ struct CVUAction_StartPlugin: CVUAction {
let
lookup
=
CVULookupController
()
let
db
=
sceneController
.
appController
.
databaseController
guard
let
targetItemIdValue
=
vars
[
"targetItemId"
],
guard
let
plugin
=
context
.
currentItem
,
let
targetItemIdValue
=
vars
[
"targetItemId"
],
let
targetItemId
:
String
=
lookup
.
resolve
(
value
:
targetItemIdValue
,
context
:
context
,
db
:
db
),
let
containerValue
=
vars
[
"container"
],
let
container
:
String
=
lookup
.
resolve
(
value
:
containerValue
,
context
:
context
,
db
:
db
)
else
{
...
...
@@ -410,12 +413,59 @@ struct CVUAction_StartPlugin: CVUAction {
do
{
let
startPluginItem
=
ItemRecord
(
type
:
"StartPlugin"
)
try
startPluginItem
.
save
()
try
startPluginItem
.
setPropertyValue
(
name
:
"container"
,
value
:
.
string
(
container
))
try
startPluginItem
.
setPropertyValue
(
name
:
"targetItemId"
,
value
:
.
string
(
targetItemId
))
try
startPluginItem
.
setPropertyValue
(
name
:
"container"
,
value
:
.
string
(
container
))
try
startPluginItem
.
setPropertyValue
(
name
:
"state"
,
value
:
.
string
(
"idle"
))
PluginHandler
.
start
(
plugin
:
plugin
,
runner
:
startPluginItem
,
sceneController
:
sceneController
,
context
:
context
)
}
catch
let
error
{
print
(
"Error starting plugin: "
,
error
)
}
}
}
struct
CVUAction_Sync
:
CVUAction
{
var
vars
:
[
String
:
CVUValue
]
func
execute
(
sceneController
:
SceneController
,
context
:
CVUContext
,
completion
:
((
Any
?)
->
())?)
{
do
{
var
pendingItems
=
[
BaseRecord
]()
if
let
item
=
context
.
currentItem
{
if
(
item
.
syncState
==
.
skip
)
{
item
.
syncState
=
.
create
pendingItems
.
append
(
item
)
}
let
edges
=
item
.
edges
()
+
item
.
reverseEdgeObjects
()
for
edge
in
edges
{
if
(
edge
.
syncState
==
.
skip
)
{
edge
.
syncState
=
.
create
pendingItems
.
append
(
edge
)
}
}
let
edgeItems
=
item
.
edgeItems
()
+
item
.
reverseEdgeItems
()
for
edgeItem
in
edgeItems
{
if
(
edgeItem
.
syncState
==
.
skip
)
{
edgeItem
.
syncState
=
.
create
pendingItems
.
append
(
edgeItem
)
}
}
}
try
AppController
.
shared
.
databaseController
.
writeSync
{
(
db
)
in
for
item
in
pendingItems
{
try
item
.
save
(
db
)
}
}
try
AppController
.
shared
.
syncController
.
sync
()
if
(
sceneController
.
isInEditMode
)
{
sceneController
.
toggleEditMode
()
}
}
catch
let
error
{
print
(
"Error starting
plugin
: "
,
error
)
print
(
"Error starting
sync
: "
,
error
)
}
completion
?(
nil
)
...
...
MemriApp/Controllers/Database/ItemRecord+Properties.swift
View file @
13149b10
...
...
@@ -114,7 +114,10 @@ extension ItemRecord {
)
throws
{
try
dbController
.
writeSync
{
(
db
)
in
try
setPropertyValue
(
name
:
name
,
value
:
value
,
db
:
db
)
self
.
syncState
=
.
update
// If created, don't change to update else will go in sync payload as updatedItems
if
(
self
.
syncState
!=
.
skip
&&
self
.
syncState
!=
.
create
)
{
self
.
syncState
=
.
update
}
try
self
.
update
(
db
)
if
(
addLog
)
{
try
addChangeLog
(
name
:
name
,
value
:
value
,
db
:
dbController
)
...
...
@@ -130,22 +133,24 @@ extension ItemRecord {
do
{
switch
value
{
case
.
string
(
let
stringValue
):
try
?
StringRecord
.
setPropertyValue
(
name
:
name
,
value
:
stringValue
,
item
:
self
,
db
:
db
)
try
StringRecord
.
setPropertyValue
(
name
:
name
,
value
:
stringValue
,
item
:
self
,
db
:
db
)
case
.
bool
(
let
boolValue
):
try
?
IntegerRecord
.
setPropertyValue
(
name
:
name
,
value
:
boolValue
==
true
?
1
:
0
,
item
:
self
,
db
:
db
)
try
IntegerRecord
.
setPropertyValue
(
name
:
name
,
value
:
boolValue
==
true
?
1
:
0
,
item
:
self
,
db
:
db
)
case
.
int
(
let
intValue
):
try
?
IntegerRecord
.
setPropertyValue
(
name
:
name
,
value
:
intValue
,
item
:
self
,
db
:
db
)
try
IntegerRecord
.
setPropertyValue
(
name
:
name
,
value
:
intValue
,
item
:
self
,
db
:
db
)
case
.
double
(
let
doubleValue
):
try
?
RealRecord
.
setPropertyValue
(
name
:
name
,
value
:
doubleValue
,
item
:
self
,
db
:
db
)
try
RealRecord
.
setPropertyValue
(
name
:
name
,
value
:
doubleValue
,
item
:
self
,
db
:
db
)
case
.
datetime
(
let
dateTimeValue
):
try
?
RealRecord
.
setPropertyValue
(
name
:
name
,
value
:
Double
(
DatabaseHelperFunctions
.
encode
(
dateTimeValue
)),
item
:
self
,
db
:
db
)
try
RealRecord
.
setPropertyValue
(
name
:
name
,
value
:
Double
(
DatabaseHelperFunctions
.
encode
(
dateTimeValue
)),
item
:
self
,
db
:
db
)
case
.
blob
(
let
data
):
if
let
stringValue
=
String
(
data
:
data
,
encoding
:
.
utf8
)
{
try
?
StringRecord
.
setPropertyValue
(
name
:
name
,
value
:
stringValue
,
item
:
self
,
db
:
db
)
try
StringRecord
.
setPropertyValue
(
name
:
name
,
value
:
stringValue
,
item
:
self
,
db
:
db
)
}
default
:
break
}
}
catch
let
error
{
print
(
"ERROR saving Property: "
,
error
)
}
}
...
...
MemriApp/Controllers/Database/ItemRecord.swift
View file @
13149b10
...
...
@@ -9,6 +9,7 @@ import Foundation
import
GRDB
enum
SyncState
:
String
,
Codable
,
Hashable
,
DatabaseValueConvertible
{
case
skip
case
create
case
update
case
noChanges
...
...
@@ -116,11 +117,15 @@ class ItemRecord: BaseRecord, Equatable, Hashable, Identifiable, Codable {
}
}
func
edges
(
_
name
:
String
,
db
:
Database
)
->
[
EdgeRecord
]?
{
try
?
request
(
for
:
ItemRecord
.
edges
)
.
filter
(
EdgeRecord
.
Columns
.
name
==
name
)
.
fetchAll
(
db
)
func
edges
(
_
name
:
String
?
=
nil
,
db
:
Database
)
->
[
EdgeRecord
]?
{
if
let
edgeName
=
name
{
return
try
?
request
(
for
:
ItemRecord
.
edges
)
.
filter
(
EdgeRecord
.
Columns
.
name
==
edgeName
)
.
fetchAll
(
db
)
}
else
{
return
try
?
request
(
for
:
ItemRecord
.
edges
)
.
fetchAll
(
db
)
}
}
func
edges
(
_
type
:
String
,
db
dbController
:
DatabaseController
=
AppController
.
shared
.
databaseController
)
->
[
EdgeRecord
]
{
func
edges
(
_
type
:
String
?
=
nil
,
db
dbController
:
DatabaseController
=
AppController
.
shared
.
databaseController
)
->
[
EdgeRecord
]
{
(
try
?
dbController
.
read
{
db
in
edges
(
type
,
db
:
db
)
})
??
[]
...
...
@@ -132,28 +137,52 @@ class ItemRecord: BaseRecord, Equatable, Hashable, Identifiable, Codable {
}
}
func
edgeItems
(
_
type
:
String
,
db
:
Database
)
->
[
ItemRecord
]?
{
func
edgeItems
(
_
type
:
String
?
=
nil
,
db
:
Database
)
->
[
ItemRecord
]?
{
edges
(
type
,
db
:
db
)?
.
lazy
.
compactMap
{
$0
.
targetItem
(
db
:
db
)
}
}
func
edgeItems
(
_
type
:
String
,
db
dbController
:
DatabaseController
=
AppController
.
shared
.
databaseController
)
->
[
ItemRecord
]
{
func
edgeItems
(
_
type
:
String
?
=
nil
,
db
dbController
:
DatabaseController
=
AppController
.
shared
.
databaseController
)
->
[
ItemRecord
]
{
(
try
?
dbController
.
read
{
db
in
edgeItems
(
type
,
db
:
db
)
})
??
[]
}
static
var
reverseEdges
=
hasMany
(
EdgeRecord
.
self
,
using
:
EdgeRecord
.
targetForeignKey
)
func
reverseEdgeObjects
(
_
name
:
String
?
=
nil
,
db
:
Database
)
->
[
EdgeRecord
]?
{
if
let
edgeName
=
name
{
return
try
?
request
(
for
:
ItemRecord
.
reverseEdges
)
.
filter
(
EdgeRecord
.
Columns
.
name
==
edgeName
)
.
fetchAll
(
db
)
}
else
{
return
try
?
request
(
for
:
ItemRecord
.
reverseEdges
)
.
fetchAll
(
db
)
}
}
func
reverseEdgeObjects
(
_
type
:
String
?
=
nil
,
db
dbController
:
DatabaseController
=
AppController
.
shared
.
databaseController
)
->
[
EdgeRecord
]
{
(
try
?
dbController
.
read
{
db
in
reverseEdgeObjects
(
type
,
db
:
db
)
})
??
[]
}
func
reverseEdgeItem
(
_
name
:
String
,
db
:
DatabaseController
=
AppController
.
shared
.
databaseController
)
->
ItemRecord
?
{
try
?
db
.
read
{
db
in
try
?
request
(
for
:
ItemRecord
.
reverseEdges
)
.
filter
(
EdgeRecord
.
Columns
.
name
==
name
)
.
fetchOne
(
db
)?
.
owningItem
(
db
:
db
)
}
}
func
reverseEdgeItems
(
_
name
:
String
,
db
:
DatabaseController
=
AppController
.
shared
.
databaseController
)
->
[
ItemRecord
]
{
(
try
?
db
.
read
{
db
in
try
?
request
(
for
:
ItemRecord
.
reverseEdges
)
.
filter
(
EdgeRecord
.
Columns
.
name
==
name
)
.
fetchAll
(
db
)
.
lazy
.
compactMap
{
$0
.
owningItem
(
db
:
db
)
}
})
??
[]
func
reverseEdgeItems
(
_
name
:
String
?
=
nil
,
db
:
Database
)
->
[
ItemRecord
]?
{
if
let
name
=
name
{
return
try
?
request
(
for
:
ItemRecord
.
reverseEdges
)
.
filter
(
EdgeRecord
.
Columns
.
name
==
name
)
.
fetchAll
(
db
)
.
lazy
.
compactMap
{
$0
.
owningItem
(
db
:
db
)
}
}
else
{
return
try
?
request
(
for
:
ItemRecord
.
reverseEdges
)
.
fetchAll
(
db
)
.
lazy
.
compactMap
{
$0
.
owningItem
(
db
:
db
)
}
}
}
func
reverseEdgeItems
(
_
name
:
String
?
=
nil
,
db
:
DatabaseController
=
AppController
.
shared
.
databaseController
)
->
[
ItemRecord
]
{
(
try
?
db
.
read
{
db
in
reverseEdgeItems
(
name
,
db
:
db
)
})
??
[]
}
...
...
MemriApp/Controllers/Plugins/PluginHandler.swift
0 → 100644
View file @
13149b10
//
// PluginHandler.swift
// Memri
import
Foundation
class
PluginHandler
{
public
static
func
start
(
plugin
:
ItemRecord
,
runner
:
ItemRecord
,
sceneController
:
SceneController
,
context
:
CVUContext
)
{
AppController
.
shared
.
pubsubController
.
startObservingItemProperty
(
runner
,
property
:
"state"
,
desiredValue
:
nil
)
{
(
newValue
,
error
)
in
guard
case
let
.
string
(
state
)
=
newValue
else
{
return
}
switch
state
{
case
"userActionNeeded"
:
presentCVUforPlugin
(
plugin
:
plugin
,
runner
:
runner
,
sceneController
:
sceneController
,
context
:
context
)
default
:
break
}
}
try
?
AppController
.
shared
.
syncController
.
sync
()
}
static
func
presentCVUforPlugin
(
plugin
:
ItemRecord
,
runner
:
ItemRecord
,
sceneController
:
SceneController
,
context
:
CVUContext
)
{
guard
let
runnerRowId
=
runner
.
rowId
,
let
view
=
plugin
.
edgeItem
(
"view"
),
case
let
.
string
(
viewName
)
=
view
.
propertyValue
(
"definition"
)
else
{
AppController
.
shared
.
pubsubController
.
stopObservingItemProperty
(
runner
,
property
:
"state"
)
return
}
let
item
=
ItemRecord
(
type
:
"Account"
)
item
.
syncState
=
.
skip
// Don't sync it yet
try
?
item
.
save
()
guard
let
itemRowId
=
item
.
rowId
,
let
edge
=
try
?
EdgeRecord
.
createFor
(
source
:
runnerRowId
,
type
:
"account"
,
target
:
itemRowId
)
else
{
return
}
edge
.
syncState
=
.
skip
// Don't sync it yet
try
?
edge
.
save
()
var
newVars
:
[
String
:
CVUValue
]
=
[:]
newVars
[
"viewArguments"
]
=
.
subdefinition
(
CVUDefinitionContent
(
properties
:
[
"readOnly"
:
.
constant
(
.
bool
(
false
))]))
CVUAction_OpenView
(
viewName
:
viewName
,
renderer
:
"generalEditor"
)
.
execute
(
sceneController
:
sceneController
,
context
:
context
.
replacingItem
(
item
),
completion
:
nil
)
sceneController
.
isInEditMode
=
true
}
}
MemriApp/Controllers/PubSub/PubSubController.swift
View file @
13149b10
...
...
@@ -10,9 +10,9 @@ struct ItemSubscription {
let
item
:
ItemRecord
let
property
:
String
var
retryCount
:
Int
=
0
let
desiredValue
:
PropertyDatabaseValue
let
desiredValue
:
PropertyDatabaseValue
?
let
request
:
DataRequest
?
=
nil
let
completion
:
((
ItemRecord
,
Error
?)
->
Void
)
let
completion
:
((
PropertyDatabaseValue
?
,
Error
?)
->
Void
)
}
extension
ItemSubscription
:
Hashable
{
...
...
@@ -37,8 +37,7 @@ class PubSubController {
private
var
cancellables
:
Set
<
AnyCancellable
>
=
Set
()
private
var
subscriptions
:
Set
<
AnyCancellable
>
=
[]
public
func
startObservingItemProperty
(
_
item
:
ItemRecord
,
property
:
String
,
desiredValue
:
PropertyDatabaseValue
,
completion
:
@escaping
((
ItemRecord
,
Error
?)
->
Void
))
{
public
func
startObservingItemProperty
(
_
item
:
ItemRecord
,
property
:
String
,
desiredValue
:
PropertyDatabaseValue
?,
completion
:
@escaping
((
PropertyDatabaseValue
?,
Error
?)
->
Void
))
{
stopObservingItemProperty
(
item
,
property
:
property
)
let
subscription
=
ItemSubscription
(
item
:
item
,
property
:
property
,
desiredValue
:
desiredValue
,
completion
:
completion
)
subscribers
.
insert
(
subscription
)
...
...
@@ -54,21 +53,52 @@ class PubSubController {
}
private
func
subscriptionForItem
(
_
item
:
ItemRecord
,
property
:
String
)
->
ItemSubscription
?
{
return
subscribers
.
filter
{
$0
.
item
.
id
==
item
.
id
&&
$0
.
property
==
property
}
.
first
return
subscribers
.
filter
{
$0
.
item
.
id
==
item
.
id
}
.
first
}
private
func
cancelSubscription
(
_
subscription
:
ItemSubscription
,
error
:
Error
?)
{
subscription
.
completion
(
subscription
.
item
,
error
??
StringError
(
description
:
"Cancelled"
))
subscription
.
completion
(
nil
,
error
??
StringError
(
description
:
"Cancelled"
))
subscribers
.
remove
(
subscription
)
}
private
func
checkSubscriptionFulfilled
(
_
dict
:
[
String
:
AnyDecodable
],
subscription
:
ItemSubscription
)
{
let
containsValue
=
containsExpectedValue
(
dict
,
itemType
:
subscription
.
item
.
type
,
property
:
subscription
.
property
,
expectedValue
:
subscription
.
desiredValue
)
private
func
checkSubscriptionFulfilled
(
_
dicts
:
[[
String
:
AnyDecodable
]],
subscription
:
ItemSubscription
)
{
// Dict could be nil if item is still not synced
guard
let
dict
=
dicts
.
first
,
dict
.
count
>
0
else
{
reschedule
(
subscription
)
return
}
guard
let
newValue
=
propertyValueFromDict
(
subscription
.
property
,
type
:
subscription
.
item
.
type
,
dict
:
dict
)
else
{
cancelSubscription
(
subscription
,
error
:
StringError
(
description
:
"Property not found"
))
return
}
guard
let
databaseValue
=
ItemRecord
.
fetchWithID
(
subscription
.
item
.
id
)?
.
propertyValue
(
subscription
.
property
)
else
{
cancelSubscription
(
subscription
,
error
:
StringError
(
description
:
"Database value not found"
))
return
}
guard
!
containsValue
else
{
subscription
.
completion
(
subscription
.
item
,
nil
)
subscribers
.
remove
(
subscription
)
// If we have expected value to look for, check if new value is same as that of expected value
if
let
expectedValue
=
subscription
.
desiredValue
{
let
containsValue
=
expectedValue
==
newValue
guard
!
containsValue
else
{
subscription
.
completion
(
newValue
,
nil
)
subscribers
.
remove
(
subscription
)
return
}
}
// else check if the new value is different from db, in which case we just call the completion block but DO NOT stop observing
// The part where we setup observer is responsible to cancel observation in this case
if
databaseValue
!=
newValue
{
// Update item in db so that next time we will detect change
if
let
dbItem
=
ItemRecord
.
fetchWithID
(
subscription
.
item
.
id
)
{
try
?
dbItem
.
setPropertyValue
(
name
:
subscription
.
property
,
value
:
newValue
)
}
subscription
.
completion
(
newValue
,
nil
)
reschedule
(
subscription
)
return
}
...
...
@@ -81,8 +111,16 @@ class PubSubController {
newSubscription
.
retryCount
=
newSubscription
.
retryCount
+
1
subscribers
.
remove
(
subscription
)
subscribers
.
insert
(
newSubscription
)
reschedule
(
newSubscription
)
}
private
func
reschedule
(
_
subscription
:
ItemSubscription
)
{
guard
subscribers
.
contains
(
subscription
)
else
{
return
}
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
PubSubController
.
pollInterval
)
{[
weak
self
]
in
self
?
.
startObserver
(
newS
ubscription
)
self
?
.
startObserver
(
s
ubscription
)
}
}
...
...
@@ -111,32 +149,26 @@ class PubSubController {
let
itemDicts
=
responseObjects
.
compactMap
{
object
->
[
String
:
AnyDecodable
]?
in
return
object
.
compactMapValues
{
AnyDecodable
(
$0
)}
}
guard
let
itemDict
=
itemDicts
.
first
else
{
self
?
.
cancelSubscription
(
subscription
,
error
:
StringError
(
description
:
"Error decoding item"
))
return
}
self
?
.
checkSubscriptionFulfilled
(
itemDict
,
subscription
:
subscription
)
self
?
.
checkSubscriptionFulfilled
(
itemDicts
,
subscription
:
subscription
)
}
.
store
(
in
:
&
subscriptions
)
}
catch
let
error
{
cancelSubscription
(
subscription
,
error
:
error
)
}
}
private
func
containsExpectedValue
(
_
dict
:
[
String
:
AnyDecodable
],
itemT
ype
:
String
,
property
:
String
,
expectedValue
:
PropertyDatabaseValue
)
->
Bool
{
private
func
propertyValueFromDict
(
_
property
:
String
,
t
ype
:
String
,
dict
:
[
String
:
AnyDecodable
])
->
PropertyDatabaseValue
?
{
guard
let
decodableValue
=
dict
[
property
]
else
{
return
false
return
nil
}
let
schema
=
AppController
.
shared
.
databaseController
.
schema
guard
!
ItemRecord
.
intrinsicProperties
.
contains
(
property
),
let
expectedType
=
schema
.
expectedType
(
forItemType
:
itemT
ype
,
propertyName
:
property
)
else
{
return
false
let
expectedType
=
schema
.
expectedType
(
forItemType
:
t
ype
,
propertyName
:
property
)
else
{
return
nil
}
let
databaseValue
=
try
?
PropertyDatabaseValue
(
value
:
decodableValue
.
value
,
propertyType
:
expectedType
)
return
databaseValue
==
expectedValue
return
try
?
PropertyDatabaseValue
(
value
:
decodableValue
.
value
,
propertyType
:
expectedType
)
}
}
MemriApp/Controllers/Syncing/SyncController.swift
View file @
13149b10
...
...
@@ -347,10 +347,15 @@ class SyncController {
let
networkCall
=
try
request
.
execute
(
connectionConfig
:
connectionConfig
)
networkCall
.
sink
{
(
result
)
in
if
result
.
error
!=
nil
,
let
resultData
=
result
.
data
{