Skip to content

Commit afd4e41

Browse files
authored
Merge pull request #585 from JuliaRobotics/feature/3Q20/Datablobs_as_nodes
CGDFG blobs as nodes
2 parents c0bc0c7 + 6eb1837 commit afd4e41

File tree

10 files changed

+180
-14
lines changed

10 files changed

+180
-14
lines changed

src/CloudGraphsDFG/CloudGraphsDFG.jl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,17 @@ import ...DistributedFactorGraphs: setSolverParams!,
7676
getEstimateFields,
7777
_getname,
7878
_getmodule,
79-
getTypeFromSerializationModule
79+
getTypeFromSerializationModule,
80+
listBlobStores,
81+
BlobStoreEntry,
82+
AbstractDataEntry,
83+
MongodbDataEntry,
84+
getDataEntries,
85+
listDataEntries,
86+
getDataEntry,
87+
addDataEntry!,
88+
updateDataEntry!,
89+
deleteDataEntry!
8090

8191
using Neo4j
8292
using Base64

src/CloudGraphsDFG/entities/CloudGraphsDFG.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mutable struct CloudGraphsDFG{T <: AbstractParams} <: AbstractDFG{T}
1313
description::String #TODO Maybe remove description
1414
addHistory::Vector{Symbol}
1515
solverParams::T # Solver parameters
16+
blobStores::Dict{Symbol, AbstractBlobStore}
1617

1718
# inner constructor for all constructors in common
1819
function CloudGraphsDFG{T}(neo4jInstance::Neo4jInstance,
@@ -25,7 +26,8 @@ mutable struct CloudGraphsDFG{T <: AbstractParams} <: AbstractDFG{T}
2526
createSessionNodes::Bool=true,
2627
userData::Dict{Symbol, String} = Dict{Symbol, String}(),
2728
robotData::Dict{Symbol, String} = Dict{Symbol, String}(),
28-
sessionData::Dict{Symbol, String} = Dict{Symbol, String}()) where T <: AbstractParams
29+
sessionData::Dict{Symbol, String} = Dict{Symbol, String}(),
30+
blobStores::Dict{Symbol, AbstractBlobStore} = Dict{Symbol, AbstractBlobStore}()) where T <: AbstractParams
2931
# Validate the userId, robotId, and sessionId
3032
!isValidLabel(userId) && error("'$userId' is not a valid User ID")
3133
!isValidLabel(robotId) && error("'$robotId' is not a valid Robot ID")
@@ -35,7 +37,7 @@ mutable struct CloudGraphsDFG{T <: AbstractParams} <: AbstractDFG{T}
3537
# graph = Neo4j.getgraph(neo4jConnection)
3638
# neo4jInstance = Neo4jInstance(neo4jConnection, graph)
3739

38-
dfg = new{T}(neo4jInstance, userId, robotId, sessionId, description, addHistory, solverParams)
40+
dfg = new{T}(neo4jInstance, userId, robotId, sessionId, description, addHistory, solverParams, blobStores)
3941
# Create the session if it doesn't already exist
4042
if createSessionNodes
4143
createDfgSessionIfNotExist(dfg)
@@ -93,7 +95,6 @@ function CloudGraphsDFG(; hostname="localhost",
9395
solverParams::T=NoSolverParams(),
9496
kwargs...) where T <: AbstractParams
9597

