Skip to content
GitLab
Explore
Projects
Groups
Snippets
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Memri
Browser application
Commits
4b6e071b
Commit
4b6e071b
authored
4 years ago
by
Amirjanyan
Browse files
Options
Download
Email Patches
Plain Diff
Convert from swift to js
parent
60b3b98c
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
model/Cache.ts
+399
-0
model/Cache.ts
model/DataItem.ts
+449
-0
model/DataItem.ts
with
848 additions
and
0 deletions
+848
-0
model/Cache.ts
0 → 100644
+
399
-
0
View file @
4b6e071b
//
// cache.swift
// memri
//
// Created by Ruben Daniels on 3/12/20.
// Copyright © 2020 memri. All rights reserved.
//
var
config
=
Realm
.
Configuration
(
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
51
,
// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
function
(
oldSchemaVersion
)
{
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if
(
oldSchemaVersion
<
2
)
{
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
}
)
/// Computes the Realm database path at /home/<user>/realm.memri/memri.realm and creates the directory (realm.memri) if it does not exist.
/// - Returns: the computed database file path
var
getRealmPath
=
function
()
{
let
homeDir
=
ProcessInfo
.
processInfo
.
environment
[
"
SIMULATOR_HOST_HOME
"
]
if
(
homeDir
)
{
let
realmDir
=
homeDir
+
"
/realm.memri
"
console
.
log
(
`REALM DIR:
${
realmDir
}
`
)
try
{
FileManager
.
default
.
createDirectory
(
realmDir
,
true
,
null
)
}
catch
(
error
)
{
console
.
log
(
error
)
}
return
realmDir
+
"
/memri.realm
"
}
else
{
throw
"
Could not get realm path
"
}
}
class
CacheTODO
{
//TODO
/// PodAPI object
podAPI
/// Object that schedules with the POD
sync
/// Realm Database object
realm
rlmTokens
=
[]
cancellables
=
[]
queryIndex
=
{}
// TODO: document
scheduleUIUpdate
/// Starts the local realm database, which is created if it does not exist, sets the api and initializes the sync from them.
/// - Parameter api: api object
constructor
(
api
)
{
// Tell Realm to use this new configuration object for the default Realm
/*#if targetEnvironment(simulator)//TODO
do {
config.fileURL = URL(fileURLWithPath: try getRealmPath())
} catch {
// TODO: Error handling
print("\(error)")
}
#endif*/
Realm
.
Configuration
.
defaultConfiguration
=
config
console
.
log
(
`Starting realm at
${
String
(
Realm
.
Configuration
.
defaultConfiguration
.
fileURL
)}
`
)
//TODO
// TODO: Error handling
this
.
realm
=
new
Realm
()
this
.
podAPI
=
api
// Create scheduler objects
this
.
sync
=
new
Sync
(
this
.
podAPI
,
this
.
realm
)
this
.
sync
.
cache
=
this
}
/// gets default item from database, and adds them to realm
install
()
{
// Load default database from disk
try
{
let
jsonData
=
jsonDataFromFile
(
"
default_database
"
)
let
items
=
MemriJSONDecoder
.
decode
(
DataItemFamily
.
constructor
,
jsonData
)
realmWriteIfAvailable
(
this
.
realm
,
function
()
{
for
(
var
item
of
items
)
{
this
.
realm
.
add
(
item
,
.
modified
)
}
})
}
catch
(
error
)
{
console
.
log
(
`Failed to Install:
${
error
}
`
)
}
}
// TODO: Refactor: don't use async syntax when nothing is async
query
(
datasource
)
{
var
error
var
items
/*query(datasource) {//TODO
error = $0
items = $1
}*/
if
(
error
)
{
throw
error
}
return
items
??
[]
}
/// This function does two things 1) executes a query on the local realm database with given querOptions, and executes callback on the result.
/// 2) calls the syncer with the same datasource to execute the query on the pod.
/// - Parameters:
/// - datasource: datasource for the query, containing datatype(s), filters, sortInstructions etc.
/// - callback: action exectued on the result
query1
(
datasource
,
callback
)
{
// Do nothing when the query is empty. Should not happen.
let
q
=
datasource
.
query
??
""
// Log to a maker user
debugHistory
.
info
(
`Executing query
${
q
}
`
)
if
(
q
==
""
)
{
callback
(
"
Empty Query
"
,
null
)
}
else
{
// Schedule the query to sync from the pod
this
.
sync
.
syncQuery
(
datasource
)
// Parse query
let
[
typeName
,
filter
]
=
this
.
parseQuery
(
q
)
let
type
=
new
DataItemFamily
(
typeName
)
if
(
typeName
==
"
*
"
)
{
var
returnValue
=
[]
for
(
var
dtype
of
DataItemFamily
.
allCases
)
{
// NOTE: Allowed forced cast
let
objects
=
this
.
realm
.
objects
(
dtype
.
getType
())
.
filter
(
"
deleted = false
"
+
(
filter
??
""
))
//TODO
for
(
var
item
of
objects
)
{
returnValue
.
push
(
item
)
}
}
callback
(
null
,
returnValue
)
}
// Fetch the type of the data item
else
if
(
type
)
{
// Get primary key of data item
// let primKey = type.getPrimaryKey()
// Query based on a simple format:
// Query format: <type><space><filter-text>
let
queryType
=
DataItemFamily
.
getType
(
type
)
// let t = queryType() as! Object.Type
var
result
=
this
.
realm
.
objects
(
queryType
())
.
filter
(
"
deleted = false
"
+
(
filter
??
""
))
//TODO
let
sortProperty
=
datasource
.
sortProperty
if
(
sortProperty
&&
sortProperty
!=
""
)
{
result
.
sort
(
//TODO
sortProperty
,
datasource
.
sortAscending
.
value
??
true
)
}
// Construct return array
var
returnValue
=
[]
for
(
var
item
of
result
)
{
if
(
item
instanceof
DataItem
)
{
returnValue
.
push
(
item
)
}
}
// Done
callback
(
null
,
returnValue
)
}
else
{
// Done
callback
(
`Unknown type send by server:
${
q
}
`
,
null
)
}
}
}
/// Parses the query string, which whould be of format \<type\>\<space\>\<filter-text\>
/// - Parameter query: query string
/// - Returns: (type to query, filter to apply)
parseQuery
(
query
)
{
if
(
query
.
indexOf
(
"
"
)
>=
0
)
{
let
splits
=
query
.
split
(
"
"
)
let
type
=
String
(
splits
[
0
])
return
[
type
,
String
(
splits
.
shift
().
join
(
"
"
))]
}
else
{
return
[
query
,
null
]
}
}
getResultSet
(
datasource
)
{
// Create a unique key from query options
let
key
=
datasource
.
uniqueString
// Look for a resultset based on the key
let
resultSet
=
this
.
queryIndex
[
key
]
if
(
resultSet
)
{
// Return found resultset
return
resultSet
}
else
{
// Create new result set
let
resultSet
=
new
ResultSet
(
this
)
// Store resultset in the lookup table
this
.
queryIndex
[
key
]
=
resultSet
// Make sure the new resultset has the right query properties
resultSet
.
datasource
.
query
=
datasource
.
query
resultSet
.
datasource
.
sortProperty
=
datasource
.
sortProperty
resultSet
.
datasource
.
sortAscending
.
value
=
datasource
.
sortAscending
.
value
// Make sure the UI updates when the resultset updates
this
.
cancellables
.
push
(
resultSet
.
objectWillChange
.
sink
(
function
()
{
// TODO: Error handling
this
.
scheduleUIUpdate
(
function
(
context
)
{
//TODO
this
.
context
.
cascadingView
.
resultSet
.
datasource
==
resultSet
.
datasource
})
}.
bind
(
this
)))
return
resultSet
}
}
/// Adding an item to cache consist of 3 phases. 1) When the passed item already exists, it is merged with the existing item in the cache.
/// If it does not exist, this method passes a new "create" action to the SyncState, which will generate a uid for this item. 2) the merged
/// objects ia added to realm 3) We create bindings from the item with the syncstate which will trigger the syncstate to update when
/// the the item changes
/// - Parameter item:DataItem to be added
/// - Throws: Sync conflict exception
/// - Returns: cached dataItem
addToCache
(
item
)
{
try
{
let
newerItem
=
this
.
mergeWithCache
(
item
)
if
(
newerItem
)
{
return
newerItem
}
// Add item to realm
realm
.
write
{
realm
.
add
(
item
,
.
modified
)
}
//TODO
if
(
item
.
syncState
?.
actionNeeded
==
"
create
"
)
{
this
.
sync
.
execute
(
item
)
}
}
catch
{
console
.
log
(
`Could not add to cache:
${
error
}
`
)
}
this
.
bindChangeListeners
(
item
)
return
item
}
mergeWithCache
(
item
)
{
// Check if this is a new item or an existing one
let
syncState
=
item
.
syncState
if
(
syncState
)
{
if
(
item
.
uid
==
0
)
{
// Schedule to be created on the pod
realm
.
write
{
//TODO
syncState
.
actionNeeded
=
"
create
"
realm
.
add
(
AuditItem
(
"
create
"
,
[
item
]))
}
}
else
{
// Fetch item from the cache to double check
let
cachedItem
=
getDataItem
(
item
.
genericType
,
item
.
memriID
)
//TODO
if
(
cachedItem
)
{
// Do nothing when the version is not higher then what we already have
if
(
!
syncState
.
isPartiallyLoaded
&&
item
.
version
<=
cachedItem
.
version
)
{
return
cachedItem
}
// Check if there are local changes
if
(
syncState
.
actionNeeded
!=
""
)
{
// Try to merge without overwriting local changes
if
(
!
item
.
safeMerge
(
cachedItem
))
{
//TODO
// Merging failed
throw
`Exception: Sync conflict with item.memriID
${
cachedItem
.
memriID
}
`
}
}
// If the item is partially loaded, then lets not overwrite the database
if
(
syncState
.
isPartiallyLoaded
)
{
// Merge in the properties from cachedItem that are not already set
item
.
merge
(
cachedItem
,
true
)
//TODO
}
}
}
return
null
}
else
{
console
.
log
(
`Error: no syncstate available during merge`
)
return
null
}
}
// TODO: does this work for subobjects?
bindChangeListeners
(
item
)
{
let
syncState
=
item
.
syncState
if
(
syncState
)
{
// Update the sync state when the item changes
this
.
rlmTokens
.
push
(
item
.
observes
(
function
(
objectChange
)
{
//TODO
let
propChanges
=
objectChange
//TODO
if
(
propChanges
)
{
if
(
syncState
.
actionNeeded
==
""
)
{
function
doAction
()
{
// Mark item for updating
syncState
.
actionNeeded
=
"
update
"
syncState
.
changedInThisSession
=
true
// Record which field was updated
for
(
var
prop
of
propChanges
)
{
if
(
!
syncState
.
updatedFields
.
includes
(
prop
.
name
))
{
syncState
.
updatedFields
.
push
(
prop
.
name
)
}
}
}
realmWriteIfAvailable
(
this
.
realm
)
{
doAction
()
}
//TODO
}
this
.
scheduleUIUpdate
(
null
)
}
}))
// Trigger sync.schedule() when the SyncState changes
// rlmTokens.append(syncState.observe { objectChange in
// if case .change = objectChange {
// if syncState.actionNeeded != "" {
// self.sync.schedule()
// }
// }
// })
}
else
{
console
.
log
(
"
Error, no syncState available for item
"
)
}
}
/// sets delete to true in the syncstate, for an array of items
/// - Parameter item: item to be deleted
/// - Remark: All methods and properties must throw when deleted = true;
delete
(
item
)
{
if
(
!
item
.
deleted
)
{
realmWriteIfAvailable
(
realm
)
{
//TODO
item
.
deleted
=
true
item
.
syncState
?.
actionNeeded
=
"
delete
"
realm
.
add
(
AuditItem
(
"
delete
"
,
[
item
]))
}
}
}
/// sets delete to true in the syncstate, for an array of items
/// - Parameter items: items to be deleted
delete1
(
items
)
{
realmWriteIfAvailable
(
realm
)
{
//TODO
for
(
var
item
in
items
)
{
if
(
!
item
.
deleted
)
{
item
.
deleted
=
true
item
.
setSyncStateActionNeeded
(
"
delete
"
)
realm
.
add
(
AuditItem
(
"
delete
"
,
[
item
]))
}
}
}
}
/// - Parameter item: item to be duplicated
/// - Remark:Does not copy the id property
/// - Returns: copied item
duplicate
(
item
)
{
let
cls
=
item
.
getType
()
if
(
cls
)
{
let
copy
=
item
.
getType
()?.
init
()
if
(
copy
)
{
let
primaryKey
=
cls
.
primaryKey
()
for
(
var
prop
of
item
.
objectSchema
.
properties
)
{
// TODO: allow generation of uid based on number replaces {uid}
// if (item[prop.name] as! String).includes("{uid}")
if
(
prop
.
name
!=
primaryKey
)
{
copy
[
prop
.
name
]
=
item
[
prop
.
name
]
}
}
return
copy
}
}
throw
`Exception: Could not copy
${
item
.
genericType
}
`
}
}
This diff is collapsed.
Click to expand it.
model/DataItem.ts
0 → 100644
+
449
-
0
View file @
4b6e071b
/// DataItem is the baseclass for all of the data clases, all functions
enum
CodingKeys
{
uid
,
memriID
,
deleted
,
starred
,
dateCreated
,
dateModified
,
dateAccessed
,
changelog
,
labels
,
syncState
}
enum
DataItemError
{
cannotMergeItemWithDifferentId
}
class
DataItem
extends
Object
,
Codable
,
Identifiable
,
ObservableObject
{
//TODO
/// name of the DataItem implementation class (E.g. "note" or "person")
genericType
()
{
return
"
unknown
"
}
/// Title computed by implementations of the DataItem class
computedTitle
()
{
return
`
${
genericType
}
[
${
memriID
}
]`
}
test
=
DataItem
.
generateUUID
()
//TODO
/// Boolean whether the DataItem has been deleted
/// uid of the DataItem set by the pod
uid
=
0
/// memriID of the DataItem
memriID
=
DataItem
.
generateUUID
()
//TODO
/// Boolean whether the DataItem has been deleted
deleted
=
false
/// The last version loaded from the server
version
=
0
/// Boolean whether the DataItem has been starred
starred
=
false
/// Creation date of the DataItem
dateCreated
=
new
Date
()
//TODO
/// Last modification date of the DataItem
dateModified
=
new
Date
()
//TODO
/// Last access date of the DataItem
dateAccessed
=
null
/// Array AuditItems describing the log history of the DataItem
changelog
=
[]
//TODO
/// Labels assigned to / associated with this DataItem
labels
=
[]
//TODO
/// Object descirbing syncing information about this object like loading state, versioning, etc.
syncState
=
new
SyncState
()
//TODO
functions
=
{}
/// Primary key used in the realm database of this DataItem
get
primaryKey
()
{
return
"
memriID
"
}
cast
()
{
return
this
}
CodingKeys
=
CodingKeys
//TODO
DataItemError
=
DataItemError
//TODO
constructor
(
decoder
)
{
super
()
this
.
functions
[
"
describeChangelog
"
]
=
function
()
{
let
dateCreated
=
Views
.
formatDate
(
this
.
dateCreated
)
let
views
=
this
.
changelog
.
filter
(
function
(
item
)
{
item
.
action
==
"
read
"
}
).
length
let
edits
=
this
.
changelog
.
filter
(
function
(
item
)
{
item
.
action
==
"
update
"
}
).
length
let
timeSinceCreated
=
Views
.
formatDateSinceCreated
(
this
.
dateCreated
)
return
`You created this
${
this
.
genericType
}
${
dateCreated
}
and viewed it
${
views
}
times and edited it
${
edits
}
times over the past
${
timeSinceCreated
}
`
}.
bind
(
this
)
//TODO
this
.
functions
[
"
computedTitle
"
]
=
function
()
{
return
this
.
computedTitle
()
}.
bind
(
this
)
//TODO
this
.
superDecode
(
decoder
)
}
/// Deserializes DataItem from json decoder
/// - Parameter decoder: Decoder object
/// - Throws: Decoding error
/* public required convenience init(from decoder: Decoder) throws {//TODO
this.init()
try superDecode(from: decoder)
}*/
/// @private
superDecode
(
decoder
)
{
//TODO
this
.
uid
=
decoder
.
decodeIfPresent
(
"
uid
"
)
||
this
.
uid
this
.
memriID
=
decoder
.
decodeIfPresent
(
"
memriID
"
)
||
this
.
memriID
this
.
starred
=
decoder
.
decodeIfPresent
(
"
starred
"
)
||
this
.
starred
this
.
deleted
=
decoder
.
decodeIfPresent
(
"
deleted
"
)
||
this
.
deleted
this
.
version
=
decoder
.
decodeIfPresent
(
"
version
"
)
||
this
.
version
this
.
syncState
=
decoder
.
decodeIfPresent
(
"
syncState
"
)
||
this
.
syncState
this
.
dateCreated
=
decoder
.
decodeIfPresent
(
"
dateCreated
"
)
||
this
.
dateCreated
this
.
dateModified
=
decoder
.
decodeIfPresent
(
"
dateModified
"
)
||
this
.
dateModified
this
.
dateAccessed
=
decoder
.
decodeIfPresent
(
"
dateAccessed
"
)
||
this
.
dateAccessed
this
.
decodeIntoList
(
decoder
,
"
changelog
"
,
this
.
changelog
)
this
.
decodeIntoList
(
decoder
,
"
labels
"
,
this
.
labels
)
}
/// Get string, or string representation (e.g. "true) from property name
/// - Parameter name: property name
/// - Returns: string representation
getString
(
name
)
{
if
(
this
.
objectSchema
[
name
]
==
null
)
{
/*#if DEBUG
print("Warning: getting property that this dataitem doesnt have: \(name) for \(genericType):\(memriID)")
#endif*/
return
""
}
else
{
let
val
=
this
[
name
]
var
typeofVal
=
typeof
val
;
if
(
typeofVal
===
"
string
"
)
{
return
val
}
else
if
(
typeofVal
===
"
boolean
"
)
{
return
String
(
val
)
}
else
if
(
typeofVal
===
"
number
"
)
{
return
String
(
val
)
// } else if let val = val as? Double {
// return String(val)
}
else
if
(
val
instanceof
Date
)
{
//TODO ?
let
formatter
=
new
DateFormatter
()
formatter
.
dateFormat
=
Settings
.
get
(
"
user/formatting/date
"
)
// "HH:mm dd/MM/yyyy"
return
formatter
.
string
(
val
)
}
else
{
return
""
}
}
}
/// Get the type of DataItem
/// - Returns: type of the DataItem
getType
()
{
let
type
=
new
DataItemFamily
(
this
.
genericType
)
if
(
type
)
{
let
T
=
DataItemFamily
.
getType
(
type
)
//TODO
// NOTE: allowed forced downcast
return
(
T
())
}
else
{
console
.
log
(
`Cannot find type
${
genericType
}
in DataItemFamily`
)
return
null
}
}
/// Determines whether item has property
/// - Parameter propName: name of the property
/// - Returns: boolean indicating whether DataItem has the property
hasProperty
(
propName
)
{
if
(
propName
==
"
self
"
)
{
return
true
}
for
(
var
prop
of
this
.
objectSchema
.
properties
)
{
if
(
prop
.
name
==
propName
)
{
return
true
}
let
haystack
=
this
[
prop
.
name
]
if
(
typeof
haystack
===
"
string
"
)
{
if
(
haystack
.
toLowerCase
().
indexOf
(
propName
.
toLowerCase
())
>
-
1
)
{
return
true
}
}
}
return
false
}
/// Get property value
/// - Parameters:
/// - name: property name
get
(
name
,
type
?)
{
if
(
name
==
"
self
"
)
{
return
this
}
return
this
[
name
]
}
/// Set property to value, which will be persisted in the local database
/// - Parameters:
/// - name: property name
/// - value: value
set
(
name
,
value
)
{
realmWriteIfAvailable
(
realm
)
{
//TODO
this
[
name
]
=
value
}
}
addEdge
(
propertyName
,
item
)
{
let
subjectID
=
this
.
get
(
"
memriID
"
)
if
(
!
subjectID
)
return
let
objectID
=
item
.
get
(
"
memriID
"
)
if
(
!
objectID
)
return
let
edges
=
this
.
get
(
propertyName
)
??
[]
if
(
!
edges
.
map
(
function
(
item
)
{
item
.
objectMemriID
}).
includes
(
objectID
))
{
let
newEdge
=
new
Edge
(
subjectID
,
objectID
,
"
Label
"
,
"
Note
"
)
let
newEdges
=
edges
.
concat
([
newEdge
])
this
.
set
(
"
appliesTo
"
,
newEdges
)
}
else
{
throw
"
Could note create Edge, already exists
"
}
// // Check that the property exists to avoid hard crash
// guard let schema = this.objectSchema[propertyName] else {
// throw "Exception: Invalid property access of \(item) for \(self)"
// }
// guard let objectID: String = item.get("memriID") else {
// throw "no memriID"
// }
//
// if schema.isArray {
// // Get list and append
// var list = dataItemListToArray(self[propertyName] as Any)
//
// if !list.map{$0.memriID}.contains(objectID){
// list.append(item)
// print(list)
// this.set(propertyName, list as Any)
// }
// else {
// print("Could not set edge, already exists")
// }
// }
// else {
// this.set(propertyName, item)
// }
}
/// Toggle boolean property
/// - Parameter name: property name
toggle
(
name
)
{
let
val
=
this
[
name
]
if
(
typeof
val
===
"
boolean
"
)
{
this
.
set
(
name
,
val
)
}
else
{
console
.
log
(
`tried to toggle property
${
name
}
, but
${
name
}
is not a boolean`
)
}
}
/// Compares value of this DataItems property with the corresponding property of the passed items property
/// - Parameters:
/// - propName: name of the compared property
/// - item: item to compare against
/// - Returns: boolean indicating whether the property values are the same
isEqualProperty
(
propName
,
item
)
{
let
prop
=
this
.
objectSchema
[
propName
]
if
(
prop
)
{
// List
if
(
prop
.
objectClassName
!=
null
)
{
return
false
// TODO: implement a list compare and a way to add to updatedFields
}
else
{
let
value1
=
this
[
propName
]
let
value2
=
item
[
propName
]
let
item1
=
value1
if
(
typeof
item1
===
"
string
"
&&
typeof
value2
===
"
string
"
)
{
return
item1
==
value2
}
if
(
typeof
item1
===
"
number
"
&&
typeof
value2
===
"
number
"
)
{
return
item1
==
value2
}
// if let item1 = value1 as? Double, let value2 = value2 as? Double {
// return item1 == value2
// }
if
(
typeof
item1
===
"
object
"
&&
typeof
value2
===
"
object
"
)
{
return
item1
==
value2
}
else
{
// TODO: Error handling
console
.
log
(
`Trying to compare property
${
propName
}
of item
${
item
}
and
${
this
}
but types do not mach`
)
}
}
return
true
}
else
{
// TODO: Error handling
console
.
log
(
`Tried to compare property
${
propName
}
, but
${
this
}
does not have that property`
)
return
false
}
}
/// Safely merges the passed item with the current DataItem. When there are merge conflicts, meaning that some other process
/// requested changes for the same properties with different values, merging is not performed.
/// - Parameter item: item to be merged with the current DataItem
/// - Returns: boolean indicating the succes of the merge
safeMerge
(
item
)
{
let
syncState
=
this
.
syncState
if
(
syncState
)
{
// Ignore when marked for deletion
if
(
syncState
.
actionNeeded
==
"
delete
"
)
{
return
true
}
// Do not update when the version is not higher then what we already have
if
(
item
.
version
<=
this
.
version
)
{
return
true
}
// Make sure to not overwrite properties that have been changed
let
updatedFields
=
syncState
.
updatedFields
// Compare all updated properties and make sure they are the same
for
(
var
fieldName
of
updatedFields
)
{
if
(
!
this
.
isEqualProperty
(
fieldName
,
item
))
{
return
false
}
}
// Merge with item
this
.
merge
(
item
)
return
true
}
else
{
// TODO: Error handling
console
.
log
(
"
trying to merge, but syncState is null
"
)
return
false
}
}
/// merges the the passed DataItem in the current item
/// - Parameters:
/// - item: passed DataItem
/// - mergeDefaults: boolean describing how to merge. If mergeDefault == true: Overwrite only the property values have
/// not already been set (null). else: Overwrite all property values with the values from the passed item, with the exception
/// that values cannot be set from a non-null value to null.
merge
(
item
,
mergeDefaults
=
false
)
{
// Store these changes in realm
let
realm
=
this
.
realm
if
(
realm
)
{
try
{
realm
.
write
{
this
.
doMerge
(
item
,
mergeDefaults
)
}
//TODO
}
catch
(
error
)
{
console
.
log
(
`Could not write merge of
${
item
}
and
${
this
}
to realm`
)
}
}
else
{
this
.
doMerge
(
item
,
mergeDefaults
)
}
}
doMerge
(
item
,
mergeDefaults
=
false
)
{
let
properties
=
this
.
objectSchema
.
properties
for
(
var
prop
of
properties
)
{
// Exclude SyncState
if
(
prop
.
name
==
"
SyncState
"
)
{
continue
}
// Perhaps not needed:
// - TODO needs to detect lists which will always be set
// - TODO needs to detect optionals which will always be set
// Overwrite only the property values that are not already set
if
(
mergeDefaults
)
{
if
(
this
[
prop
.
name
]
==
null
)
{
this
[
prop
.
name
]
=
item
[
prop
.
name
]
}
}
// Overwrite all property values with the values from the passed item, with the
// exception, that values cannot be set ot null
else
{
if
(
item
[
prop
.
name
]
!=
null
)
{
this
[
prop
.
name
]
=
item
[
prop
.
name
]
}
}
}
}
/// update the dateAccessed property to the current date
access
()
{
realmWriteIfAvailable
(
realm
)
{
//TODO
this
.
dateAccessed
=
Date
()
}
}
/// compare two dataItems
/// - Parameters:
/// - lhs: DataItem 1
/// - rhs: DataItem 2
/// - Returns: boolean indicating equality
/*public static func == (lhs: DataItem, rhs: DataItem) -> Bool {//TODO
lhs.memriID == rhs.memriID
}*/
/// Generate a new UUID, which are used by swift to identify objects
/// - Returns: UUID string with "0xNEW" prepended
static
generateUUID
()
{
//TODO
return
`Memri
${
UUID
().
uuidString
}
`
}
/// Reads DataItems from file
/// - Parameters:
/// - file: filename (without extension)
/// - ext: extension
/// - Throws: Decoding error
/// - Returns: Array of deserialized DataItems
fromJSONFile
(
file
,
ext
=
"
json
"
)
{
let
jsonData
=
jsonDataFromFile
(
file
,
ext
)
//TODO
let
items
=
MemriJSONDecoder
.
decode
(
DataItemFamily
.
constructor
,
jsonData
)
//TODO
return
items
}
/// Sets syncState .actionNeeded property
/// - Parameters:
/// - action: action name
setSyncStateActionNeeded
(
action
)
{
let
syncState
=
this
.
syncState
if
(
syncState
)
{
syncState
.
actionNeeded
=
action
}
else
{
console
.
log
(
`No syncState available for item
${
self
}
`
)
}
}
/// Read DataItem from string
/// - Parameter json: string to parse
/// - Throws: Decoding error
/// - Returns: Array of deserialized DataItems
fromJSONString
(
json
)
{
let
items
=
MemriJSONDecoder
.
decode
(
DataItemFamily
.
constructor
,
new
Data
(
json
.
utf8
))
//TODO
return
items
}
}
class
Edge
extends
Object
{
objectMemriID
=
DataItem
.
generateUUID
()
//TODO
subjectMemriID
=
DataItem
.
generateUUID
()
//TODO
objectType
=
"
unknown
"
subjectType
=
"
unknown
"
// required init() {}//TODO
constructor
(
subjectMemriID
,
objectMemriID
,
subjectType
=
"
unknown
"
,
objectType
=
"
unknown
"
)
{
super
()
subjectMemriID
=
subjectMemriID
||
DataItem
.
generateUUID
()
objectMemriID
=
objectMemriID
||
DataItem
.
generateUUID
()
this
.
objectMemriID
=
objectMemriID
this
.
subjectMemriID
=
subjectMemriID
this
.
objectType
=
objectType
this
.
subjectType
=
subjectType
}
// maybe we dont need this
// @objc dynamic var objectType:String = DataItem.generateUUID()
// @objc dynamic var subectType:String = DataItem.generateUUID()
/// Deserializes DataItem from json decoder
/// - Parameter decoder: Decoder object
/// - Throws: Decoding error
// required public convenience init(from decoder: Decoder) throws{
// this.init()
// objectUid = try decoder.decodeIfPresent("objectUid") ?? objectUid
// subjectUid = try decoder.decodeIfPresent("subjectUid") ?? subjectUid
// }
}
This diff is collapsed.
Click to expand it.
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment
Menu
Explore
Projects
Groups
Snippets