OSDN Git Service

Update version.
[osdn-codes/osdn-cli.git] / lib / osdn / cli.rb
1 require "osdn/cli/version"
2 require "osdn/cli/runner"
3 require "osdn/cli/overrides"
4 require 'getoptlong'
5 require 'logger'
6 require 'pathname'
7 require 'yaml'
8 require 'fileutils'
9 require 'osdn-client'
10 require 'hashie'
11 require 'json'
12 require 'digest'
13
14 module OSDN
15   module CLI
16     @@client_id = "osdn-cli"
17     @@client_secret = "not-secret"
18     def client_id
19       @@client_id
20     end
21     def client_secret
22       @@client_secret
23     end
24
25     @@_show_progress = false
26     def _show_progress
27       @@_show_progress
28     end
29     def _show_progress=(v)
30       @@_show_progress = v
31     end
32
33     @@_rate_limit = nil
34     def _rate_limit
35       @@_rate_limit
36     end
37     def _rate_limit=(v)
38       @@_rate_limit = v
39     end
40
41     module_function :client_id, :client_secret,
42                     :_show_progress, :_show_progress=,
43                     :_rate_limit, :_rate_limit=
44     
45     module Command
46       autoload :Login,     'osdn/cli/command/login'
47       autoload :Package,   'osdn/cli/command/package'
48       autoload :Release,   'osdn/cli/command/release'
49       autoload :Relfile,   'osdn/cli/command/relfile'
50       autoload :FrsMkdirs, 'osdn/cli/command/frs_mkdirs'
51       autoload :FrsUpload, 'osdn/cli/command/frs_upload'
52       
53       class Base
54         def initialize(logger)
55           @logger = logger
56           @credential = Hashie::Mash.new
57           @format = 'pretty'
58         end
59         attr_reader :logger
60         attr_accessor :credential, :format
61
62         def credential_path
63           Pathname.new(ENV['HOME']) + '.config/osdn/credential.yml'
64         end
65
66         def load_credential
67           begin
68             stat = credential_path.stat()
69             unless credential_path.owned?
70               logger.error "Invalid ownership of credential file #{credential_path}, skip loading."
71               return
72             end
73             if RUBY_PLATFORM !~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ && (stat.mode & 0777).to_s(8) != "600"
74               logger.error "Invalid permission #{(stat.mode & 0777).to_s(8)} of credential file #{credential_path}, skip loading."
75               return
76             end
77           rescue Errno::ENOENT
78             return
79           end
80           logger.debug "Loading credentials from #{credential_path}"
81           @credential = Hashie::Mash.new(YAML.load_file(credential_path))
82           set_client_token
83         end
84
85         def write_credential
86           FileUtils.mkdir_p credential_path.dirname, verbose: false
87           cio = credential_path.open('w', 0600)
88           YAML.dump(credential.to_hash, cio)
89           cio.close
90         end
91
92         def update_token
93           logger.debug "Checking token expires..."
94           load_credential
95           unless credential.access_token
96             logger.fatal "You have no access token. Please login with '#{$0} login'."
97             return
98           end
99           if credential.expires_at > Time.now + 30
100             logger.debug "You have valid access token, skip to refresh."
101             return
102           end
103
104           logger.debug "Access token has been expired. Trying to refresh token..."
105           api = OSDNClient::DefaultApi.new
106           begin
107             set_credential api.token(CLI.client_id, CLI.client_secret, grant_type: 'refresh_token', refresh_token: credential.refresh_token)
108           rescue OSDNClient::ApiError => e
109             begin
110               err = JSON.parse(e.response_body)
111               logger.fatal err["error_description"]
112             rescue
113               logger.fatal "Failed to refresh access token."
114             end
115             logger.fatal "Please login again."
116             return
117           end
118           logger.debug "Access token is refreshed successfully."
119         end
120
121         def set_credential(token, update_expires = true)
122           token = Hashie::Mash.new(token.to_hash)
123           if update_expires
124             token.expires_at = Time.now + token.expires_in.to_i
125           end
126           token.scope = [*token.scope].join(' ').split(' ')
127           
128           credential.update token
129           write_credential
130           set_client_token
131         end
132
133         def set_client_token
134           if credential.access_token && !credential.access_token.empty?
135             OSDNClient.configure do |config|
136               config.access_token = credential.access_token
137             end
138           end
139         end
140
141         def load_variables(path = '.', recursive_merge = true)
142           vars = {}
143           path = Pathname.new(Dir.getwd) + path
144           cur_dir = Pathname.new('/')
145           if recursive_merge
146             path.each_filename do |d|
147               cur_dir = cur_dir + d
148               vf = cur_dir + '.osdn.vars'
149               vf.exist? or next
150               begin
151                 logger.debug "Load and merge variables from #{vf}"
152                 vars.update YAML.load_file(vf.to_s)
153               rescue => e
154                 logger.warn "Failed to load variables from #{vf}; #{e.message}"
155               end
156             end
157           else
158             begin
159               path = path+'.osdn.vars'
160               if path.exist?
161                 logger.debug "Load and merge variables from #{path}"
162                 vars.update YAML.load_file(path)
163               end
164             rescue => e
165               logger.warn "Failed to load variables from #{path}; #{e.message}"
166             end
167           end
168           # Fix key conflicts
169           if vars['local_file_info']
170             vars['local_file_info'].each do |fname, finfo|
171               finfo.has_key?('size') or next
172               finfo['filesize'] = finfo.delete('size')
173             end
174           end
175           logger.debug "Variables: #{vars.inspect}"
176           Hashie::Mash.new(vars)
177         end
178
179         def write_variables(vars, dir = nil)
180           path = Pathname.new(dir || '.') + '.osdn.vars'
181           logger.info "Save variables to #{path}"
182           vio = path.open('w')
183           YAML.dump(vars.to_hash, vio)
184           vio.close
185         end
186
187         def update_variables(dir, vars)
188           write_variables(load_variables(dir, false).deep_merge(vars), dir)
189         end
190
191         private
192         def hexdigest(klass, file)
193           fio = file.open
194           dig = klass.new
195           while buf = fio.read(1024*1024) and buf.length > 0
196             dig << buf
197           end
198           fio.close
199           dig.hexdigest
200         end
201         
202       end
203
204       class Ping < Base
205         def run
206           update_token
207           api = OSDNClient::DefaultApi.new
208           pp api.ping
209         end
210
211         def help
212           puts "#{$0} ping"
213         end
214
215         def self.description
216           "Test API request."
217         end
218       end
219         
220       class Vars < Base
221         def run
222           subcommand = ARGV.shift ||'show'
223           self.send subcommand
224         end
225
226         def show
227           name = ARGV.shift
228           if name
229             puts load_variables[name]
230           else
231             pp load_variables
232           end
233         end
234
235         def set
236           name, value = ARGV.shift, ARGV.shift
237           if !name || name.empty?
238             logger.fatal "Missing variable name"
239             help
240             exit
241           end
242           if !value || value.empty?
243             logger.fatal "Missing variable value"
244             help
245             exit
246           end
247           vars = load_variables('.', false)
248           vars[name] = value
249           write_variables vars
250         end
251
252         def help
253           puts "#{$0} vars show [name]            -- Show current variable"
254           puts "#{$0} vars set <name> <value>     -- Save variable to .osdn.vars"
255         end
256
257         def self.description
258           "Get/set request environment variable."
259         end
260       end
261     end
262   end
263 end