OSDN Git Service

Update version.
[osdn-codes/osdn-cli.git] / lib / osdn / cli / command / relfile.rb
1 require 'osdn/cli/command/frs_base'
2 require 'pathname'
3
4 module OSDN; module CLI; module Command
5   class Relfile < FrsBase
6     attr_accessor :target_proj, :target_package, :target_release, :visibility, :force_digest, :show_progress
7
8     def help
9       puts "#{$0} relfile [opts] [list]"
10       puts "#{$0} relfile [opts] create <target-file> [target-files...]"
11       puts "#{$0} relfile [opts] update <numeric-file-id>"
12       puts "#{$0} relfile [opts] delete <numeric-file-id>"
13       puts "Options:"
14       puts "  -f --format=<pretty|json>  Set output format"
15       puts "  -p --project=<project>     Target project (numeric id or name)"
16       puts "     --package=<package-id>  Target package (numeric id)"
17       puts "     --release=<release-id>  Target release (numeric id)"
18       puts "  -v --visibility=<public|private|hidden>"
19       puts "      --force-digest         Calc local file digest forcely"
20       puts "      --progress             Force to show upload progress"
21       puts "      --no-progress          Force to hide upload progress"
22       puts "      --bwlimit=RATE         Limit bandwidth (in KB)"
23     end
24
25     def self.description
26       "Manipulate frs files of project"
27     end
28
29     def process_options
30       opts = GetoptLong.new(
31         [ '--format', '-f', GetoptLong::REQUIRED_ARGUMENT ],
32         [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ],
33         [ '--package', GetoptLong::REQUIRED_ARGUMENT ],
34         [ '--release', GetoptLong::REQUIRED_ARGUMENT ],
35         [ '--visibility', '-v', GetoptLong::REQUIRED_ARGUMENT ],
36         [ '--force-digest', GetoptLong::NO_ARGUMENT],
37         [ '--progress', GetoptLong::NO_ARGUMENT],
38         [ '--no-progress', GetoptLong::NO_ARGUMENT],
39         [ '--bwlimit', GetoptLong::REQUIRED_ARGUMENT ],
40       )
41       opts.each do |opt, arg|
42         case opt
43         when '--format'
44           arg == 'json' and
45             self.format = arg
46         when '--project'
47           arg.empty? or
48             @target_proj = arg
49         when '--package'
50           arg.empty? or
51             @target_package = arg
52         when '--release'
53           arg.empty? or
54             @target_release = arg
55         when '--force-digest'
56           @force_digest = true
57         when '--visibility'
58           unless %w(public private hidden).member?(arg)
59             logger.fatal "Invalid visibility status: #{arg}"
60             exit
61           end
62           @visibility = arg
63         when '--progress'
64           @show_progress = true
65         when '--no-progress'
66           @show_progress = false
67         when '--bwlimit'
68           arg.to_i != 0 and
69             OSDN::CLI._rate_limit = arg.to_i * 1024
70         end
71       end
72     end
73     
74     def list
75       release = api.get_release target_proj, target_package, target_release
76       list = release.files
77       if format == 'json'
78         puts list.map{|i| i.to_hash}.to_json
79       else
80         list.each do |f|
81           puts format_file(f)
82         end
83       end
84     end
85
86     def create
87       if ARGV.empty? || ARGV.first == ""
88         logger.fatal "Target filename is missing."
89         help
90         return
91       end
92
93       ARGV.each do |f|
94         create_one(f)
95       end
96     end
97
98     def calc_file_digest(filename)
99       file = Pathname('.') + filename
100       vars = load_variables(file.dirname)
101       digests = nil
102       if !@force_digest && vars.local_file_info &&
103         vars.local_file_info[file.basename.to_s]
104         finfo = vars.local_file_info[file.basename.to_s]
105         if finfo.filesize == file.size && finfo.mtime == file.mtime
106           digests = vars.local_file_info[file.basename.to_s].digests
107         end
108       end
109
110       unless digests
111         logger.info "Calculating digest for #{file}..."
112         digests = {
113           sha256: hexdigest(Digest::SHA256, file),
114           sha1:   hexdigest(Digest::SHA1, file),
115           md5:    hexdigest(Digest::MD5, file),
116         }
117         update_variables file.dirname, {local_file_info: {file.basename.to_s => {digests: digests, mtime: file.mtime, filesize: file.size}}}
118       end
119     end
120
121     def create_one(filename)
122       file = Pathname('.') + filename
123       calc_file_digest file
124       vars = load_variables(file.dirname)
125       fio = file.open
126       logger.level <= Logger::INFO && @show_progress != false || @show_progress and
127         OSDN::CLI._show_progress = true
128       logger.info "Starting upload #{file} (#{file.size} bytes)..."
129       max_upload_tries = 5
130       upload_tries = 0
131       remote_file = nil
132       begin
133         upload_tries += 1
134         remote_file = api.create_release_file target_proj, target_package, target_release, fio, visibility: @visibility
135       rescue OSDNClient::ApiError => e
136         if max_upload_tries - upload_tries <= 0 
137           logger.error "Max upload attempts (#{max_upload_tries}) has been exceeded, give up!"
138           raise e
139         elsif [0, 100, 502].member?(e.code.to_i)
140           fio.rewind
141           logger.error "Upload error (#{e.code} #{e.message}), retrying (#{upload_tries}/#{max_upload_tries})..."
142           sleep 10
143           retry
144         else
145           raise e
146         end
147       ensure
148         fio.close
149         OSDN::CLI._show_progress = false
150       end
151       
152       if vars.local_file_info[file.basename.to_s].digests.find { |type, dig| rd = remote_file.send("digest_#{type}"); rd && rd != '' && rd != dig }
153         logger.error "File digest mismatch! Uploaded file #{file} may be broken! Please check."
154       elsif file.size != remote_file.size
155         logger.error "File size mismatch! Uploaded file #{file} may be broken! Please check."
156       else
157         logger.info "Upload complete."
158       end
159       puts format_file(remote_file)
160       remote_file
161     end
162
163     def update
164       target_id = ARGV.shift
165       if !target_id
166         logger.fatal "Target file ID is missing."
167         help
168         return
169       end
170       if @visibility.nil?
171         logger.fatal "Visibility status is missing. Use '-v <public|private|hidden>'."
172         return
173       end
174       f = api.update_release_file target_proj, target_package, target_release, target_id, visibility: @visibility
175       logger.info "file #{target_id} has been updated."
176       puts format_file(f)
177     end
178
179     def delete
180       target_id = ARGV.shift
181       if !target_id
182         logger.fatal "Target file ID is missing."
183         help
184         return
185       end
186       f = api.delete_release_file target_proj, target_package, target_release, target_id
187       logger.info "file #{target_id} has been deleted."
188     end
189   end
190 end; end; end