MuranoCLI 2.2.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/.agignore +3 -0
  3. data/.gitignore +18 -1
  4. data/.rubocop.yml +222 -0
  5. data/.trustme.sh +185 -0
  6. data/.trustme.vim +24 -0
  7. data/Gemfile +23 -4
  8. data/LICENSE.txt +1 -1
  9. data/MuranoCLI.gemspec +43 -8
  10. data/README.markdown +9 -11
  11. data/Rakefile +187 -143
  12. data/TODO.taskpaper +2 -2
  13. data/bin/murano +51 -52
  14. data/docs/basic_example.rst +436 -0
  15. data/docs/completions/murano_completion-bash +3484 -0
  16. data/docs/demo.md +32 -32
  17. data/docs/develop.rst +391 -0
  18. data/lib/MrMurano.rb +21 -7
  19. data/lib/MrMurano/Account.rb +159 -174
  20. data/lib/MrMurano/Business.rb +381 -0
  21. data/lib/MrMurano/Config-Migrate.rb +32 -26
  22. data/lib/MrMurano/Config.rb +407 -128
  23. data/lib/MrMurano/Content.rb +191 -0
  24. data/lib/MrMurano/Gateway.rb +489 -0
  25. data/lib/MrMurano/Keystore.rb +48 -0
  26. data/lib/MrMurano/Passwords.rb +103 -0
  27. data/lib/MrMurano/ProjectFile.rb +121 -79
  28. data/lib/MrMurano/ReCommander.rb +114 -10
  29. data/lib/MrMurano/Setting.rb +90 -0
  30. data/lib/MrMurano/Solution-ServiceConfig.rb +89 -45
  31. data/lib/MrMurano/Solution-Services.rb +461 -166
  32. data/lib/MrMurano/Solution-Users.rb +70 -31
  33. data/lib/MrMurano/Solution.rb +372 -13
  34. data/lib/MrMurano/SolutionId.rb +73 -0
  35. data/lib/MrMurano/SyncRoot.rb +137 -0
  36. data/lib/MrMurano/SyncUpDown.rb +594 -284
  37. data/lib/MrMurano/Webservice-Cors.rb +71 -0
  38. data/lib/MrMurano/Webservice-Endpoint.rb +234 -0
  39. data/lib/MrMurano/Webservice-File.rb +193 -0
  40. data/lib/MrMurano/Webservice.rb +51 -0
  41. data/lib/MrMurano/commands.rb +18 -15
  42. data/lib/MrMurano/commands/business.rb +300 -6
  43. data/lib/MrMurano/commands/completion-bash.erb +166 -0
  44. data/lib/MrMurano/commands/{zshcomplete.erb → completion-zsh.erb} +0 -0
  45. data/lib/MrMurano/commands/completion.rb +76 -39
  46. data/lib/MrMurano/commands/config.rb +108 -44
  47. data/lib/MrMurano/commands/content.rb +115 -72
  48. data/lib/MrMurano/commands/cors.rb +29 -14
  49. data/lib/MrMurano/commands/devices.rb +286 -0
  50. data/lib/MrMurano/commands/domain.rb +52 -12
  51. data/lib/MrMurano/commands/gb.rb +24 -9
  52. data/lib/MrMurano/commands/globals.rb +64 -0
  53. data/lib/MrMurano/commands/init.rb +377 -155
  54. data/lib/MrMurano/commands/keystore.rb +92 -82
  55. data/lib/MrMurano/commands/link.rb +300 -0
  56. data/lib/MrMurano/commands/login.rb +74 -11
  57. data/lib/MrMurano/commands/logs.rb +63 -32
  58. data/lib/MrMurano/commands/mock.rb +57 -29
  59. data/lib/MrMurano/commands/password.rb +57 -39
  60. data/lib/MrMurano/commands/postgresql.rb +127 -94
  61. data/lib/MrMurano/commands/settings.rb +203 -0
  62. data/lib/MrMurano/commands/show.rb +79 -38
  63. data/lib/MrMurano/commands/solution.rb +423 -5
  64. data/lib/MrMurano/commands/solution_picker.rb +547 -0
  65. data/lib/MrMurano/commands/status.rb +195 -61
  66. data/lib/MrMurano/commands/sync.rb +78 -39
  67. data/lib/MrMurano/commands/timeseries.rb +71 -55
  68. data/lib/MrMurano/commands/tsdb.rb +113 -87
  69. data/lib/MrMurano/commands/usage.rb +57 -15
  70. data/lib/MrMurano/hash.rb +100 -10
  71. data/lib/MrMurano/http.rb +187 -43
  72. data/lib/MrMurano/makePretty.rb +16 -14
  73. data/lib/MrMurano/optparse.rb +2178 -0
  74. data/lib/MrMurano/progress.rb +138 -0
  75. data/lib/MrMurano/schema/resource-v1.0.0.yaml +32 -0
  76. data/lib/MrMurano/template/projectFile.murano.erb +16 -13
  77. data/lib/MrMurano/verbosing.rb +166 -29
  78. data/lib/MrMurano/version.rb +30 -1
  79. data/spec/Account-Passwords_spec.rb +21 -4
  80. data/spec/Account_spec.rb +69 -146
  81. data/spec/Business_spec.rb +290 -0
  82. data/spec/ConfigFile_spec.rb +1 -0
  83. data/spec/ConfigMigrate_spec.rb +12 -8
  84. data/spec/Config_spec.rb +40 -34
  85. data/spec/Content_spec.rb +363 -0
  86. data/spec/GatewayBase_spec.rb +54 -0
  87. data/spec/GatewayDevice_spec.rb +321 -0
  88. data/spec/GatewayResource_spec.rb +266 -0
  89. data/spec/GatewaySettings_spec.rb +120 -0
  90. data/spec/Http_spec.rb +18 -8
  91. data/spec/Mock_spec.rb +2 -2
  92. data/spec/ProjectFile_spec.rb +25 -14
  93. data/spec/Setting_spec.rb +110 -0
  94. data/spec/Solution-ServiceConfig_spec.rb +44 -5
  95. data/spec/Solution-ServiceEventHandler_spec.rb +23 -14
  96. data/spec/Solution-ServiceModules_spec.rb +47 -37
  97. data/spec/Solution-UsersRoles_spec.rb +10 -8
  98. data/spec/Solution_spec.rb +17 -8
  99. data/spec/SyncRoot_spec.rb +46 -20
  100. data/spec/SyncUpDown_spec.rb +437 -201
  101. data/spec/Verbosing_spec.rb +12 -4
  102. data/spec/{Solution-Cors_spec.rb → Webservice-Cors_spec.rb} +23 -20
  103. data/spec/{Solution-Endpoint_spec.rb → Webservice-Endpoint_spec.rb} +43 -41
  104. data/spec/{Solution-File_spec.rb → Webservice-File_spec.rb} +44 -33
  105. data/spec/Webservice-Setting_spec.rb +89 -0
  106. data/spec/_workspace.rb +4 -4
  107. data/spec/cmd_business_spec.rb +9 -4
  108. data/spec/cmd_common.rb +44 -1
  109. data/spec/cmd_content_spec.rb +43 -17
  110. data/spec/cmd_cors_spec.rb +4 -4
  111. data/spec/cmd_device_spec.rb +61 -16
  112. data/spec/cmd_domain_spec.rb +29 -6
  113. data/spec/cmd_init_spec.rb +281 -126
  114. data/spec/cmd_keystore_spec.rb +3 -3
  115. data/spec/cmd_link_spec.rb +98 -0
  116. data/spec/cmd_password_spec.rb +1 -1
  117. data/spec/cmd_setting_application_spec.rb +260 -0
  118. data/spec/cmd_setting_product_spec.rb +220 -0
  119. data/spec/cmd_status_spec.rb +223 -114
  120. data/spec/cmd_syncdown_spec.rb +115 -35
  121. data/spec/cmd_syncup_spec.rb +68 -15
  122. data/spec/cmd_usage_spec.rb +35 -8
  123. data/spec/fixtures/dumped_config +6 -4
  124. data/spec/fixtures/gateway_resource_files/resources.notyaml +12 -0
  125. data/spec/fixtures/gateway_resource_files/resources.yaml +13 -0
  126. data/spec/fixtures/gateway_resource_files/resources_invalid.yaml +13 -0
  127. data/spec/fixtures/mrmuranorc_deleted_bob +0 -2
  128. data/spec/fixtures/product_spec_files/lightbulb.yaml +20 -13
  129. data/spec/fixtures/{syncable_content → syncable_conflict}/services/devdata.lua +1 -1
  130. data/spec/fixtures/{syncable_content → syncable_conflict}/services/timers.lua +0 -0
  131. data/spec/spec_helper.rb +5 -0
  132. metadata +262 -171
  133. data/bin/mr +0 -8
  134. data/lib/MrMurano/Product-1P-Device.rb +0 -145
  135. data/lib/MrMurano/Product-Resources.rb +0 -205
  136. data/lib/MrMurano/Product.rb +0 -358
  137. data/lib/MrMurano/Solution-Cors.rb +0 -47
  138. data/lib/MrMurano/Solution-Endpoint.rb +0 -191
  139. data/lib/MrMurano/Solution-File.rb +0 -166
  140. data/lib/MrMurano/commands/assign.rb +0 -57
  141. data/lib/MrMurano/commands/businessList.rb +0 -45
  142. data/lib/MrMurano/commands/product.rb +0 -14
  143. data/lib/MrMurano/commands/productCreate.rb +0 -39
  144. data/lib/MrMurano/commands/productDelete.rb +0 -33
  145. data/lib/MrMurano/commands/productDevice.rb +0 -87
  146. data/lib/MrMurano/commands/productDeviceIdCmds.rb +0 -89
  147. data/lib/MrMurano/commands/productList.rb +0 -45
  148. data/lib/MrMurano/commands/productWrite.rb +0 -27
  149. data/lib/MrMurano/commands/solutionCreate.rb +0 -41
  150. data/lib/MrMurano/commands/solutionDelete.rb +0 -34
  151. data/lib/MrMurano/commands/solutionList.rb +0 -45
  152. data/spec/ProductBase_spec.rb +0 -113
  153. data/spec/ProductContent_spec.rb +0 -162
  154. data/spec/ProductResources_spec.rb +0 -329
  155. data/spec/Product_1P_Device_spec.rb +0 -202
  156. data/spec/Product_1P_RPC_spec.rb +0 -175
  157. data/spec/Product_spec.rb +0 -153
  158. data/spec/Solution-ServiceDevice_spec.rb +0 -176
  159. data/spec/cmd_assign_spec.rb +0 -51
