MrMurano 1.0.0
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 +7 -0
- data/.gitignore +30 -0
- data/Gemfile +9 -0
- data/MrMurano.gemspec +35 -0
- data/README.markdown +27 -0
- data/Rakefile +25 -0
- data/TODO.taskpaper +45 -0
- data/bin/mr +66 -0
- data/lib/MrMurano/Account.rb +176 -0
- data/lib/MrMurano/Solution-Endpoint.rb +107 -0
- data/lib/MrMurano/Solution-File.rb +137 -0
- data/lib/MrMurano/Solution-ServiceConfig.rb +86 -0
- data/lib/MrMurano/Solution-Services.rb +163 -0
- data/lib/MrMurano/Solution-Users.rb +123 -0
- data/lib/MrMurano/Solution.rb +318 -0
- data/lib/MrMurano/configFile.rb +194 -0
- data/lib/MrMurano/hash.rb +18 -0
- data/lib/MrMurano/shelledCommand.rb +43 -0
- data/lib/MrMurano/status.rb +106 -0
- data/lib/MrMurano/sync.rb +132 -0
- data/lib/MrMurano/version.rb +4 -0
- data/lib/MrMurano.rb +13 -0
- metadata +166 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
|
3
|
+
module MrMurano
|
4
|
+
# …/serviceconfig
|
5
|
+
class ServiceConfig < SolutionBase
|
6
|
+
def initialize
|
7
|
+
super
|
8
|
+
@uriparts << 'serviceconfig'
|
9
|
+
end
|
10
|
+
|
11
|
+
def list
|
12
|
+
get()['items']
|
13
|
+
end
|
14
|
+
def fetch(id)
|
15
|
+
get('/' + id.to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def assignTriggers(products)
|
20
|
+
scr = list().select{|i| i['service'] == 'device' or i[:service] == 'device'}.first
|
21
|
+
scid = scr['id'] or scr[:id]
|
22
|
+
|
23
|
+
details = Hash.transform_keys_to_symbols(fetch(scid))
|
24
|
+
products = [products] unless products.kind_of? Array
|
25
|
+
details[:triggers] = {:pid=>products}
|
26
|
+
|
27
|
+
put('/'+scid, details)
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def showTriggers
|
32
|
+
scr = list().select{|i| i['service'] == 'device' or i[:service] == 'device'}.first
|
33
|
+
scid = scr['id'] or scr[:id]
|
34
|
+
|
35
|
+
details = Hash.transform_keys_to_symbols(fetch(scid))
|
36
|
+
|
37
|
+
return [] if details[:triggers].nil?
|
38
|
+
details[:triggers][:pid]
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
command :assign do |c|
|
45
|
+
c.syntax = 'mr assign [product]'
|
46
|
+
c.description = 'Assign a product to a eventhandler'
|
47
|
+
|
48
|
+
c.option '--list', %{List assigned products}
|
49
|
+
c.option '--idonly', 'Only return the ids'
|
50
|
+
|
51
|
+
c.action do |args, options|
|
52
|
+
sol = MrMurano::ServiceConfig.new
|
53
|
+
|
54
|
+
if options.list then
|
55
|
+
trigs = sol.showTriggers()
|
56
|
+
if options.idonly then
|
57
|
+
say trigs.join(' ')
|
58
|
+
else
|
59
|
+
acc = MrMurano::Account.new
|
60
|
+
products = acc.products.map{|p| Hash.transform_keys_to_symbols(p)}
|
61
|
+
products.select!{|p| trigs.include? p[:pid] }
|
62
|
+
busy = products.map{|r| [r[:label], r[:type], r[:pid], r[:modelId]]}
|
63
|
+
table = Terminal::Table.new :rows => busy, :headings => ['Label', 'Type', 'PID', 'ModelID']
|
64
|
+
say table
|
65
|
+
end
|
66
|
+
|
67
|
+
else
|
68
|
+
prname = args.shift
|
69
|
+
if prname.nil? then
|
70
|
+
prid = $cfg['product.id']
|
71
|
+
else
|
72
|
+
acc = MrMurano::Account.new
|
73
|
+
products = acc.products.map{|p| Hash.transform_keys_to_symbols(p)}
|
74
|
+
products.select!{|p| p[:label] == prname or p[:pid] == prname }
|
75
|
+
prid = products.map{|p| p[:pid]}
|
76
|
+
end
|
77
|
+
raise "No product ID!" if prid.nil?
|
78
|
+
say "Assigning #{prid} to solution" if $cfg['tool.verbose']
|
79
|
+
sol.assignTriggers(prid) unless $cfg['tool.dry']
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# vim: set ai et sw=2 ts=2 :
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
module MrMurano
|
7
|
+
##
|
8
|
+
# Things that servers do that is common.
|
9
|
+
class ServiceBase < SolutionBase
|
10
|
+
# not quite sure why this is needed, but…
|
11
|
+
def mkalias(name)
|
12
|
+
case name
|
13
|
+
when String
|
14
|
+
"/#{$cfg['solution.id']}_#{name}"
|
15
|
+
when Hash
|
16
|
+
if name.has_key? :name then
|
17
|
+
"/#{$cfg['solution.id']}_#{name[:name]}"
|
18
|
+
elsif name.has_key? :service and name.has_key? :event then
|
19
|
+
"/#{$cfg['solution.id']}_#{name[:service]}_#{name[:event]}"
|
20
|
+
else
|
21
|
+
raise "unknown keys. #{name}"
|
22
|
+
end
|
23
|
+
else
|
24
|
+
raise "unknown type. #{name}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def list
|
29
|
+
ret = get()
|
30
|
+
ret['items']
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch(name)
|
34
|
+
ret = get('/'+name)
|
35
|
+
if block_given? then
|
36
|
+
yield ret['script']
|
37
|
+
else
|
38
|
+
ret['script']
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# ??? remove
|
43
|
+
def remove(name)
|
44
|
+
delete('/'+name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def upload(local, remote)
|
48
|
+
local = Pathname.new(local) unless local.kind_of? Pathname
|
49
|
+
raise "no file" unless local.exist?
|
50
|
+
|
51
|
+
# we assume these are small enough to slurp.
|
52
|
+
script = local.read
|
53
|
+
|
54
|
+
pst = remote.merge ({
|
55
|
+
:solution_id => $cfg['solution.id'],
|
56
|
+
:script => script
|
57
|
+
})
|
58
|
+
|
59
|
+
# try put, if 404, then post.
|
60
|
+
put(mkalias(remote), pst) do |request, http|
|
61
|
+
response = http.request(request)
|
62
|
+
case response
|
63
|
+
when Net::HTTPSuccess
|
64
|
+
#return JSON.parse(response.body)
|
65
|
+
when Net::HTTPNotFound
|
66
|
+
verbose "Doesn't exist, creating"
|
67
|
+
post('/', pst)
|
68
|
+
else
|
69
|
+
say_error "got #{response} from #{request} #{request.uri.to_s}"
|
70
|
+
say_error ":: #{response.body}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def docmp(itemA, itemB)
|
76
|
+
if itemA[:updated_at].nil? and itemA[:local_path] then
|
77
|
+
itemA[:updated_at] = itemA[:local_path].mtime.getutc
|
78
|
+
elsif itemA[:updated_at].kind_of? String then
|
79
|
+
itemA[:updated_at] = DateTime.parse(itemA[:updated_at]).to_time.getutc
|
80
|
+
end
|
81
|
+
if itemB[:updated_at].nil? and itemB[:local_path] then
|
82
|
+
itemB[:updated_at] = itemB[:local_path].mtime.getutc
|
83
|
+
elsif itemB[:updated_at].kind_of? String then
|
84
|
+
itemB[:updated_at] = DateTime.parse(itemB[:updated_at]).to_time.getutc
|
85
|
+
end
|
86
|
+
return itemA[:updated_at] != itemB[:updated_at]
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
# …/library
|
92
|
+
class Library < ServiceBase
|
93
|
+
def initialize
|
94
|
+
super
|
95
|
+
@uriparts << 'library'
|
96
|
+
@itemkey = :alias
|
97
|
+
end
|
98
|
+
|
99
|
+
def tolocalname(item, key)
|
100
|
+
name = item[:name]
|
101
|
+
"#{name}.lua"
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def toremotename(from, path)
|
106
|
+
name = path.basename.to_s.sub(/\..*/, '')
|
107
|
+
{:name => name}
|
108
|
+
end
|
109
|
+
|
110
|
+
def synckey(item)
|
111
|
+
item[:name]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# …/eventhandler
|
116
|
+
class EventHandler < ServiceBase
|
117
|
+
def initialize
|
118
|
+
super
|
119
|
+
@uriparts << 'eventhandler'
|
120
|
+
@itemkey = :alias
|
121
|
+
end
|
122
|
+
|
123
|
+
def list
|
124
|
+
ret = get()
|
125
|
+
skiplist = ($cfg['eventhandler.skiplist'] or '').split
|
126
|
+
ret['items'].reject{|i| i.has_key?('service') and skiplist.include? i['service'] }
|
127
|
+
end
|
128
|
+
|
129
|
+
def fetch(name)
|
130
|
+
ret = get('/'+name)
|
131
|
+
aheader = (ret['script'].lines.first or "").chomp
|
132
|
+
dheader = "--#EVENT #{ret['service']} #{ret['event']}"
|
133
|
+
if block_given? then
|
134
|
+
yield dheader + "\n" if aheader != dheader
|
135
|
+
yield ret['script']
|
136
|
+
else
|
137
|
+
res = ''
|
138
|
+
res << dheader + "\n" if aheader != dheader
|
139
|
+
res << ret['script']
|
140
|
+
res
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def tolocalname(item, key)
|
145
|
+
"#{item[:name]}.lua"
|
146
|
+
end
|
147
|
+
|
148
|
+
def toremotename(from, path)
|
149
|
+
path = Pathname.new(path) unless path.kind_of? Pathname
|
150
|
+
aheader = path.readlines().first
|
151
|
+
md = /--#EVENT (\S+) (\S+)/.match(aheader)
|
152
|
+
raise "Not an Event handler: #{path.to_s}" if md.nil?
|
153
|
+
{:service=>md[1], :event=>md[2]}
|
154
|
+
end
|
155
|
+
|
156
|
+
def synckey(item)
|
157
|
+
"#{item[:service]}_#{item[:event]}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# How do we enable product.id to flow into the eventhandler?
|
162
|
+
end
|
163
|
+
# vim: set ai et sw=2 ts=2 :
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
require 'yaml'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
module MrMurano
|
8
|
+
##
|
9
|
+
# User Management common things
|
10
|
+
class UserBase < SolutionBase
|
11
|
+
def list()
|
12
|
+
get()
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch(id)
|
16
|
+
get('/' + id.to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
def remove(id)
|
20
|
+
delete('/' + id.to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
def upload(local, remote)
|
24
|
+
# Roles cannot be modified, so must delete and post.
|
25
|
+
delete('/' + remote[@itemkey]) do |request, http|
|
26
|
+
response = http.request(request)
|
27
|
+
case response
|
28
|
+
when Net::HTTPSuccess
|
29
|
+
when Net::HTTPNotFound
|
30
|
+
else
|
31
|
+
say_error "got #{response} from #{request} #{request.uri.to_s}"
|
32
|
+
say_error ":: #{response.body}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
post('/', remote)
|
36
|
+
end
|
37
|
+
|
38
|
+
def download(local, item)
|
39
|
+
# needs to append/merge with file
|
40
|
+
# for now, we'll read, modify, write
|
41
|
+
here = []
|
42
|
+
if local.exist? then
|
43
|
+
local.open('rb') {|io| here = YAML.load(io)}
|
44
|
+
here = [] if here == false
|
45
|
+
end
|
46
|
+
here.delete_if do |i|
|
47
|
+
Hash.transform_keys_to_symbols(i)[@itemkey] == item[@itemkey]
|
48
|
+
end
|
49
|
+
here << item
|
50
|
+
local.open('wb') do |io|
|
51
|
+
io.write here.map{|i| Hash.transform_keys_to_strings(i)}.to_yaml
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def removelocal(dest, item)
|
56
|
+
# needs to append/merge with file
|
57
|
+
# for now, we'll read, modify, write
|
58
|
+
here = []
|
59
|
+
if local.exist? then
|
60
|
+
local.open('rb') {|io| here = YAML.load(io)}
|
61
|
+
here = [] if here == false
|
62
|
+
end
|
63
|
+
key = @itemkey.to_sym
|
64
|
+
here.delete_if do |it|
|
65
|
+
Hash.transform_keys_to_symbols(it)[key] == item[key]
|
66
|
+
end
|
67
|
+
local.open('wb') do|io|
|
68
|
+
io.write here.map{|i| Hash.transform_keys_to_strings(i)}.to_yaml
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def tolocalpath(into, item)
|
73
|
+
into
|
74
|
+
end
|
75
|
+
|
76
|
+
def locallist(from)
|
77
|
+
from = Pathname.new(from) unless from.kind_of? Pathname
|
78
|
+
if not from.exist? then
|
79
|
+
say_warning "Skipping missing #{from.to_s}"
|
80
|
+
return []
|
81
|
+
end
|
82
|
+
unless from.file? then
|
83
|
+
say_warning "Cannot read from #{from.to_s}"
|
84
|
+
return []
|
85
|
+
end
|
86
|
+
key = @itemkey.to_sym
|
87
|
+
|
88
|
+
here = []
|
89
|
+
from.open {|io| here = YAML.load(io) }
|
90
|
+
here = [] if here == false
|
91
|
+
|
92
|
+
here
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# …/role
|
97
|
+
class Role < UserBase
|
98
|
+
def initialize
|
99
|
+
super
|
100
|
+
@uriparts << 'role'
|
101
|
+
@itemkey = :role_id
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# …/user
|
106
|
+
class User < UserBase
|
107
|
+
def initialize
|
108
|
+
super
|
109
|
+
@uriparts << 'user'
|
110
|
+
end
|
111
|
+
|
112
|
+
def upload(local, remote)
|
113
|
+
# TODO figure out APIs for updating users.
|
114
|
+
say_warning "Updating Users isn't working currently."
|
115
|
+
# post does work if the :password field is set.
|
116
|
+
end
|
117
|
+
|
118
|
+
def synckey(item)
|
119
|
+
item[:email]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
# vim: set ai et sw=2 ts=2 :
|
@@ -0,0 +1,318 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
module MrMurano
|
7
|
+
class SolutionBase
|
8
|
+
# This might also be a valid ProductBase.
|
9
|
+
def initialize
|
10
|
+
@token = Account.new.token
|
11
|
+
@sid = $cfg['solution.id']
|
12
|
+
raise "No solution!" if @sid.nil?
|
13
|
+
@uriparts = [:solution, @sid]
|
14
|
+
@itemkey = :id
|
15
|
+
end
|
16
|
+
|
17
|
+
def verbose(msg)
|
18
|
+
if $cfg['tool.verbose'] then
|
19
|
+
say msg
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def endPoint(path='')
|
24
|
+
parts = ['https:/', $cfg['net.host'], 'api:1'] + @uriparts
|
25
|
+
s = parts.map{|v| v.to_s}.join('/')
|
26
|
+
URI(s + path.to_s)
|
27
|
+
end
|
28
|
+
def http
|
29
|
+
uri = URI('https://' + $cfg['net.host'])
|
30
|
+
if @http.nil? then
|
31
|
+
@http = Net::HTTP.new(uri.host, uri.port)
|
32
|
+
@http.use_ssl = true
|
33
|
+
@http.start
|
34
|
+
end
|
35
|
+
@http
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_req_defaults(request)
|
39
|
+
request.content_type = 'application/json'
|
40
|
+
request['authorization'] = 'token ' + @token
|
41
|
+
request['User-Agent'] = "MrMurano/#{MrMurano::VERSION}"
|
42
|
+
request
|
43
|
+
end
|
44
|
+
|
45
|
+
def workit(request, &block)
|
46
|
+
set_req_defaults(request)
|
47
|
+
if block_given? then
|
48
|
+
yield request, http()
|
49
|
+
else
|
50
|
+
response = http().request(request)
|
51
|
+
case response
|
52
|
+
when Net::HTTPSuccess
|
53
|
+
return {} if response.body.nil?
|
54
|
+
begin
|
55
|
+
return JSON.parse(response.body)
|
56
|
+
rescue
|
57
|
+
return response.body
|
58
|
+
end
|
59
|
+
else
|
60
|
+
say_error "got #{response} from #{request} #{request.uri.to_s}"
|
61
|
+
say_error ":: #{response.body}"
|
62
|
+
raise response
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def get(path='', &block)
|
68
|
+
uri = endPoint(path)
|
69
|
+
workit(Net::HTTP::Get.new(uri), &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
def post(path='', body={}, &block)
|
73
|
+
uri = endPoint(path)
|
74
|
+
req = Net::HTTP::Post.new(uri)
|
75
|
+
req.body = JSON.generate(body)
|
76
|
+
workit(req, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def put(path='', body={}, &block)
|
80
|
+
uri = endPoint(path)
|
81
|
+
req = Net::HTTP::Put.new(uri)
|
82
|
+
req.body = JSON.generate(body)
|
83
|
+
workit(req, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def delete(path='', &block)
|
87
|
+
uri = endPoint(path)
|
88
|
+
workit(Net::HTTP::Delete.new(uri), &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
# …
|
92
|
+
|
93
|
+
def toremotename(root, path)
|
94
|
+
path = Pathname.new(path) unless path.kind_of? Pathname
|
95
|
+
root = Pathname.new(root) unless root.kind_of? Pathname
|
96
|
+
path.relative_path_from(root).to_s
|
97
|
+
end
|
98
|
+
def tolocalpath(into, item)
|
99
|
+
into.mkpath unless $cfg['tool.dry']
|
100
|
+
return item[:local_path] if item.has_key? :local_path
|
101
|
+
itemkey = @itemkey.to_sym
|
102
|
+
name = tolocalname(item, itemkey)
|
103
|
+
raise "Bad key(#{itemkey}) for #{item}" if name.nil?
|
104
|
+
dest = into + name
|
105
|
+
end
|
106
|
+
|
107
|
+
def locallist(from)
|
108
|
+
from = Pathname.new(from) unless from.kind_of? Pathname
|
109
|
+
unless from.exist? then
|
110
|
+
return []
|
111
|
+
end
|
112
|
+
raise "Not a directory: #{from.to_s}" unless from.directory?
|
113
|
+
|
114
|
+
Pathname.glob(from.to_s + '/**/*').map do |path|
|
115
|
+
name = toremotename(from, path)
|
116
|
+
case name
|
117
|
+
when Hash
|
118
|
+
name[:local_path] = path
|
119
|
+
name
|
120
|
+
else
|
121
|
+
{:local_path => path, :name => name}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def synckey(item)
|
127
|
+
key = @itemkey.to_sym
|
128
|
+
item[key]
|
129
|
+
end
|
130
|
+
|
131
|
+
def syncup(from, options={})
|
132
|
+
there = list()
|
133
|
+
here = locallist(from)
|
134
|
+
itemkey = @itemkey.to_sym
|
135
|
+
|
136
|
+
# split into three lists.
|
137
|
+
# - Items here and not there. (toadd)
|
138
|
+
# - Items there and not here. (todel)
|
139
|
+
# - Items here and there. (tomod)
|
140
|
+
therebox = {}
|
141
|
+
there.each do |item|
|
142
|
+
item = Hash.transform_keys_to_symbols(item)
|
143
|
+
therebox[ synckey(item) ] = item
|
144
|
+
end
|
145
|
+
herebox = {}
|
146
|
+
here.each do |item|
|
147
|
+
item = Hash.transform_keys_to_symbols(item)
|
148
|
+
herebox[ synckey(item) ] = item
|
149
|
+
end
|
150
|
+
toadd = herebox.keys - therebox.keys
|
151
|
+
todel = therebox.keys - herebox.keys
|
152
|
+
tomod = herebox.keys & therebox.keys
|
153
|
+
|
154
|
+
if options.delete then
|
155
|
+
todel.each do |key|
|
156
|
+
verbose "Removing item #{key}"
|
157
|
+
unless $cfg['tool.dry'] then
|
158
|
+
item = therebox[key]
|
159
|
+
remove(item[itemkey])
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
if options.create then
|
164
|
+
toadd.each do |key|
|
165
|
+
verbose "Adding item #{key}"
|
166
|
+
unless $cfg['tool.dry'] then
|
167
|
+
item = herebox[key]
|
168
|
+
upload(item[:local_path], item.reject{|k,v| k==:local_path})
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
if options.update then
|
173
|
+
tomod.each do |key|
|
174
|
+
verbose "Updating item #{key}"
|
175
|
+
unless $cfg['tool.dry'] then
|
176
|
+
#item = therebox[key].merge herebox[key] # need to be consistent with key types for this to work
|
177
|
+
id = therebox[key][itemkey]
|
178
|
+
item = herebox[key].dup
|
179
|
+
item[itemkey] = id
|
180
|
+
upload(item[:local_path], item.reject{|k,v| k==:local_path})
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def syncdown(into, options={})
|
187
|
+
there = list()
|
188
|
+
into = Pathname.new(into) unless into.kind_of? Pathname
|
189
|
+
here = locallist(into)
|
190
|
+
itemkey = @itemkey.to_sym
|
191
|
+
|
192
|
+
# split into three lists.
|
193
|
+
# - Items here and not there. (todel)
|
194
|
+
# - Items there and not here. (toadd)
|
195
|
+
# - Items here and there. (tomod)
|
196
|
+
therebox = {}
|
197
|
+
there.each do |item|
|
198
|
+
item = Hash.transform_keys_to_symbols(item)
|
199
|
+
therebox[ synckey(item) ] = item
|
200
|
+
end
|
201
|
+
herebox = {}
|
202
|
+
here.each do |item|
|
203
|
+
item = Hash.transform_keys_to_symbols(item)
|
204
|
+
herebox[ synckey(item) ] = item
|
205
|
+
end
|
206
|
+
todel = herebox.keys - therebox.keys
|
207
|
+
toadd = therebox.keys - herebox.keys
|
208
|
+
tomod = herebox.keys & therebox.keys
|
209
|
+
|
210
|
+
if options.delete then
|
211
|
+
todel.each do |key|
|
212
|
+
verbose "Removing item #{key}"
|
213
|
+
unless $cfg['tool.dry'] then
|
214
|
+
item = herebox[key]
|
215
|
+
dest = tolocalpath(into, item)
|
216
|
+
removelocal(dest, item)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
if options.create then
|
221
|
+
toadd.each do |key|
|
222
|
+
verbose "Adding item #{key}"
|
223
|
+
unless $cfg['tool.dry'] then
|
224
|
+
item = therebox[key]
|
225
|
+
dest = tolocalpath(into, item)
|
226
|
+
|
227
|
+
download(dest, item)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
if options.update then
|
232
|
+
tomod.each do |key|
|
233
|
+
verbose "Updating item #{key}"
|
234
|
+
unless $cfg['tool.dry'] then
|
235
|
+
item = therebox[key]
|
236
|
+
dest = tolocalpath(into, herebox[key].merge(item) )
|
237
|
+
|
238
|
+
download(dest, item)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def download(local, item)
|
245
|
+
id = item[@itemkey.to_sym]
|
246
|
+
local.open('wb') do |io|
|
247
|
+
fetch(id) do |chunk|
|
248
|
+
io.write chunk
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def removelocal(dest, item)
|
254
|
+
dest.unlink
|
255
|
+
end
|
256
|
+
|
257
|
+
def status(from, options={})
|
258
|
+
there = list()
|
259
|
+
here = locallist(from)
|
260
|
+
itemkey = @itemkey.to_sym
|
261
|
+
|
262
|
+
therebox = {}
|
263
|
+
there.each do |item|
|
264
|
+
item = Hash.transform_keys_to_symbols(item)
|
265
|
+
item[:synckey] = synckey(item)
|
266
|
+
therebox[ item[:synckey] ] = item
|
267
|
+
end
|
268
|
+
herebox = {}
|
269
|
+
here.each do |item|
|
270
|
+
item = Hash.transform_keys_to_symbols(item)
|
271
|
+
item[:synckey] = synckey(item)
|
272
|
+
herebox[ item[:synckey] ] = item
|
273
|
+
end
|
274
|
+
if options.asdown then
|
275
|
+
todel = herebox.keys - therebox.keys
|
276
|
+
toadd = therebox.keys - herebox.keys
|
277
|
+
tomod = herebox.keys & therebox.keys
|
278
|
+
{
|
279
|
+
:toadd=> toadd.map{|key| therebox[key] },
|
280
|
+
:todel=> todel.map{|key| herebox[key] },
|
281
|
+
# FIXME what if therebox[key] is nil?
|
282
|
+
:tomod=> tomod.map{|key| therebox[key].merge(herebox[key]) }
|
283
|
+
}
|
284
|
+
else
|
285
|
+
toadd = herebox.keys - therebox.keys
|
286
|
+
todel = therebox.keys - herebox.keys
|
287
|
+
tomod = herebox.keys & therebox.keys
|
288
|
+
{
|
289
|
+
:toadd=> toadd.map{|key| herebox[key] },
|
290
|
+
:todel=> todel.map{|key| therebox[key] },
|
291
|
+
:tomod=> tomod.map{|key| therebox[key].merge(herebox[key]) }
|
292
|
+
}
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
class Solution < SolutionBase
|
298
|
+
def version
|
299
|
+
get('/version')
|
300
|
+
end
|
301
|
+
|
302
|
+
def info
|
303
|
+
get()
|
304
|
+
end
|
305
|
+
|
306
|
+
def list
|
307
|
+
get('/')
|
308
|
+
end
|
309
|
+
|
310
|
+
def log
|
311
|
+
get('/logs')
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
end
|
317
|
+
|
318
|
+
# vim: set ai et sw=2 ts=2 :
|