nutella_lib 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/VERSION +1 -1
- data/lib/nutella_lib/app_core.rb +6 -2
- data/lib/nutella_lib/app_persist.rb +53 -0
- data/lib/nutella_lib/core.rb +5 -0
- data/lib/nutella_lib/persist.rb +38 -67
- data/lib/nutella_lib.rb +1 -0
- data/lib/util/json_file_persisted_collection.rb +71 -0
- data/lib/util/json_file_persisted_hash.rb +80 -0
- data/lib/util/json_store.rb +50 -0
- data/lib/util/mongo_persisted_collection.rb +76 -0
- data/lib/util/mongo_persisted_hash.rb +120 -0
- data/nutella_lib.gemspec +13 -3
- data/test/test_nutella_net_app.rb +0 -2
- data/test/test_peristence.rb +48 -0
- metadata +29 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 89d3a0b4c11ff480c5ddc4cd1194b3d45282554c
|
|
4
|
+
data.tar.gz: 945a65a8305195c62565fa7a0271bfbed976372f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e420ed840733d07fd49022a92804dd3abd452a4ed5c5d41652e81f8efce7fc02557e406eba493ae22a9a37fb125aebeaf3d9ac133933c16f97741b2d085ce3fa
|
|
7
|
+
data.tar.gz: 13d69e009f11b515b67ece3051944d7fcf230084dc9c6759b55245eddf6007c17a4b69f9f5407b0035f928712f21c71e1af0cdb3b9463f45823cb67666e2a6ae
|
data/Gemfile
CHANGED
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.4.
|
|
1
|
+
0.4.3
|
data/lib/nutella_lib/app_core.rb
CHANGED
|
@@ -12,17 +12,21 @@ module Nutella
|
|
|
12
12
|
Nutella.resource_id = nil
|
|
13
13
|
Nutella.mqtt = SimpleMQTTClient.new broker_hostname
|
|
14
14
|
# Fetch the `run_id`s list for this application and subscribe to its updates
|
|
15
|
-
|
|
15
|
+
net.async_request('app_runs_list', lambda { |res| Nutella.app.app_runs_list = res })
|
|
16
16
|
self.net.subscribe('app_runs_list', lambda {|message, _| Nutella.app.app_runs_list = message })
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# Setter/getter for runs_list
|
|
20
20
|
def self.app_runs_list=(val) @app_runs_list=val; end
|
|
21
|
-
def self.app_runs_list
|
|
21
|
+
def self.app_runs_list
|
|
22
|
+
raise 'Nutella has not been initialized: you need to call the proper init method before you can start using nutella' if @app_runs_list.nil?
|
|
23
|
+
@app_runs_list
|
|
24
|
+
end
|
|
22
25
|
|
|
23
26
|
# Accessors for sub-modules
|
|
24
27
|
def self.net; Nutella::App::Net; end
|
|
25
28
|
def self.log; Nutella::App::Log; end
|
|
29
|
+
def self.persist; Nutella::App::Persist; end
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
# Parse command line arguments for app level components
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'util/mongo_persisted_collection'
|
|
2
|
+
require 'util/json_file_persisted_collection'
|
|
3
|
+
require 'util/mongo_persisted_hash'
|
|
4
|
+
require 'util/json_file_persisted_hash'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module Nutella
|
|
9
|
+
|
|
10
|
+
module App
|
|
11
|
+
|
|
12
|
+
# Implements basic app-dependent persistence for app-level components
|
|
13
|
+
module Persist
|
|
14
|
+
|
|
15
|
+
# This method returns a MongoDB-backed store (i.e. persistence)
|
|
16
|
+
# for a collection (i.e. an Array)
|
|
17
|
+
# @param [String] name the name of the store
|
|
18
|
+
# @return [MongoPersistedCollection] a MongoDB-backed collection store
|
|
19
|
+
def self.get_mongo_collection_store( name )
|
|
20
|
+
MongoPersistedCollection.new Nutella.mongo_host, Nutella.app_id, name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# This method returns a MongoDB-backed store (i.e. persistence)
|
|
24
|
+
# for a single object (i.e. an Hash)
|
|
25
|
+
# @param [String] name the name of the store
|
|
26
|
+
# @return [MongoPersistedHash] a MongoDB-backed Hash store
|
|
27
|
+
def self.get_mongo_object_store( name )
|
|
28
|
+
MongoPersistedHash.new Nutella.mongo_host, Nutella.app_id, 'app_persisted_hashes', name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# This method returns a JSON-file-backed store (i.e. persistence)
|
|
32
|
+
# for a collection (i.e. an Array)
|
|
33
|
+
# @param [String] name the name of the store
|
|
34
|
+
# @return [JSONFilePersistedCollection] a JSON-file-backed collection store
|
|
35
|
+
def self.get_json_collection_store( name )
|
|
36
|
+
file_path = "data/#{name}.json"
|
|
37
|
+
FileUtils.mkdir_p 'data/'
|
|
38
|
+
JSONFilePersistedCollection.new file_path
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# This method returns a JSON-file-backed store (i.e. persistence)
|
|
42
|
+
# for a single object (i.e. an Hash)
|
|
43
|
+
# @param [String] name the name of the store
|
|
44
|
+
# @return [JSONFilePersistedHash] a JSON-file-backed Hash store
|
|
45
|
+
def self.get_json_object_store( name )
|
|
46
|
+
file_path = "data/#{name}.json"
|
|
47
|
+
FileUtils.mkdir_p 'data/'
|
|
48
|
+
JSONFilePersistedHash.new file_path
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
data/lib/nutella_lib/core.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Nutella
|
|
|
11
11
|
@run_id = run_id
|
|
12
12
|
@component_id = component_id
|
|
13
13
|
@resource_id = nil
|
|
14
|
+
@mongo_host = broker_hostname
|
|
14
15
|
@mqtt = SimpleMQTTClient.new broker_hostname
|
|
15
16
|
end
|
|
16
17
|
|
|
@@ -23,6 +24,10 @@ module Nutella
|
|
|
23
24
|
raise 'Nutella has not been initialized: you need to call the proper init method before you can start using nutella' if @component_id.nil?
|
|
24
25
|
@component_id
|
|
25
26
|
end
|
|
27
|
+
def self.mongo_host
|
|
28
|
+
raise 'Nutella has not been initialized: you need to call the proper init method before you can start using nutella' if @mongo_host.nil?
|
|
29
|
+
@mongo_host
|
|
30
|
+
end
|
|
26
31
|
def self.mqtt;
|
|
27
32
|
raise 'Nutella has not been initialized: you need to call the proper init method before you can start using nutella' if @mqtt.nil?
|
|
28
33
|
@mqtt
|
data/lib/nutella_lib/persist.rb
CHANGED
|
@@ -1,85 +1,56 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
1
|
+
require 'util/mongo_persisted_collection'
|
|
2
|
+
require 'util/json_file_persisted_collection'
|
|
3
|
+
require 'util/mongo_persisted_hash'
|
|
4
|
+
require 'util/json_file_persisted_hash'
|
|
3
5
|
require 'fileutils'
|
|
4
|
-
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
module Nutella
|
|
7
9
|
|
|
8
|
-
# Implements basic run-dependent persistence for
|
|
10
|
+
# Implements basic run-dependent persistence for run-level components
|
|
9
11
|
module Persist
|
|
10
12
|
|
|
11
|
-
# This
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# JSONStore provides the same functionality as PStore, except it uses JSON
|
|
23
|
-
# to dump objects instead of Marshal.
|
|
24
|
-
# Example use:
|
|
25
|
-
# store = nutella.persist.getJsonStore("json_store/json_test.json")
|
|
26
|
-
# # Write
|
|
27
|
-
# store.transaction { store["key"]="value" }
|
|
28
|
-
# # Read
|
|
29
|
-
# value = store.transaction { store["key"] }
|
|
30
|
-
# puts value # prints "value"
|
|
31
|
-
# # Dump the whole store
|
|
32
|
-
# hash = store.transaction { store.to_h }
|
|
33
|
-
# p hash # prints {"key" => "value"}
|
|
34
|
-
|
|
35
|
-
class JSONStore < PStore
|
|
36
|
-
|
|
37
|
-
def initialize(path)
|
|
38
|
-
super
|
|
39
|
-
@semaphore = Mutex.new
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def dump(table)
|
|
43
|
-
table.to_json
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def load(content)
|
|
47
|
-
JSON.parse(content)
|
|
13
|
+
# This method returns a MongoDB-backed store (i.e. persistence)
|
|
14
|
+
# for a collection (i.e. an Array)
|
|
15
|
+
# @param [String] name the name of the store
|
|
16
|
+
# @return [MongoPersistedCollection] a MongoDB-backed collection store
|
|
17
|
+
def self.get_mongo_collection_store( name )
|
|
18
|
+
collection = "#{Nutella.run_id}/#{name}"
|
|
19
|
+
MongoPersistedCollection.new Nutella.mongo_host, Nutella.app_id, collection
|
|
48
20
|
end
|
|
49
21
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
22
|
+
# This method returns a MongoDB-backed store (i.e. persistence)
|
|
23
|
+
# for a single object (i.e. an Hash)
|
|
24
|
+
# @param [String] name the name of the store
|
|
25
|
+
# @return [MongoPersistedHash] a MongoDB-backed Hash store
|
|
26
|
+
def self.get_mongo_object_store( name )
|
|
27
|
+
doc_id = "#{Nutella.run_id}/#{name}"
|
|
28
|
+
MongoPersistedHash.new Nutella.mongo_host, Nutella.app_id, 'run_persisted_hashes', doc_id
|
|
55
29
|
end
|
|
56
30
|
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
def
|
|
62
|
-
|
|
31
|
+
# This method returns a JSON-file-backed store (i.e. persistence)
|
|
32
|
+
# for a collection (i.e. an Array)
|
|
33
|
+
# @param [String] name the name of the store
|
|
34
|
+
# @return [JSONFilePersistedCollection] a JSON-file-backed collection store
|
|
35
|
+
def self.get_json_collection_store( name )
|
|
36
|
+
dir_path = "data/#{Nutella.run_id}"
|
|
37
|
+
file_path = "data/#{Nutella.run_id}/#{name}.json"
|
|
38
|
+
FileUtils.mkdir_p dir_path
|
|
39
|
+
JSONFilePersistedCollection.new file_path
|
|
63
40
|
end
|
|
64
41
|
|
|
65
|
-
|
|
66
|
-
|
|
42
|
+
# This method returns a JSON-file-backed store (i.e. persistence)
|
|
43
|
+
# for a single object (i.e. an Hash)
|
|
44
|
+
# @param [String] name the name of the store
|
|
45
|
+
# @return [JSONFilePersistedHash] a JSON-file-backed Hash store
|
|
46
|
+
def self.get_json_object_store( name )
|
|
47
|
+
dir_path = "data/#{Nutella.run_id}"
|
|
48
|
+
file_path = "data/#{Nutella.run_id}/#{name}.json"
|
|
49
|
+
FileUtils.mkdir_p dir_path
|
|
50
|
+
JSONFilePersistedHash.new file_path
|
|
67
51
|
end
|
|
68
52
|
|
|
69
53
|
|
|
70
|
-
def marshal_dump_supports_canonical_option?
|
|
71
|
-
false
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
EMPTY_MARSHAL_DATA = {}.to_json
|
|
75
|
-
EMPTY_MARSHAL_CHECKSUM = Digest::MD5.digest(EMPTY_MARSHAL_DATA)
|
|
76
|
-
def empty_marshal_data
|
|
77
|
-
EMPTY_MARSHAL_DATA
|
|
78
|
-
end
|
|
79
|
-
def empty_marshal_checksum
|
|
80
|
-
EMPTY_MARSHAL_CHECKSUM
|
|
81
|
-
end
|
|
82
54
|
end
|
|
83
55
|
|
|
84
|
-
|
|
85
56
|
end
|
data/lib/nutella_lib.rb
CHANGED
|
@@ -11,6 +11,7 @@ require 'nutella_lib/persist'
|
|
|
11
11
|
require 'nutella_lib/app_core'
|
|
12
12
|
require 'nutella_lib/app_net'
|
|
13
13
|
require 'nutella_lib/app_log'
|
|
14
|
+
require 'nutella_lib/app_persist'
|
|
14
15
|
|
|
15
16
|
# NO_EXT gets defined when you require "nutella_lib/noext", which
|
|
16
17
|
# signals that you don't want any extensions.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'util/json_store'
|
|
3
|
+
|
|
4
|
+
# Collection of items that are automatically persisted to a JSON file
|
|
5
|
+
class JSONFilePersistedCollection
|
|
6
|
+
|
|
7
|
+
# Creates a new JSONFilePersistedCollection
|
|
8
|
+
#
|
|
9
|
+
# @param [String] file the JSON file used to persist this collection
|
|
10
|
+
def initialize( file )
|
|
11
|
+
@store = JSONStore.new(file, true)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Pushes (appends) the given hash on to the end of this persisted collection
|
|
16
|
+
#
|
|
17
|
+
# @param [Hash] item the object we want to append
|
|
18
|
+
# @return [JSONFilePersistedCollection] the persisted collection itself, so several appends may be chained together
|
|
19
|
+
def push( item )
|
|
20
|
+
@store.transaction do
|
|
21
|
+
@store['collection'] = Array.new if @store['collection'].nil?
|
|
22
|
+
@store['collection'].push(item)
|
|
23
|
+
end
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Deletes the first element from this persisted collection that is equal to item
|
|
29
|
+
#
|
|
30
|
+
# @param [Hash] item the object we want to delete
|
|
31
|
+
# @return [Hash] the object we just deleted
|
|
32
|
+
def delete( item )
|
|
33
|
+
r = nil
|
|
34
|
+
@store.transaction do
|
|
35
|
+
if @store['collection'].nil?
|
|
36
|
+
r = {}
|
|
37
|
+
@store.abort
|
|
38
|
+
end
|
|
39
|
+
r = @store['collection'].delete_at(@store['collection'].index(item) || @store['collection'].length)
|
|
40
|
+
r = {} if r.nil?
|
|
41
|
+
end
|
|
42
|
+
r
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Replaces the first element from this persisted array that matches item, with replacement
|
|
47
|
+
#
|
|
48
|
+
# @param [Hash] replacement for the current element
|
|
49
|
+
# @return [Hahs] the item that was replaced, {} if the element wasn't in the collection
|
|
50
|
+
def replace( item, replacement )
|
|
51
|
+
r = delete item
|
|
52
|
+
push replacement
|
|
53
|
+
r
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Returns an array representation of this collection
|
|
58
|
+
#
|
|
59
|
+
# @return [Array<Hash>] array representation of this persisted collection
|
|
60
|
+
def to_a
|
|
61
|
+
@store.transaction { @store['collection'].nil? ? {} : @store['collection'] }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns the length of the collection
|
|
65
|
+
#
|
|
66
|
+
# @return [Fixnum] the length of the collection
|
|
67
|
+
def length
|
|
68
|
+
@store.transaction { @store['collection'].nil? ? 0 : @store['collection'].length }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require 'util/json_store'
|
|
2
|
+
|
|
3
|
+
# An hash that is automatically persisted to a JSON file.
|
|
4
|
+
# This class behaves *similarly* to a regular Hash but it persists every operation
|
|
5
|
+
# to a specified JSON file.
|
|
6
|
+
# Not all Hash operations are supported and we added some of our own.
|
|
7
|
+
class JSONFilePersistedHash
|
|
8
|
+
|
|
9
|
+
def initialize( file )
|
|
10
|
+
@store = JSONStore.new(file, true)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Methods borrowed from Ruby's Hash class
|
|
15
|
+
|
|
16
|
+
def []( key )
|
|
17
|
+
@store.transaction { @store[key] }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def []=( key, val )
|
|
21
|
+
@store.transaction { @store[key]=val }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def delete( key )
|
|
25
|
+
@store.transaction { @store.delete key }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def empty?
|
|
29
|
+
@store.transaction { @store.to_h.empty? }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def has_key?( key )
|
|
33
|
+
@store.transaction { @store.to_h.has_key? key }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def include?( key )
|
|
37
|
+
has_key? key
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_s
|
|
41
|
+
@store.transaction { @store.to_h.to_s }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_h
|
|
45
|
+
@store.transaction { @store.to_h }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def keys
|
|
49
|
+
@store.transaction { @store.to_h.keys }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def length
|
|
53
|
+
@store.transaction { @store.to_h.length }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# PersistedHash-only public methods
|
|
58
|
+
|
|
59
|
+
# Adds a <key, value> pair to the PersistedHash _only if_
|
|
60
|
+
# there is currently no value associated with the specified key.
|
|
61
|
+
# @return [Boolean] false if the key already exists, true if the
|
|
62
|
+
# <key, value> pair was added successfully
|
|
63
|
+
def add_key_value?(key, val)
|
|
64
|
+
@store.transaction do
|
|
65
|
+
return false if @store.to_h.key? key
|
|
66
|
+
@store[key]=val
|
|
67
|
+
end
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Removes a <key, value> pair from the PersistedHash _only if_
|
|
72
|
+
# there is currently a value associated with the specified key.
|
|
73
|
+
# @return [Boolean] false if there is no value associated with
|
|
74
|
+
# the specified key, true otherwise
|
|
75
|
+
def delete_key_value?( key )
|
|
76
|
+
@store.transaction { return false if @store.delete(key).nil? }
|
|
77
|
+
true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'pstore'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
# JSONStore provides the same functionality as PStore, except it uses JSON
|
|
6
|
+
# to dump objects instead of Marshal.
|
|
7
|
+
# Example use:
|
|
8
|
+
# store = JSONStore.new("json_store/json_test.json")
|
|
9
|
+
# # Write
|
|
10
|
+
# store.transaction { store["key"]="value" }
|
|
11
|
+
# # Read
|
|
12
|
+
# value = store.transaction { store["key"] }
|
|
13
|
+
# puts value # prints "value"
|
|
14
|
+
# # Dump the whole store
|
|
15
|
+
# hash = store.transaction { store.to_h }
|
|
16
|
+
# p hash # prints {"key" => "value"}
|
|
17
|
+
|
|
18
|
+
class JSONStore < PStore
|
|
19
|
+
|
|
20
|
+
def dump(table)
|
|
21
|
+
table.to_json
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def load(content)
|
|
25
|
+
JSON.parse(content)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Dumps the whole store to hash
|
|
30
|
+
# example:
|
|
31
|
+
# store = JSONStore.new("my_file.json")
|
|
32
|
+
# hash = store.transaction { store.to_h }
|
|
33
|
+
def to_h
|
|
34
|
+
@table
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def marshal_dump_supports_canonical_option?
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
EMPTY_MARSHAL_DATA = {}.to_json
|
|
43
|
+
EMPTY_MARSHAL_CHECKSUM = Digest::MD5.digest(EMPTY_MARSHAL_DATA)
|
|
44
|
+
def empty_marshal_data
|
|
45
|
+
EMPTY_MARSHAL_DATA
|
|
46
|
+
end
|
|
47
|
+
def empty_marshal_checksum
|
|
48
|
+
EMPTY_MARSHAL_CHECKSUM
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'mongo'
|
|
2
|
+
|
|
3
|
+
# Collection of items that are automatically persisted to a MongoDB collection
|
|
4
|
+
class MongoPersistedCollection
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Creates a new MongoPersistedCollection
|
|
8
|
+
#
|
|
9
|
+
# @param [String] hostname of the MongoDB server
|
|
10
|
+
# @param [String] db the database where we want to persist the array
|
|
11
|
+
# @param [String] collection the collection we are using to persist this collection
|
|
12
|
+
def initialize( hostname, db, collection )
|
|
13
|
+
Mongo::Logger.logger.level = ::Logger::INFO
|
|
14
|
+
client = Mongo::Client.new([hostname], :database => db)
|
|
15
|
+
@collection = client[collection]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Pushes (appends) the given hash on to the end of this persisted collection
|
|
20
|
+
#
|
|
21
|
+
# @param [Hash] item the object we want to append
|
|
22
|
+
# @return [MongoPersistedCollection] the persisted collection itself, so several appends may be chained together
|
|
23
|
+
def push( item )
|
|
24
|
+
@collection.insert_one item
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Deletes the first element from this persisted collection that is equal to item
|
|
30
|
+
#
|
|
31
|
+
# @param [Hash] item the object we want to delete
|
|
32
|
+
# @return [Hash] the object we just deleted
|
|
33
|
+
def delete( item )
|
|
34
|
+
from_bson_to_hash @collection.find(item).find_one_and_delete
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Replaces the first element from this persisted array that matches item, with replacement
|
|
39
|
+
#
|
|
40
|
+
# @param [Hash] replacement for the current element
|
|
41
|
+
# @return [Hahs] the item that was replaced, {} if the element wasn't in the collection
|
|
42
|
+
def replace( item, replacement )
|
|
43
|
+
r = delete item
|
|
44
|
+
push replacement
|
|
45
|
+
r
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Returns an array representation of this collection
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<Hash>] array representation of this persisted collection
|
|
52
|
+
def to_a
|
|
53
|
+
ta = Array.new
|
|
54
|
+
@collection.find.each do |doc|
|
|
55
|
+
ta.push from_bson_to_hash(doc)
|
|
56
|
+
end
|
|
57
|
+
ta
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the length of the collection
|
|
61
|
+
#
|
|
62
|
+
# @return [Fixnum] the length of the collection
|
|
63
|
+
def length
|
|
64
|
+
@collection.find.count.to_i
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def from_bson_to_hash( item )
|
|
71
|
+
h = item.to_h
|
|
72
|
+
h.delete '_id'
|
|
73
|
+
h
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require 'mongo'
|
|
2
|
+
require 'pstore'
|
|
3
|
+
|
|
4
|
+
# An hash that is automatically persisted to a MongoDB document.
|
|
5
|
+
# This class behaves *similarly* to a regular Hash but it persists every operation
|
|
6
|
+
# to a specified MongoDB document.
|
|
7
|
+
# Not all Hash operations are supported and we added some of our own.
|
|
8
|
+
class MongoPersistedHash
|
|
9
|
+
|
|
10
|
+
# Creates a new MongoPersistedHash that is persisted as a document
|
|
11
|
+
# with _id +name+ inside a MongoDB collection
|
|
12
|
+
#
|
|
13
|
+
# @param [String] hostname of the MongoDB server
|
|
14
|
+
# @param [String] db the database where we want to persist the array
|
|
15
|
+
# @param [String] collection the collection we are using to persist this collection
|
|
16
|
+
# @param [String] name the _id of the document we are using to persist this Hash
|
|
17
|
+
def initialize( hostname, db, collection, name )
|
|
18
|
+
Mongo::Logger.logger.level = ::Logger::INFO
|
|
19
|
+
client = Mongo::Client.new([hostname], :database => db)
|
|
20
|
+
@collection = client[collection]
|
|
21
|
+
@doc_id = name
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Methods borrowed from Ruby's Hash class
|
|
26
|
+
|
|
27
|
+
def []( key )
|
|
28
|
+
hash = load_hash
|
|
29
|
+
hash[key]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def []=( key, val )
|
|
33
|
+
hash = load_hash
|
|
34
|
+
hash[key]=val
|
|
35
|
+
store_hash hash
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def delete( key )
|
|
39
|
+
hash = load_hash
|
|
40
|
+
return_value = hash.delete key
|
|
41
|
+
store_hash hash
|
|
42
|
+
return_value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def empty?
|
|
46
|
+
hash = load_hash
|
|
47
|
+
hash.empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def has_key?( key )
|
|
51
|
+
hash = load_hash
|
|
52
|
+
hash.has_key? key
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def include?( key )
|
|
56
|
+
has_key? key
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_s
|
|
60
|
+
hash = load_hash
|
|
61
|
+
hash.to_s
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_h
|
|
65
|
+
load_hash
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def keys
|
|
69
|
+
hash = load_hash
|
|
70
|
+
hash.keys
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def length
|
|
74
|
+
hash = load_hash
|
|
75
|
+
hash.length
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# PersistedHash-only public methods
|
|
80
|
+
|
|
81
|
+
# Adds a <key, value> pair to the PersistedHash _only if_
|
|
82
|
+
# there is currently no value associated with the specified key.
|
|
83
|
+
# @return [Boolean] false if the key already exists, true if the
|
|
84
|
+
# <key, value> pair was added successfully
|
|
85
|
+
def add_key_value?(key, val)
|
|
86
|
+
hash = load_hash
|
|
87
|
+
return false if hash.key? key
|
|
88
|
+
hash[key] = val
|
|
89
|
+
store_hash hash
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Removes a <key, value> pair from the PersistedHash _only if_
|
|
94
|
+
# there is currently a value associated with the specified key.
|
|
95
|
+
# @return [Boolean] false if there is no value associated with
|
|
96
|
+
# the specified key, true otherwise
|
|
97
|
+
def delete_key_value?( key )
|
|
98
|
+
hash = load_hash
|
|
99
|
+
return false if hash.delete(key).nil?
|
|
100
|
+
store_hash hash
|
|
101
|
+
true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# private
|
|
106
|
+
|
|
107
|
+
def load_hash
|
|
108
|
+
r = @collection.find({_id: @doc_id}).limit(1).first
|
|
109
|
+
r.nil? ? {_id: @doc_id} : r
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def store_hash(hash)
|
|
114
|
+
@collection.find({_id: @doc_id}).find_one_and_replace(hash, :upsert => :true)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
end
|
data/nutella_lib.gemspec
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
|
4
4
|
# -*- encoding: utf-8 -*-
|
|
5
|
-
# stub: nutella_lib 0.4.
|
|
5
|
+
# stub: nutella_lib 0.4.3 ruby lib
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |s|
|
|
8
8
|
s.name = "nutella_lib"
|
|
9
|
-
s.version = "0.4.
|
|
9
|
+
s.version = "0.4.3"
|
|
10
10
|
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
12
12
|
s.require_paths = ["lib"]
|
|
13
13
|
s.authors = ["Alessandro Gnoli"]
|
|
14
|
-
s.date = "2015-03-
|
|
14
|
+
s.date = "2015-03-26"
|
|
15
15
|
s.description = "Implements the nutella protocol and exposes it natively to ruby developers"
|
|
16
16
|
s.email = "tebemis@gmail.com"
|
|
17
17
|
s.extra_rdoc_files = [
|
|
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
|
|
|
30
30
|
"lib/nutella_lib/app_core.rb",
|
|
31
31
|
"lib/nutella_lib/app_log.rb",
|
|
32
32
|
"lib/nutella_lib/app_net.rb",
|
|
33
|
+
"lib/nutella_lib/app_persist.rb",
|
|
33
34
|
"lib/nutella_lib/core.rb",
|
|
34
35
|
"lib/nutella_lib/ext/kernel.rb",
|
|
35
36
|
"lib/nutella_lib/log.rb",
|
|
@@ -37,11 +38,17 @@ Gem::Specification.new do |s|
|
|
|
37
38
|
"lib/nutella_lib/noext.rb",
|
|
38
39
|
"lib/nutella_lib/persist.rb",
|
|
39
40
|
"lib/simple_mqtt_client/simple_mqtt_client.rb",
|
|
41
|
+
"lib/util/json_file_persisted_collection.rb",
|
|
42
|
+
"lib/util/json_file_persisted_hash.rb",
|
|
43
|
+
"lib/util/json_store.rb",
|
|
44
|
+
"lib/util/mongo_persisted_collection.rb",
|
|
45
|
+
"lib/util/mongo_persisted_hash.rb",
|
|
40
46
|
"nutella_lib.gemspec",
|
|
41
47
|
"test/helper.rb",
|
|
42
48
|
"test/test_logger.rb",
|
|
43
49
|
"test/test_nutella_net.rb",
|
|
44
50
|
"test/test_nutella_net_app.rb",
|
|
51
|
+
"test/test_peristence.rb",
|
|
45
52
|
"test/test_simple_mqtt_client.rb"
|
|
46
53
|
]
|
|
47
54
|
s.homepage = "https://github.com/nutella-framework/nutella_lib.rb"
|
|
@@ -55,6 +62,7 @@ Gem::Specification.new do |s|
|
|
|
55
62
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
56
63
|
s.add_runtime_dependency(%q<mqtt>, [">= 0.3", "~> 0.3"])
|
|
57
64
|
s.add_runtime_dependency(%q<ansi>, [">= 1.4", "~> 1.5"])
|
|
65
|
+
s.add_runtime_dependency(%q<mongo>, [">= 2.0.0.rc", "~> 2.0.0.rc"])
|
|
58
66
|
s.add_development_dependency(%q<shoulda>, [">= 3", "~> 3"])
|
|
59
67
|
s.add_development_dependency(%q<minitest>, [">= 5", "~> 5.4"])
|
|
60
68
|
s.add_development_dependency(%q<yard>, [">= 0.8.7", "~> 0.8"])
|
|
@@ -65,6 +73,7 @@ Gem::Specification.new do |s|
|
|
|
65
73
|
else
|
|
66
74
|
s.add_dependency(%q<mqtt>, [">= 0.3", "~> 0.3"])
|
|
67
75
|
s.add_dependency(%q<ansi>, [">= 1.4", "~> 1.5"])
|
|
76
|
+
s.add_dependency(%q<mongo>, [">= 2.0.0.rc", "~> 2.0.0.rc"])
|
|
68
77
|
s.add_dependency(%q<shoulda>, [">= 3", "~> 3"])
|
|
69
78
|
s.add_dependency(%q<minitest>, [">= 5", "~> 5.4"])
|
|
70
79
|
s.add_dependency(%q<yard>, [">= 0.8.7", "~> 0.8"])
|
|
@@ -76,6 +85,7 @@ Gem::Specification.new do |s|
|
|
|
76
85
|
else
|
|
77
86
|
s.add_dependency(%q<mqtt>, [">= 0.3", "~> 0.3"])
|
|
78
87
|
s.add_dependency(%q<ansi>, [">= 1.4", "~> 1.5"])
|
|
88
|
+
s.add_dependency(%q<mongo>, [">= 2.0.0.rc", "~> 2.0.0.rc"])
|
|
79
89
|
s.add_dependency(%q<shoulda>, [">= 3", "~> 3"])
|
|
80
90
|
s.add_dependency(%q<minitest>, [">= 5", "~> 5.4"])
|
|
81
91
|
s.add_dependency(%q<yard>, [">= 0.8.7", "~> 0.8"])
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
|
|
3
|
+
require 'util/mongo_persisted_collection'
|
|
4
|
+
require 'util/json_file_persisted_collection'
|
|
5
|
+
require 'util/mongo_persisted_hash'
|
|
6
|
+
require 'util/json_file_persisted_hash'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestNutellaNetApp < MiniTest::Test
|
|
10
|
+
|
|
11
|
+
def test_mongo_persisted_collection
|
|
12
|
+
# pa = MongoPersistedCollection.new 'ltg.evl.uic.edu', 'nutella_test', 'test_collection'
|
|
13
|
+
# pa.push( {an: 'object'} )
|
|
14
|
+
# pa.push( {another: 'object'} )
|
|
15
|
+
# pa.push( {idx_2: 'object'} )
|
|
16
|
+
# pa.push( {idx_3: 'object'} )
|
|
17
|
+
# pa.push( {idx_4: 'object'} )
|
|
18
|
+
# p pa.replace( {ciccio: 'pasticcio'}, {ciccio: 'pasticcio'} )
|
|
19
|
+
# p pa.length
|
|
20
|
+
# p pa.to_a
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_json_file_persisted_collection
|
|
25
|
+
# pc = JSONFilePersistedCollection.new 'test.json'
|
|
26
|
+
# # pc.push({an: 'object'}).push({another: 'object'})
|
|
27
|
+
# p pc.length
|
|
28
|
+
# p pc.to_a
|
|
29
|
+
# p pc.replace({'an' => 'object'}, {'with_another' => 'object'})
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_json_file_persisted_hash
|
|
34
|
+
# ph = JSONFilePersistedHash.new 'test.json'
|
|
35
|
+
# p ph['test']
|
|
36
|
+
# p ph['test'] = 'yes'
|
|
37
|
+
# p ph.delete_key_value? 'test'
|
|
38
|
+
# p ph.add_key_value? 'test', 'pippo'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_mongo_persisted_hash
|
|
43
|
+
# ph = MongoPersistedHash.new 'ltg.evl.uic.edu', 'nutella_test', 'hash_test', 'my_hash'
|
|
44
|
+
# ph['ciao'] = 'bello'
|
|
45
|
+
# p ph.to_h
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nutella_lib
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alessandro Gnoli
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2015-03-
|
|
11
|
+
date: 2015-03-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: mqtt
|
|
@@ -50,6 +50,26 @@ dependencies:
|
|
|
50
50
|
- - "~>"
|
|
51
51
|
- !ruby/object:Gem::Version
|
|
52
52
|
version: '1.5'
|
|
53
|
+
- !ruby/object:Gem::Dependency
|
|
54
|
+
name: mongo
|
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: 2.0.0.rc
|
|
60
|
+
- - "~>"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: 2.0.0.rc
|
|
63
|
+
type: :runtime
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: 2.0.0.rc
|
|
70
|
+
- - "~>"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: 2.0.0.rc
|
|
53
73
|
- !ruby/object:Gem::Dependency
|
|
54
74
|
name: shoulda
|
|
55
75
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -209,6 +229,7 @@ files:
|
|
|
209
229
|
- lib/nutella_lib/app_core.rb
|
|
210
230
|
- lib/nutella_lib/app_log.rb
|
|
211
231
|
- lib/nutella_lib/app_net.rb
|
|
232
|
+
- lib/nutella_lib/app_persist.rb
|
|
212
233
|
- lib/nutella_lib/core.rb
|
|
213
234
|
- lib/nutella_lib/ext/kernel.rb
|
|
214
235
|
- lib/nutella_lib/log.rb
|
|
@@ -216,11 +237,17 @@ files:
|
|
|
216
237
|
- lib/nutella_lib/noext.rb
|
|
217
238
|
- lib/nutella_lib/persist.rb
|
|
218
239
|
- lib/simple_mqtt_client/simple_mqtt_client.rb
|
|
240
|
+
- lib/util/json_file_persisted_collection.rb
|
|
241
|
+
- lib/util/json_file_persisted_hash.rb
|
|
242
|
+
- lib/util/json_store.rb
|
|
243
|
+
- lib/util/mongo_persisted_collection.rb
|
|
244
|
+
- lib/util/mongo_persisted_hash.rb
|
|
219
245
|
- nutella_lib.gemspec
|
|
220
246
|
- test/helper.rb
|
|
221
247
|
- test/test_logger.rb
|
|
222
248
|
- test/test_nutella_net.rb
|
|
223
249
|
- test/test_nutella_net_app.rb
|
|
250
|
+
- test/test_peristence.rb
|
|
224
251
|
- test/test_simple_mqtt_client.rb
|
|
225
252
|
homepage: https://github.com/nutella-framework/nutella_lib.rb
|
|
226
253
|
licenses:
|