OSDN Git Service

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