OSDN Git Service

f85ea2e32714c2425ecd7d8dfc978aab8a789ed3
[osdn-codes/osdn-cli.git] / lib / osdn / cli / command / frs_upload.rb
1 module OSDN; module CLI; module Command
2   class FrsUpload < Base
3     attr_accessor :target_proj, :visibility, :force_digest, :dry_run, :show_progress
4
5     def help
6       puts "#{$0} frs_upload [opts] [target_dir]"
7       puts "Options:"
8       puts "  -n --dry-run               Do noting (use with global -v to inspect)"
9       puts "  -p --project=<project>     Target project (numeric id or name)"
10       puts "     --package=<package-id>  Target package (numeric id)"
11       puts "     --release=<release-id>  Target release (numeric id)"
12       puts "  -v --visibility=<public|private|hidden>"
13       puts "                             Default visibility for newly created items"
14       puts "      --force-digest         Calc local file digest forcely"
15       puts "      --progress             Force to show upload progress"
16       puts "      --no-progress          Force to hide upload progress"
17       puts "      --bwlimit=RATE         Limit bandwidth (in KB)"
18     end
19
20     def run
21       update_token
22       opts = GetoptLong.new(
23         [ '--dry-run', '-n', GetoptLong::NO_ARGUMENT ],
24         [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ],
25         [ '--package', GetoptLong::REQUIRED_ARGUMENT ],
26         [ '--release', GetoptLong::REQUIRED_ARGUMENT ],
27         [ '--visibility', '-v', GetoptLong::REQUIRED_ARGUMENT ],
28         [ '--force-digest', GetoptLong::NO_ARGUMENT],
29         [ '--progress', GetoptLong::NO_ARGUMENT],
30         [ '--no-progress', GetoptLong::NO_ARGUMENT],
31         [ '--bwlimit', GetoptLong::REQUIRED_ARGUMENT ],
32       )
33       opts.each do |opt, arg|
34         case opt
35         when '--project'
36           arg.empty? or
37             @target_proj = arg
38         when '--release'
39           arg.empty? or
40             @target_release = arg
41         when '--package'
42           arg.empty? or
43             @target_package = arg
44         when '--visibility'
45           unless %w(public private hidden).member?(arg)
46             logger.fatal "Invalid visibility status: #{arg}"
47             exit
48           end
49           @visibility = arg
50         when '--force-digest'
51           @force_digest = true
52         when '--dry-run'
53           @dry_run = true
54         when '--progress'
55           @show_progress = true
56         when '--no-progress'
57           @show_progress = false
58         when '--bwlimit'
59           arg.to_i != 0 and
60             OSDN::CLI._rate_limit = arg.to_i * 1024
61         end
62       end
63
64       (ARGV.empty? ? ['.'] : ARGV).each do |d|
65         @target_dir = Pathname.new(d)
66         process_target
67       end
68     end
69
70     def process_target
71       proj_info = api.get_project target_proj # check project existance
72
73       vars = load_variables(@target_dir)
74       parent_vars = load_variables(@target_dir.parent)
75
76       if @target_release || vars.release_id ||
77          parent_vars.package_id && !vars.release_id # new release case...
78         process_release(@target_dir)
79       elsif @target_package || vars.package_id
80         process_package(@target_dir)
81       else
82         Pathname.glob(@target_dir+'*').sort.each do |pdir|
83           process_package(pdir)
84         end
85       end
86     end
87
88     def self.description
89       "Upload local file tree and create package/release implicitly."
90     end
91
92     def process_package(pdir)
93       if cur_pkgid = load_variables(pdir).package_id
94         # check package existance on server
95         begin
96           api.get_package target_proj, target_package(pdir)
97         rescue OSDNClient::ApiError => e
98           begin
99             err = JSON.parse(e.response_body)
100           rescue
101             raise e
102           end
103           if err['status'] == 404
104             logger.warn "Package ##{cur_pkgid} has been deleted on server and local directory '#{pdir}' remains. You can delete the local directory or delete '#{pdir}/.osdn.vars' file to create a package again with new ID."
105             return false
106           end
107           raise e
108         end
109       else
110         logger.info "Createing new package '#{pdir.basename}'"
111         if @dry_run
112           pinfo = Hashie::Mash.new id: '(dry-run)', name: pdir.basename, url: '(dry-run)'
113         else
114           pinfo = api.create_package target_proj, pdir.basename, visibility: @visibility
115           update_variables pdir, package_id: pinfo.id
116         end
117         $stdout.puts "New package '#{pinfo.name}' has been created; #{pinfo.url}"
118       end
119
120       Pathname.glob(pdir + '*').sort.each do |rdir|
121         process_release(rdir)
122       end
123     end
124
125     def process_release(rdir)
126       if !rdir.directory?
127         logger.warn "Skip normal file '#{rdir}' in release level"
128         return false
129       end
130
131       vars = load_variables(rdir)
132       rinfo = nil
133       if vars.release_id
134         begin
135           rinfo = api.get_release target_proj, target_package(rdir), target_release(rdir)
136         rescue OSDNClient::ApiError => e
137           begin
138             err = JSON.parse(e.response_body)
139           rescue
140             raise e
141           end
142           if err['status'] == 404
143             logger.warn "Release ##{vars.release_id} has been deleted on server and local directory '#{rdir}' remains. You can delete the local directory or delete '#{rdir}/.osdn.vars' file to create a release again with new ID."
144             return false
145           end
146           raise e
147         end
148       else
149         logger.info "Createing new release '#{rdir.basename}'"
150         if @dry_run
151           rinfo = Hashie::Mash.new id: '(dry-run)', name: rdir.basename, url: '(dry-run)', files: []
152         else
153           rinfo = api.create_release target_proj, target_package(rdir), rdir.basename, visibility: @visibility
154           update_variables rdir, release_id: rinfo.id
155         end
156         $stdout.puts "New release '#{rinfo.name}' has been created; #{rinfo.url}"
157       end
158       Pathname.glob(rdir + '*').sort.each do |file|
159         process_file(file, rdir, rinfo)
160       end
161     end
162
163     def process_file(file, rdir, rinfo)
164       if file.directory?
165         logger.error "Skip direcotry #{file}"
166         return false
167       end
168
169       filecmd = Relfile.new logger
170       [:target_proj, :visibility, :force_digest, :show_progress].each do |opt|
171         filecmd.send "#{opt}=", send(opt)
172       end
173       filecmd.target_package = target_package(rdir)
174       filecmd.target_release = target_release(rdir)
175       filecmd.calc_file_digest(file)
176
177       vars = load_variables(rdir)
178       digests = vars.local_file_info[file.basename.to_s].digests
179
180       if remote_f = rinfo.files.find { |f| f.name == file.basename.to_s }
181         if remote_f.size != file.size || digests.find { |type, dig| rd = remote_f.send("digest_#{type}"); rd && rd != '' && dig != rd }
182           logger.error "#{file} was changed from remote file! Please delete remote file before uploading new one."
183         end
184         logger.info "Skip already uploaded file '#{file}'"
185         return
186       end
187
188       finfo = {}
189       logger.info "Uploading file #{file} (#{file.size} bytes)"
190       if @dry_run
191         finfo = Hashie::Mash.new id: '(dry-run)', url: '(dry-run)'
192       else
193         finfo = filecmd.create_one file
194       end
195       $stdout.puts "New file '#{file}' has been uploaded; #{finfo.url}"
196     end
197     
198     private
199     def target_proj
200       @target_proj and return @target_proj
201       vars = load_variables(@target_dir)
202       vars.project && !vars.project.empty? and
203         return vars.project
204       logger.fatal "No target project is specified."
205       exit
206     end
207
208     def target_package(dir)
209       @target_package and return @target_package
210       vars = load_variables(dir)
211       vars.package_id && !vars.package_id.to_s.empty? and
212         return vars.package_id
213       logger.fatal "No target package is specified."
214       exit
215     end
216
217     def target_release(dir)
218       @target_release and return @target_release
219       vars = load_variables(dir)
220       vars.release_id && !vars.release_id.to_s.empty? and
221         return vars.release_id
222       logger.fatal "No target release is specified."
223       exit
224     end
225
226     def api
227       OSDNClient::ProjectApi.new
228     end
229   end
230 end; end; end