OSDN Git Service

Add error handling on conflicting with local file.
[osdn-codes/osdn-cli.git] / lib / osdn / cli / command / frs_upload.rb
1 module OSDN; module CLI; module Command
2   class FrsUpload < Base
3     def help
4       puts "#{$0} frs_upload [opts] [target_dir]"
5       puts "Options:"
6       puts "  -n --dry-run               Do noting (use with global -v to inspect)"
7       puts "  -p --project=<project>     Target project (numeric id or name)"
8       #puts "     --package=<project>     Target package (numeric id)"
9       #puts "     --release=<project>     Target release (numeric id)"
10       puts "  -v --visibility=<public|private|hidden>"
11       puts "                             Default visibility for newly created items"
12       puts "      --force-digest         Calc local file digest forcely"
13     end
14
15     def run
16       update_token
17       opts = GetoptLong.new(
18         [ '--dry-run', '-n', GetoptLong::NO_ARGUMENT ],
19         [ '--project', '-p', GetoptLong::REQUIRED_ARGUMENT ],
20         [ '--release', '-r', GetoptLong::REQUIRED_ARGUMENT ],
21         [ '--visibility', '-v', GetoptLong::REQUIRED_ARGUMENT ],
22         [ '--force-digest', GetoptLong::NO_ARGUMENT],
23       )
24       opts.each do |opt, arg|
25         case opt
26         when '--project'
27           arg.empty? or
28             @target_proj = arg
29         #when '--release'
30         #  arg.empty? or
31         #    @target_release = arg
32         #when '--package'
33         #  arg.empty? or
34         #    @target_package = arg
35         when '--visibility'
36           unless %w(public private hidden).member?(arg)
37             logger.fatal "Invalid visibility status: #{arg}"
38             exit
39           end
40           @visibility = arg
41         when '--force-digest'
42           @force_digest = true
43         when '--dry-run'
44           @dry_run = true
45         end
46       end
47
48       @target_dir = Pathname.new(ARGV.shift || '.')
49       proj_info = api.get_project target_proj # check project existance
50
51       Pathname.glob(@target_dir+'*').sort.each do |pdir|
52         unless load_variables(pdir).package_id
53           logger.info "Createing new package '#{pdir.basename}'"
54           if @dry_run
55             pinfo = Hashie::Mash.new id: '(dry-run)', name: pdir.basename, url: '(dry-run)'
56           else
57             pinfo = api.create_package target_proj, pdir.basename, visibility: @visibility
58             update_variables pdir, package_id: pinfo.id
59           end
60           $stdout.puts "New package '#{pinfo.name}' has been created; #{pinfo.url}"
61         end
62
63         Pathname.glob(pdir + '*').sort.each do |rdir|
64           if !rdir.directory?
65             logger.warn "Skip normal file '#{rdir}' in release level"
66             next
67           end
68
69           vars = load_variables(rdir)
70           rinfo = nil
71           if vars.release_id
72             rinfo = api.get_release target_proj, target_package(rdir), target_release(rdir)
73           else vars.release_id
74             logger.info "Createing new release '#{rdir.basename}'"
75             if @dry_run
76               rinfo = Hashie::Mash.new id: '(dry-run)', name: rdir.basename, url: '(dry-run)', files: []
77             else
78               rinfo = api.create_release target_proj, target_package(rdir), rdir.basename, visibility: @visibility
79               update_variables rdir, release_id: rinfo.id
80             end
81             $stdout.puts "New release '#{rinfo.name}' has been created; #{rinfo.url}"
82           end
83           
84           Pathname.glob(rdir + '*').sort.each do |file|
85             if file.directory?
86               logger.error "Skip direcotry #{file}"
87               next
88             end
89
90             vars = load_variables(rdir)
91             digests = nil
92             if !@force_digest && vars.local_file_info &&
93                vars.local_file_info[file.basename.to_s]
94               finfo = vars.local_file_info[file.basename.to_s]
95               if finfo[:size] == file.size && finfo.mtime == file.mtime
96                 digests = vars.local_file_info[file.basename.to_s].digests
97               end
98             end
99
100             unless digests
101               logger.info "Calculating digest for #{file}..."
102               digests = {
103                 sha256: hexdigest(Digest::SHA256, file),
104                 sha1:   hexdigest(Digest::SHA1, file),
105                 md5:    hexdigest(Digest::MD5, file),
106               }
107               update_variables rdir, {local_file_info: {file.basename.to_s => {digests: digests, mtime: file.mtime, size: file.size}}}
108             end
109             if remote_f = rinfo.files.find { |f| f.name == file.basename.to_s }
110               if digests.find { |type, dig| dig != remote_f.send("digest_#{type}") }
111                 logger.error "#{file} was changed from remote file! Please delete remote file before uploading new one."
112               end
113               logger.info "Skip already uploaded file '#{file}'"
114             else
115               logger.info "Uploading file #{file} (#{file.size} bytes)"
116               if @dry_run
117                 finfo = Hashie::Mash.new id: '(dry-run)', url: '(dry-run)'
118               else
119                 logger.level <= Logger::INFO and
120                   OSDN::CLI._show_progress = true
121                 fio = file.open
122                 logger.info "Starting upload #{file}..."
123                 finfo = api.create_release_file target_proj, target_package(rdir), target_release(rdir), fio, visibility: @visibility
124                 fio.close
125                 OSDN::CLI._show_progress = false
126                 if digests.find { |type, dig| dig != finfo.send("digest_#{type}") }
127                   logger.error "File digests are mismatch! Upload file #{file} may be broken! Please check."
128                 else
129                   logger.info "Upload complete."
130                 end
131               end
132               $stdout.puts "New file '#{file}' has been uploaded; #{finfo.url}"
133             end
134           end
135         end
136       end
137     end
138
139     def self.description
140       "Upload local file tree and create package/release implicitly."
141     end
142
143     private
144     def target_proj
145       @target_proj and return @target_proj
146       vars = load_variables(@target_dir)
147       vars.project && !vars.project.empty? and
148         return vars.project
149       logger.fatal "No target project is specified."
150       exit
151     end
152
153     def target_package(dir)
154       @target_package and return @target_package
155       vars = load_variables(dir)
156       vars.package_id && !vars.package_id.to_s.empty? and
157         return vars.package_id
158       logger.fatal "No target package is specified."
159       exit
160     end
161
162     def target_release(dir)
163       @target_release and return @target_release
164       vars = load_variables(dir)
165       vars.release_id && !vars.release_id.to_s.empty? and
166         return vars.release_id
167       logger.fatal "No target release is specified."
168       exit
169     end
170
171     def api
172       OSDNClient::ProjectApi.new
173     end
174   end
175 end; end; end