96-
@info "Creating $sessionId"
9798
return CloudGraphsDFG{T}(hostname,
9899
port,
99100
username,
@@ -140,4 +141,5 @@ function show(io::IO, ::MIME"text/plain", c::CloudGraphsDFG)
140141
println(io, " - Neo4J instance: $(c.neo4jInstance.connection.host)")
141142
println(io, " - Session: $(c.userId):$(c.robotId):$(c.sessionId)")
142143
println(io, " - Description: ", c.description)
144+
println(io, " - Blob Stores: ", listBlobStores(c))
143145
end

src/CloudGraphsDFG/services/CloudGraphsDFG.jl

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ function updateVariable!(dfg::CloudGraphsDFG, variable::DFGVariable; skipAddErro
175175
!skipAddError && !exist && @warn "Variable label '$(variable.label)' does not exist in the factor graph, adding"
176176

177177
# Create/update the base variable
178-
# NOTE: We are no merging the variable.tags into the labels anymore. We can index by that but not
178+
# NOTE: We are not merging the variable.tags into the labels anymore. We can index by that but not
179179
# going to pollute the graph with unnecessary (and potentially dangerous) labels.
180180
addProps = Dict("softtype" => "\"$(string(typeof(getSofttype(variable))))\"")
181181
query = """
@@ -239,7 +239,10 @@ function getVariable(dfg::CloudGraphsDFG, label::Union{Symbol, String})
239239
for solverKey in listVariableSolverData(dfg, label)
240240
variable.solverDataDict[solverKey] = getVariableSolverData(dfg, label, solverKey)
241241
end
242-
# TODO - data entries
242+
dataDict = getDataEntries(dfg, label)
243+
for (k,v) in dataDict
244+
variable.dataDict[k] = v
245+
end
243246

244247
return variable
245248
end
@@ -254,6 +257,9 @@ function mergeVariableData!(dfg::CloudGraphsDFG, sourceVariable::DFGVariable; cu
254257
for (k,v) in sourceVariable.solverDataDict
255258
updateVariableSolverData!(dfg, getLabel(sourceVariable), v, currentTransaction=currentTransaction)
256259
end
260+
for (k,v) in sourceVariable.dataDict
261+
updateDatEntry!(dfg, getLabel(sourceVariable), v, currentTransaction=currentTransaction)
262+
end
257263
return sourceVariable
258264
end
259265

@@ -632,6 +638,83 @@ function deletePPE!(dfg::CloudGraphsDFG, variablekey::Symbol, ppekey::Symbol=:de
632638
return _unpackPPE(dfg, props)
633639
end
634640

641+
## DataEntry CRUD
642+
643+
function getDataEntries(dfg::CloudGraphsDFG, label::Symbol; currentTransaction::Union{Nothing, Neo4j.Transaction}=nothing)
644+
entries = Dict{Symbol, BlobStoreEntry}()
645+
# TODO: Optimize if necessary.
646+
for key in listDataEntries(dfg, label, currentTransaction=currentTransaction)
647+
entry = getDataEntry(dfg, label, key, currentTransaction=currentTransaction)
648+
entries[entry.label] = entry
649+
end
650+
return entries
651+
end
652+
653+
function listDataEntries(dfg::CloudGraphsDFG, label::Symbol; currentTransaction::Union{Nothing, Neo4j.Transaction}=nothing)
654+
return _listVarSubnodesForType(
655+
dfg,
656+
label,
657+
BlobStoreEntry,
658+
"label";
659+
currentTransaction=currentTransaction)
660+
end
661+
662+
function getDataEntry(dfg::CloudGraphsDFG, label::Symbol, key::Symbol; currentTransaction::Union{Nothing, Neo4j.Transaction}=nothing)
663+
properties = _getVarSubnodeProperties(
664+
dfg, label,
665+
BlobStoreEntry,
666+
key;
667+
currentTransaction=currentTransaction)
668+
return Unmarshal.unmarshal(
669+
BlobStoreEntry,
670+
properties)
671+
end
672+
673+
function addDataEntry!(dfg::CloudGraphsDFG, label::Symbol, bde::BlobStoreEntry; currentTransaction::Union{Nothing, Neo4j.Transaction}=nothing)
674+
if bde.label in listDataEntries(dfg, label, currentTransaction=currentTransaction)
675+
error("Data label '$(bde.label)' already exists")
676+
end
677+
packed = _matchmergeVariableSubnode!(
678+
dfg,
679+
label,
680+
_getLabelsForInst(dfg, bde, parentKey=label),
681+
bde,
682+
:DATA,
683+
currentTransaction=currentTransaction)
684+
return Unmarshal.unmarshal(
685+
BlobStoreEntry,
686+
packed)
687+
end
688+
689+
function updateDataEntry!(dfg::CloudGraphsDFG, label::Symbol, bde::BlobStoreEntry; currentTransaction::Union{Nothing, Neo4j.Transaction}=nothing)
690+
if !(bde.label in listDataEntries(dfg, label, currentTransaction=currentTransaction))
691+
@warn "Data label '$(bde.label)' does not exist, adding"
692+
end
693+
packed = _matchmergeVariableSubnode!(
694+
dfg,
695+
label,
696+
_getLabelsForInst(dfg, bde, parentKey=label),
697+
bde,
698+
:DATA,
699+
currentTransaction=currentTransaction)
700+
return Unmarshal.unmarshal(
701+
BlobStoreEntry,
702+
packed)
703+
end
704+
705+
function deleteDataEntry!(dfg::CloudGraphsDFG, label::Symbol, key::Symbol; currentTransaction::Union{Nothing, Neo4j.Transaction}=nothing)
706+
props = _deleteVarSubnode!(
707+
dfg,
708+
label,
709+
:DATA,
710+
_getLabelsForType(dfg, BlobStoreEntry, parentKey=label),
711+
key,
712+
currentTransaction=currentTransaction)
713+
return Unmarshal.unmarshal(
714+
BlobStoreEntry,
715+
props)
716+
end
717+
635718
## VariableSolverData CRUD
636719

637720
"""

src/CloudGraphsDFG/services/CommonFunctions.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ function _structToNeo4jProps(inst::Union{User, Robot, Session, PVND, N, APPE, AB
147147
if field isa ZonedDateTime
148148
val = "datetime(\"$(string(field))\")"
149149
end
150+
if field isa UUID
151+
val = "\"$(string(field))\""
152+
end
150153
# TODO: Switch this to decorator pattern
151154
if typeof(inst) <: DFGNode
152155
# Variables
@@ -248,7 +251,7 @@ function _getLabelsForInst(dfg::CloudGraphsDFG,
248251
typeof(inst) <: DFGFactor && push!(labels, String(getLabel(inst)))
249252
typeof(inst) <: AbstractPointParametricEst && push!(labels, String(inst.solverKey))
250253
typeof(inst) <: VariableNodeData && push!(labels, String(inst.solverKey))
251-
typeof(inst) <: AbstractDataEntry && push!(labels, String(inst.key))
254+
typeof(inst) <: AbstractDataEntry && push!(labels, String(inst.label))
252255
return labels
253256
end
254257

src/DataBlobs/services/BlobStores.jl

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,13 @@ function addData!(dfg::AbstractDFG, blobstore::AbstractBlobStore, label::Symbol,
7777
return de=>db
7878
end
7979

80-
function updateData!(dfg::AbstractDFG, blobstore::AbstractBlobStore, label::Symbol, entry::AbstractDataEntry, blob::Vector{UInt8})
81-
assertHash(entry, blob, hashfunction=hashfunction)
82-
de = updateDataEntry!(dfg, label, entry)
80+
function updateData!(dfg::AbstractDFG, blobstore::AbstractBlobStore, label::Symbol, entry::AbstractDataEntry, blob::Vector{UInt8}; hashfunction = sha256)
81+
# Recalculate the hash - NOTE Assuming that this is going to be a BlobStoreEntry. TBD.
82+
newEntry = BlobStoreEntry(entry.label, entry.id, blobstore.key, bytes2hex(hashfunction(blob)),
83+
"$(dfg.userId)|$(dfg.robotId)|$(dfg.sessionId)|$(label)",
84+
entry.description, entry.mimeType, entry.createdTimestamp)
85+
86+
de = updateDataEntry!(dfg, label, newEntry)
8387
db = updateDataBlob!(blobstore, de, blob)
8488
return de=>db
8589
end
@@ -156,9 +160,27 @@ function addDataBlob!(store::FolderStore{T}, entry::BlobStoreEntry, data::T) whe
156160
blobfilename = joinpath(store.folder,"$(entry.id).dat")
157161
entryfilename = joinpath(store.folder,"$(entry.id).json")
158162
if isfile(blobfilename)
159-
error("Key '$(id)' blob already exists.")
163+
error("Key '$(entry.id)' blob already exists.")
160164
elseif isfile(entryfilename)
161-
error("Key '$(id)' entry already exists, but no blob.")
165+
error("Key '$(entry.id)' entry already exists, but no blob.")
166+
else
167+
open(blobfilename, "w") do f
168+
write(f, data)
169+
end
170+
open(entryfilename, "w") do f
171+
JSON.print(f, entry)
172+
end
173+
return data
174+
end
175+
end
176+
177+
function updateDataBlob!(store::FolderStore{T}, entry::BlobStoreEntry, data::T) where T
178+
blobfilename = joinpath(store.folder,"$(entry.id).dat")
179+
entryfilename = joinpath(store.folder,"$(entry.id).json")
180+
if !isfile(blobfilename)
181+
@warn "Key '$(entry.id)' doesn't exist."
182+
elseif !isfile(entryfilename)
183+
@warn "Key '$(entry.id)' doesn't exist."
162184
else
163185
open(blobfilename, "w") do f
164186
write(f, data)
@@ -170,6 +192,7 @@ function addDataBlob!(store::FolderStore{T}, entry::BlobStoreEntry, data::T) whe
170192
end
171193
end
172194

195+
173196
function deleteDataBlob!(store::FolderStore{T}, entry::BlobStoreEntry) where T
174197
blobfilename = joinpath(store.folder,"$(entry.id).dat")
175198
entryfilename = joinpath(store.folder,"$(entry.id).json")

src/DataBlobs/services/InMemoryDataEntryBlob.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export InMemoryDataEntry
55
"""
66
$(TYPEDEF)
77
Store data temporary in memory.
8-
NOTE: Neigher Entry nor Blob will be persisted.
8+
NOTE: Neither Entry nor Blob will be persisted.
99
"""
1010
struct InMemoryDataEntry{T} <: AbstractDataEntry
1111
label::Symbol

src/DistributedFactorGraphs.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ export getBlobStore,
5454
addBlobStore!,
5555
updateBlobStore!,
5656
deleteBlobStore!,
57-
emptyBlobStore!
57+
emptyBlobStore!,
58+
listBlobStores
5859

5960
# TODO Not sure these are needed or should work everywhere, implement in cloud?
6061
export updateUserData!, updateRobotData!, updateSessionData!, deleteUserData!, deleteRobotData!, deleteSessionData!

src/services/AbstractDFG.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ addBlobStore!(dfg::AbstractDFG, bs::AbstractBlobStore) = push!(dfg.blobStores, g
196196
updateBlobStore!(dfg::AbstractDFG, bs::AbstractBlobStore) = push!(dfg.blobStores, getKey(bs)=>bs)
197197
deleteBlobStore!(dfg::AbstractDFG, key::Symbol) = pop!(dfg.blobStores, key)
198198
emptyBlobStore!(dfg::AbstractDFG) = empty!(dfg.blobStores)
199+
listBlobStores(dfg::AbstractDFG) = collect(keys(dfg.blobStores))
199200

200201
##==============================================================================
201202
## CRUD Interfaces

test/interfaceTests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ end
118118
else
119119
@test_skip DataEntriesTestBlock!(fg1, var2)
120120
end
121+
122+
# New data blob API
123+
blobsTestBlock!(fg1, var2)
121124
end
122125

123126
@testset "TODO Sorteer groep" begin

test/testBlocks.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,46 @@ function DataEntriesTestBlock!(fg, v2)
867867
@test listDataEntries(v1) == Symbol[]
868868
end
869869

870+
function blobsTestBlock!(fg, v1)
871+
@testset "Data blob tests" begin
872+
# Blobstore functions
873+
fs = FolderStore("/tmp/$(string(uuid4())[1:8])")
874+
# Adding
875+
addBlobStore!(fg, fs)
876+
# Listing
877+
@test listBlobStores(fg) == [fs.key]
878+
# Getting
879+
@test getBlobStore(fg, fs.key) == fs
880+
# Deleting
881+
@test deleteBlobStore!(fg, fs.key) == fs
882+
# Updating
883+
updateBlobStore!(fg, fs)
884+
@test listBlobStores(fg) == [fs.key]
885+
# Emptying
886+
emptyBlobStore!(fg)
887+
@test listBlobStores(fg) == []
888+
# Add it back
889+
addBlobStore!(fg, fs)
890+
891+
# Data functions
892+
testData = rand(UInt8, 50)
893+
# Adding
894+
newData = addData!(fg, fs.key, getLabel(v1), :testing, testData)
895+
# Listing
896+
@test :testing in listDataEntries(fg, getLabel(v1))
897+
# Getting
898+
data = getData(fg, fs, getLabel(v1), :testing)
899+
@test data[1].hash == newData[1].hash
900+
@test data[2] == newData[2]
901+
# Updating
902+
updateData = updateData!(fg, fs, getLabel(v1), newData[1], rand(UInt8, 50))
903+
@test updateData[1].hash != data[1].hash
904+
@test updateData[2] != data[2]
905+
# Deleting
906+
retData = deleteData!(fg, getLabel(v1), :testing)
907+
end
908+
end
909+
870910

871911
function testGroup!(fg, v1, v2, f0, f1)
872912
# "TODO Sorteer groep"

0 commit comments

Comments
 (0)