OSDN Git Service

Runnable on win32 environment.
[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           logger.debug "Variables: #{vars.inspect}"
169           Hashie::Mash.new(vars)
170         end
171
172         def write_variables(vars, dir = nil)
173           path = Pathname.new(dir || '.') + '.osdn.vars'
174           logger.info "Save variables to #{path}"
175           vio = path.open('w')
176           YAML.dump(vars.to_hash, vio)
177           vio.close
178         end
179
180         def update_variables(dir, vars)
181           write_variables(load_variables(dir, false).deep_merge(vars), dir)
182         end
183
184         private
185         def hexdigest(klass, file)
186           fio = file.open
187           dig = klass.new
188           while buf = fio.read(1024*1024) and buf.length > 0
189             dig << buf
190           end
191           fio.close
192           dig.hexdigest
193         end
194         
195       end
196
197       class Ping < Base
198         def run
199           update_token
200           api = OSDNClient::DefaultApi.new
201           pp api.ping
202         end
203
204         def help
205           puts "#{$0} ping"
206         end
207
208         def self.description
209           "Test API request."
210         end
211       end
212         
213       class Vars < Base
214         def run
215           subcommand = ARGV.shift ||'show'
216           self.send subcommand
217         end
218
219         def show
220           name = ARGV.shift
221           if name
222             puts load_variables[name]
223           else
224             pp load_variables
225           end
226         end
227
228         def set
229           name, value = ARGV.shift, ARGV.shift
230           if !name || name.empty?
231             logger.fatal "Missing variable name"
232             help
233             exit
234           end
235           if !value || value.empty?
236             logger.fatal "Missing variable value"
237             help
238             exit
239           end
240           vars = load_variables('.', false)
241           vars[name] = value
242           write_variables vars
243         end
244
245         def help
246           puts "#{$0} vars show [name]            -- Show current variable"
247           puts "#{$0} vars set <name> <value>     -- Save variable to .osdn.vars"
248         end
249
250         def self.description
251           "Get/set request environment variable."
252         end
253       end
254     end
255   end
256 end