@@ -0,0 +1,71 @@
1
+ # Last Modified: 2017.08.17 /coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright © 2016-2017 Exosite LLC.
5
+ # License: MIT. See LICENSE.txt.
6
+ # vim:tw=0:ts=2:sw=2:et:ai
7
+
8
+ require 'json'
9
+ require 'yaml'
10
+ require 'MrMurano/Webservice'
11
+
12
+ module MrMurano
13
+ module Webservice
14
+ class Settings < WebserviceBase
15
+ def initialize
16
+ super
17
+ @uriparts << :cors
18
+ end
19
+
20
+ def cors
21
+ ret = get
22
+ return {} unless ret.is_a?(Hash) && !ret.key?(:error)
23
+ ret
24
+ end
25
+
26
+ def cors=(x)
27
+ raise 'Not Hash' unless x.is_a? Hash
28
+ put('', x)
29
+ end
30
+ end
31
+
32
+ class Cors < WebserviceBase
33
+ def initialize
34
+ super
35
+ @uriparts << :cors
36
+ #@project_section = :cors
37
+ end
38
+
39
+ def fetch(_id=nil)
40
+ ret = get
41
+ return [] unless ret.is_a?(Hash) && !ret.key?(:error)
42
+ if ret.key?(:cors)
43
+ # XXX cors is a JSON encoded string. That seems weird. keep an eye on this.
44
+ data = JSON.parse(ret[:cors], @json_opts)
45
+ else
46
+ data = ret
47
+ end
48
+ if block_given?
49
+ yield Hash.transform_keys_to_strings(data).to_yaml
50
+ else
51
+ data
52
+ end
53
+ end
54
+
55
+ ##
56
+ # Upload CORS
57
+ # @param file [String,Nil] File path to upload other than defaults
58
+ def upload(file=nil)
59
+ if !file.nil?
60
+ data = YAML.load_file(file)
61
+ else
62
+ data = $project['routes.cors']
63
+ # If it is just a string, then is a file to load.
64
+ data = YAML.load_file(data) if data.is_a? String
65
+ end
66
+ put('', data)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,234 @@
1
+ # Last Modified: 2017.08.17 /coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright © 2016-2017 Exosite LLC.
5
+ # License: MIT. See LICENSE.txt.
6
+ # vim:tw=0:ts=2:sw=2:et:ai
7
+
8
+ require 'json'
9
+ require 'net/http'
10
+ require 'pp'
11
+ require 'uri'
12
+ require 'MrMurano/SyncRoot'
13
+ require 'MrMurano/Webservice'
14
+
15
+ module MrMurano
16
+ # …/endpoint
17
+ module Webservice
18
+ class Endpoint < WebserviceBase
19
+
20
+ # Route Specific details on an Item
21
+ class RouteItem < Item
22
+ # @return [String] HTTP method for this endpoint
23
+ attr_accessor :method
24
+ # @return [String] path for URL maps to this endpoint
25
+ attr_accessor :path
26
+ # @return [String] Acceptable Content-Type for this endpoint
27
+ attr_accessor :content_type
28
+ # ???? What is this?
29
+ attr_accessor :use_basic_auth
30
+ end
31
+
32
+ def initialize
33
+ super
34
+ @uriparts << :endpoint
35
+ @project_section = :routes
36
+ @match_header = /--#ENDPOINT (?<method>\S+) (?<path>\S+)( (?<ctype>.*))?/
37
+ end
38
+
39
+ def self.description
40
+ # 2017-08-07: UI and ProjectFile call these "Routes". Let's be consistent.
41
+ # 2017-08-14: UI team says "Route" changing to "Endpoint" in new UI.
42
+ #%(Route)
43
+ %(Endpoint)
44
+ end
45
+
46
+ ##
47
+ # This gets all data about all endpoints
48
+ def list
49
+ ret = get
50
+ return [] unless ret.is_a?(Array)
51
+ ret.map do |item|
52
+ if item[:content_type].to_s.empty? then
53
+ item[:content_type] = 'application/json'
54
+ end
55
+ # XXX should this update the script header?
56
+ RouteItem.new(item)
57
+ end
58
+ # MAYBE/2017-08-17:
59
+ # ret.map! ...
60
+ # sort_by_name(ret)
61
+ end
62
+
63
+ def fetch(id)
64
+ ret = get('/' + id.to_s)
65
+ unless ret.is_a?(Hash) && !ret.key?(:error)
66
+ error "#{UNEXPECTED_TYPE_OR_ERROR_MSG}: #{ret}"
67
+ ret = {}
68
+ end
69
+
70
+ ret[:content_type] = 'application/json' if ret[:content_type].empty?
71
+
72
+ script = ret[:script].lines.map{|l|l.chomp}
73
+
74
+ aheader = (script.first or "")
75
+
76
+ rh = ['--#ENDPOINT', ret[:method].upcase, ret[:path]]
77
+ rh << ret[:content_type] if ret[:content_type] != 'application/json'
78
+ rheader = rh.join(' ')
79
+
80
+ # if header is missing add it.
81
+ # If header is wrong, replace it.
82
+
83
+ md = @match_header.match(aheader)
84
+ if md.nil? then
85
+ # header missing.
86
+ script.unshift rheader
87
+ elsif md[:method] != ret[:method] or
88
+ md[:path] != ret[:path] or
89
+ md[:ctype] != ret[:content_type] then
90
+ # header is wrong.
91
+ script[0] = rheader
92
+ end
93
+ # otherwise current header is good.
94
+
95
+ script = script.join("\n") + "\n"
96
+ if block_given? then
97
+ yield script
98
+ else
99
+ script
100
+ end
101
+ end
102
+
103
+ ##
104
+ # Upload endpoint
105
+ # @param local [Pathname] path to file to push
106
+ # @param remote [RouteItem] of method and endpoint path
107
+ # @param modify [Boolean] True if item exists already and this is changing it
108
+ def upload(local, remote, modify)
109
+ local = Pathname.new(local) unless local.kind_of? Pathname
110
+ raise "no file" unless local.exist?
111
+ # we assume these are small enough to slurp.
112
+ if remote.script.nil? then
113
+ script = local.read
114
+ remote[:script] = script
115
+ end
116
+ limitkeys = [:method, :path, :script, :content_type, @itemkey]
117
+ remote = remote.to_h.select{|k,v| limitkeys.include? k }
118
+ # post('', remote)
119
+ if remote.has_key? @itemkey then
120
+ put('/' + remote[@itemkey], remote) do |request, http|
121
+ response = http.request(request)
122
+ case response
123
+ when Net::HTTPSuccess
124
+ #return JSON.parse(response.body)
125
+ when Net::HTTPNotFound
126
+ verbose "\tDoesn't exist, creating"
127
+ post('/', remote)
128
+ else
129
+ showHttpError(request, response)
130
+ end
131
+ end
132
+ else
133
+ verbose "\tNo itemkey, creating"
134
+ post('', remote)
135
+ end
136
+ end
137
+
138
+ ##
139
+ # Delete an endpoint
140
+ def remove(id)
141
+ delete('/' + id.to_s)
142
+ end
143
+
144
+ def tolocalname(item, key)
145
+ name = ''
146
+ # 2017-07-02: Changing shovel operator << to +=
147
+ # to support Ruby 3.0 frozen string literals.
148
+ name += item[:path].split('/').reject { |i| i.empty? }.join('-')
149
+ name += '.'
150
+ # This downcase is just for the filename.
151
+ name += item[:method].downcase
152
+ name += '.lua'
153
+ end
154
+
155
+ def to_remote_item(from, path)
156
+ # Path could be have multiple endpoints in side, so a loop.
157
+ items = []
158
+ path = Pathname.new(path) unless path.kind_of? Pathname
159
+ cur = nil
160
+ lineno = 0
161
+ path.readlines().each do |line|
162
+ md = @match_header.match(line)
163
+ if not md.nil? then
164
+ # header line.
165
+ cur[:line_end] = lineno unless cur.nil?
166
+ items << cur unless cur.nil?
167
+ # VERIFY/2017-07-03: The syncdown test is revealing a
168
+ # problem with casing. The original file has a lowercase
169
+ # HTTP verb, e.g., "post". This is what syncup uploaded.
170
+ # But on murano status, the local route's method is upcased
171
+ # in memory, so the status command says the route is diff.
172
+ # But on murano diff, MurCLI makes two local temp files
173
+ # to execute the diff, and it also upcases the method in
174
+ # both files, so the diff runs clean!
175
+ # VERIFY/2017-07-03: [lb] adding upcase here; hope that works!
176
+ # OHOHOH/2017-07-03: [lb] also recreating the header line.
177
+ up_line = "--#ENDPOINT #{md[:method].upcase} #{md[:path]}"
178
+ up_line += " #{md[:ctype]}" unless md[:ctype].to_s.empty?
179
+ up_line += "\n"
180
+ cur = RouteItem.new(
181
+ #method: md[:method],
182
+ method: md[:method].upcase,
183
+ path: md[:path],
184
+ content_type: (md[:ctype] or 'application/json'),
185
+ local_path: path,
186
+ line: lineno,
187
+ script: up_line,
188
+ )
189
+ elsif not cur.nil? and not cur[:script].nil? then
190
+ # 2017-07-02: Frozen string literal: change << to +=
191
+ cur[:script] += line
192
+ end
193
+ lineno += 1
194
+ end
195
+ cur[:line_end] = lineno unless cur.nil?
196
+ items << cur unless cur.nil?
197
+ items
198
+ end
199
+
200
+ def match(item, pattern)
201
+ # Pattern is: #{method}#{path glob}
202
+ pattern_pattern = /^#(?<method>[^#]*)#(?<path>.*)/i
203
+ md = pattern_pattern.match(pattern)
204
+ return false if md.nil?
205
+ debug "match pattern: '#{md[:method]}' '#{md[:path]}'"
206
+
207
+ unless md[:method].empty? then
208
+ return false unless item[:method].casecmp(md[:method]).zero?
209
+ end
210
+
211
+ return true if md[:path].empty?
212
+
213
+ ::File.fnmatch(md[:path],item[:path])
214
+ end
215
+
216
+ def synckey(item)
217
+ "#{item[:method].upcase}_#{item[:path]}"
218
+ end
219
+
220
+ def docmp(itemA, itemB)
221
+ if itemA[:script].nil? and itemA[:local_path] then
222
+ itemA[:script] = itemA[:local_path].read
223
+ end
224
+ if itemB[:script].nil? and itemB[:local_path] then
225
+ itemB[:script] = itemB[:local_path].read
226
+ end
227
+ return (itemA[:script] != itemB[:script] or itemA[:content_type] != itemB[:content_type])
228
+ end
229
+ end
230
+
231
+ SyncRoot.instance.add('endpoints', Endpoint, 'E', true, %w[routes])
232
+ end
233
+ end
234
+
@@ -0,0 +1,193 @@
1
+ require 'digest/sha1'
2
+ require "http/form_data"
3
+ require 'mime/types'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'MrMurano/Webservice'
7
+ require 'MrMurano/SyncRoot'
8
+
9
+ module MrMurano
10
+ module Webservice
11
+ # Static File content
12
+ class File < WebserviceBase
13
+ # File Specific details on an Item
14
+ class FileItem < Item
15
+ # @return [String] path for URL maps to this static file
16
+ attr_accessor :path
17
+ # @return [String] The MIME-Type for this content
18
+ attr_accessor :mime_type
19
+ # @return [String] Checksum for the content.
20
+ attr_accessor :checksum
21
+ end
22
+
23
+ def initialize
24
+ super
25
+ @uriparts << :file
26
+ @itemkey = :path
27
+ @project_section = :assets
28
+ end
29
+
30
+ def self.description
31
+ # 2017-08-07: The UI calls these ASSETS in the tab
32
+ # (and refers to "Static file hosting").
33
+ #%(Static File)
34
+ %(Asset)
35
+ end
36
+
37
+ ##
38
+ # Get a list of all of the static content
39
+ # @return [Array<FileItem>] List of items on server
40
+ def list
41
+ ret = get
42
+ return [] unless ret.is_a?(Array)
43
+ ret.map { |i| FileItem.new(i) }
44
+ # MAYBE/2017-08-17:
45
+ # ret.map! { |i| FileItem.new(i) }
46
+ # sort_by_name(ret)
47
+ end
48
+
49
+ ##
50
+ # Get one item of the static content.
51
+ def fetch(path, &block)
52
+ path = path[1..-1] if path[0] == '/'
53
+ path = '/'+ URI.encode_www_form_component(path)
54
+ get(path) do |request, http|
55
+ http.request(request) do |resp|
56
+ case resp
57
+ when Net::HTTPSuccess
58
+ if block_given? then
59
+ resp.read_body(&block)
60
+ else
61
+ resp.read_body do |chunk|
62
+ $stdout.write chunk
63
+ end
64
+ end
65
+ else
66
+ showHttpError(request, resp)
67
+ end
68
+ end
69
+ nil
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Delete a file
75
+ # @param path [String] The identifying key for this item
76
+ def remove(path)
77
+ path = path[1..-1] if path[0] == '/'
78
+ delete('/' + URI.encode_www_form_component(path))
79
+ end
80
+
81
+ def curldebug(request)
82
+ # The upload will get printed out inside of upload.
83
+ # Because we don't have the correct info here.
84
+ if request.method != 'PUT' then
85
+ super(request)
86
+ end
87
+ end
88
+
89
+ ##
90
+ # Upload a file
91
+ # @param src [Pathname] Full path of where to upload from
92
+ # @param item [Hash] The item details to upload
93
+ # @param modify [Boolean] True if item exists already and this is changing it
94
+ def upload(local, remote, modify)
95
+ local = Pathname.new(local) unless local.kind_of? Pathname
96
+
97
+ path = remote[:path]
98
+ path = path[1..-1] if path[0] == '/'
99
+ uri = endpoint('upload/' + URI.encode_www_form_component(path))
100
+ # kludge past for a bit.
101
+ #`curl -s -H 'Authorization: token #{@token}' '#{uri.to_s}' -F file=@#{local.to_s}`
102
+
103
+ # http://stackoverflow.com/questions/184178/ruby-how-to-post-a-file-via-http-as-multipart-form-data
104
+ #
105
+ # Look at: https://github.com/httprb/http
106
+ # If it works well, consider porting over to it.
107
+ #
108
+ # Or just: https://github.com/httprb/form_data.rb ?
109
+ #
110
+ # Most of these pull into ram. So maybe just go with that. Would guess that
111
+ # truely large static content is rare, and we can optimize/fix that later.
112
+
113
+ file = HTTP::FormData::File.new(local.to_s, { content_type: remote[:mime_type] })
114
+ form = HTTP::FormData.create(file: file)
115
+ req = Net::HTTP::Put.new(uri)
116
+ set_def_headers(req)
117
+ workit(req) do |request, http|
118
+ request.content_type = form.content_type
119
+ request.content_length = form.content_length
120
+ request.body = form.to_s
121
+
122
+ if $cfg['tool.curldebug'] then
123
+ a = []
124
+ a << %{curl -s -H 'Authorization: #{request['authorization']}'}
125
+ a << %{-H 'User-Agent: #{request['User-Agent']}'}
126
+ a << %{-X #{request.method}}
127
+ a << %{'#{request.uri.to_s}'}
128
+ a << %{-F file=@#{local.to_s}}
129
+ if $cfg.curlfile_f.nil?
130
+ puts a.join(' ')
131
+ else
132
+ $cfg.curlfile_f << a.join(' ') + "\n\n"
133
+ $cfg.curlfile_f.flush
134
+ end
135
+ end
136
+
137
+ response = http.request(request)
138
+ showHttpError(request, response) unless response.is_a?(Net::HTTPSuccess)
139
+ end
140
+ end
141
+
142
+ # @param item [Item] listing details for the item.
143
+ # @param itemkey [Symbol] Key for look up.
144
+ def tolocalname(item, key)
145
+ name = item[key]
146
+ name = $cfg['files.default_page'] if name == '/'
147
+ name
148
+ end
149
+
150
+ # @param root [Pathname,String] Root path for this resource type from config files
151
+ # @param path [Pathname,String] Path to local item
152
+ # @return [Item] hash of the details for the remote item for this path
153
+ def to_remote_item(from, path)
154
+ item = super(from, path)
155
+ name = item[:name]
156
+ name = '/' if name == $cfg['files.default_page']
157
+ name = "/#{name}" unless name.chars.first == '/'
158
+
159
+ mime = MIME::Types.type_for(path.to_s)[0] || MIME::Types["application/octet-stream"][0]
160
+
161
+ # It does not actually take the SHA1 of the file.
162
+ # It first converts the file to hex, then takes the SHA1 of that string
163
+ #sha1 = Digest::SHA1.file(path.to_s).hexdigest
164
+ sha1 = Digest::SHA1.new
165
+ path.open('rb:ASCII-8BIT') do |io|
166
+ while chunk = io.read(1048576) do
167
+ sha1 << Digest.hexencode(chunk)
168
+ end
169
+ end
170
+ debug "Checking #{name} (#{mime.simplified} #{sha1.hexdigest})"
171
+
172
+ FileItem.new(:path=>name, :mime_type=>mime.simplified, :checksum=>sha1.hexdigest)
173
+ end
174
+
175
+ # @param item [FileItem] The item to get a key from
176
+ # @return [Object] The object to use a comparison key
177
+ def synckey(item)
178
+ item[:path]
179
+ end
180
+
181
+ # Compare items.
182
+ # @param itemA [FileItem]
183
+ # @param itemB [FileItem]
184
+ def docmp(itemA, itemB)
185
+ return (itemA[:mime_type] != itemB[:mime_type] or
186
+ itemA[:checksum] != itemB[:checksum])
187
+ end
188
+ end
189
+
190
+ SyncRoot.instance.add('assets', File, 'A', true, %w[files])
191
+ end
192
+ end
193
+ # vim: set ai et sw=2 ts=2 :