cos 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.gitignore +12 -0
 - data/.rspec +2 -0
 - data/.travis.yml +13 -2
 - data/Gemfile +4 -1
 - data/LICENSE +191 -0
 - data/README.md +2014 -17
 - data/Rakefile +23 -6
 - data/bin/cos +325 -0
 - data/bin/setup +1 -3
 - data/cos.gemspec +24 -13
 - data/lib/cos.rb +41 -4
 - data/lib/cos/api.rb +289 -0
 - data/lib/cos/bucket.rb +731 -0
 - data/lib/cos/checkpoint.rb +62 -0
 - data/lib/cos/client.rb +58 -0
 - data/lib/cos/config.rb +102 -0
 - data/lib/cos/dir.rb +301 -0
 - data/lib/cos/download.rb +252 -0
 - data/lib/cos/exception.rb +62 -0
 - data/lib/cos/file.rb +152 -0
 - data/lib/cos/http.rb +95 -0
 - data/lib/cos/logging.rb +47 -0
 - data/lib/cos/resource.rb +201 -0
 - data/lib/cos/signature.rb +119 -0
 - data/lib/cos/slice.rb +292 -0
 - data/lib/cos/struct.rb +49 -0
 - data/lib/cos/tree.rb +165 -0
 - data/lib/cos/util.rb +82 -0
 - data/lib/cos/version.rb +2 -2
 - data/spec/cos/bucket_spec.rb +562 -0
 - data/spec/cos/client_spec.rb +77 -0
 - data/spec/cos/dir_spec.rb +195 -0
 - data/spec/cos/download_spec.rb +105 -0
 - data/spec/cos/http_spec.rb +70 -0
 - data/spec/cos/signature_spec.rb +83 -0
 - data/spec/cos/slice_spec.rb +302 -0
 - data/spec/cos/struct_spec.rb +38 -0
 - data/spec/cos/tree_spec.rb +322 -0
 - data/spec/cos/util_spec.rb +106 -0
 - data/test/download_test.rb +44 -0
 - data/test/list_test.rb +43 -0
 - data/test/upload_test.rb +48 -0
 - metadata +132 -21
 - data/.idea/.name +0 -1
 - data/.idea/cos.iml +0 -49
 - data/.idea/encodings.xml +0 -6
 - data/.idea/misc.xml +0 -14
 - data/.idea/modules.xml +0 -8
 - data/.idea/workspace.xml +0 -465
 - data/bin/console +0 -14
 
    
        data/lib/cos/slice.rb
    ADDED
    
    | 
         @@ -0,0 +1,292 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # coding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'tempfile'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module COS
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              # 分片大文件上传, 支持断点续传, 支持多线程
         
     | 
| 
      
 8 
     | 
    
         
            +
              class Slice < Checkpoint
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                include Logging
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                # 默认分片大小 3M
         
     | 
| 
      
 13 
     | 
    
         
            +
                DEFAULT_SLICE_SIZE = 3 * 1024 * 1024
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                required_attrs :config, :http, :path, :file_name, :file_src, :options
         
     | 
| 
      
 16 
     | 
    
         
            +
                optional_attrs :progress
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                attr_accessor :cpt_file, :result, :offset, :slice_size, :session
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def initialize(opts = {})
         
     | 
| 
      
 21 
     | 
    
         
            +
                  super(opts)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  @cpt_file = options[:cpt_file] || "#{File.expand_path(file_src)}.cpt"
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # 开始上传
         
     | 
| 
      
 27 
     | 
    
         
            +
                def upload
         
     | 
| 
      
 28 
     | 
    
         
            +
                  logger.info("Begin upload, file: #{file_src}, threads: #{@num_threads}")
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  # 重建断点续传或重新从服务器初始化分片上传
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # 有可能sha命中直接完成
         
     | 
| 
      
 32 
     | 
    
         
            +
                  data = rebuild
         
     | 
| 
      
 33 
     | 
    
         
            +
                  return data if data
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  # 文件分片
         
     | 
| 
      
 36 
     | 
    
         
            +
                  divide_parts if @parts.empty?
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  # 未完成的片段
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @todo_parts = @parts.reject { |p| p[:done] }
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 42 
     | 
    
         
            +
                    # 多线程上传
         
     | 
| 
      
 43 
     | 
    
         
            +
                    (1..@num_threads).map do
         
     | 
| 
      
 44 
     | 
    
         
            +
                      Thread.new do
         
     | 
| 
      
 45 
     | 
    
         
            +
                        loop do
         
     | 
| 
      
 46 
     | 
    
         
            +
                          # 获取下一个未上传的片段
         
     | 
| 
      
 47 
     | 
    
         
            +
                          p = sync_get_todo_part
         
     | 
| 
      
 48 
     | 
    
         
            +
                          break unless p
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                          # 上传片段
         
     | 
| 
      
 51 
     | 
    
         
            +
                          upload_part(p)
         
     | 
| 
      
 52 
     | 
    
         
            +
                        end
         
     | 
| 
      
 53 
     | 
    
         
            +
                      end
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end.map(&:join)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  rescue => error
         
     | 
| 
      
 56 
     | 
    
         
            +
                    unless finish?
         
     | 
| 
      
 57 
     | 
    
         
            +
                      # 部分服务端异常需要重新初始化, 可能上传已经完成了
         
     | 
| 
      
 58 
     | 
    
         
            +
                      if error.is_a?(ServerError) and error.error_code == -288
         
     | 
| 
      
 59 
     | 
    
         
            +
                        File.delete(cpt_file) unless options[:disable_cpt]
         
     | 
| 
      
 60 
     | 
    
         
            +
                      end
         
     | 
| 
      
 61 
     | 
    
         
            +
                      raise error
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  # 返回100%的进度
         
     | 
| 
      
 66 
     | 
    
         
            +
                  progress.call(1.to_f) if progress
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  # 上传完成, 删除checkpoint文件
         
     | 
| 
      
 69 
     | 
    
         
            +
                  File.delete(cpt_file) unless options[:disable_cpt]
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  logger.info("Done upload, file: #{@file_src}")
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  # 返回文件信息
         
     | 
| 
      
 74 
     | 
    
         
            +
                  result
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                # 断点续传状态记录
         
     | 
| 
      
 78 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 79 
     | 
    
         
            +
                #   states = {
         
     | 
| 
      
 80 
     | 
    
         
            +
                #     :session => 'session',
         
     | 
| 
      
 81 
     | 
    
         
            +
                #     :offset => 0,
         
     | 
| 
      
 82 
     | 
    
         
            +
                #     :slice_size => 2048,
         
     | 
| 
      
 83 
     | 
    
         
            +
                #     :file => 'file',
         
     | 
| 
      
 84 
     | 
    
         
            +
                #     :file_meta => {
         
     | 
| 
      
 85 
     | 
    
         
            +
                #       :mtime => Time.now,
         
     | 
| 
      
 86 
     | 
    
         
            +
                #       :sha1 => 'file sha1',
         
     | 
| 
      
 87 
     | 
    
         
            +
                #       :size => 10000,
         
     | 
| 
      
 88 
     | 
    
         
            +
                #     },
         
     | 
| 
      
 89 
     | 
    
         
            +
                #     :parts => [
         
     | 
| 
      
 90 
     | 
    
         
            +
                #       {:number => 1, :range => [0, 100], :done => false},
         
     | 
| 
      
 91 
     | 
    
         
            +
                #       {:number => 2, :range => [100, 200], :done => true}
         
     | 
| 
      
 92 
     | 
    
         
            +
                #     ],
         
     | 
| 
      
 93 
     | 
    
         
            +
                #     :sha1 => 'checkpoint file sha1'
         
     | 
| 
      
 94 
     | 
    
         
            +
                #   }
         
     | 
| 
      
 95 
     | 
    
         
            +
                def checkpoint
         
     | 
| 
      
 96 
     | 
    
         
            +
                  logger.debug("Make checkpoint, options[:disable_cpt]: #{options[:disable_cpt] == true}")
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                  ensure_file_not_changed
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                  parts = sync_get_all_parts
         
     | 
| 
      
 101 
     | 
    
         
            +
                  states = {
         
     | 
| 
      
 102 
     | 
    
         
            +
                      :session    => session,
         
     | 
| 
      
 103 
     | 
    
         
            +
                      :slice_size => slice_size,
         
     | 
| 
      
 104 
     | 
    
         
            +
                      :offset     => offset,
         
     | 
| 
      
 105 
     | 
    
         
            +
                      :file       => file_src,
         
     | 
| 
      
 106 
     | 
    
         
            +
                      :file_meta  => @file_meta,
         
     | 
| 
      
 107 
     | 
    
         
            +
                      :parts      => parts
         
     | 
| 
      
 108 
     | 
    
         
            +
                  }
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                  done = parts.count { |p| p[:done] }
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                  # 上传进度回调
         
     | 
| 
      
 113 
     | 
    
         
            +
                  if progress
         
     | 
| 
      
 114 
     | 
    
         
            +
                    percent = (offset + done*slice_size).to_f / states[:file_meta][:size]
         
     | 
| 
      
 115 
     | 
    
         
            +
                    progress.call(percent > 1 ? 1.to_f : percent)
         
     | 
| 
      
 116 
     | 
    
         
            +
                  end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                  write_checkpoint(states, cpt_file) unless options[:disable_cpt]
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                  logger.debug("Upload Parts #{done}/#{states[:parts].count}")
         
     | 
| 
      
 121 
     | 
    
         
            +
                end
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                private
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                # 是否完成上传
         
     | 
| 
      
 126 
     | 
    
         
            +
                def finish?
         
     | 
| 
      
 127 
     | 
    
         
            +
                  result != nil and result[:access_url] != nil
         
     | 
| 
      
 128 
     | 
    
         
            +
                end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                # 断点续传文件重建
         
     | 
| 
      
 131 
     | 
    
         
            +
                def rebuild
         
     | 
| 
      
 132 
     | 
    
         
            +
                  logger.info("Begin rebuild session, checkpoint: #{cpt_file}")
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                  # 是否启用断点续传并且记录文件存在
         
     | 
| 
      
 135 
     | 
    
         
            +
                  if options[:disable_cpt] || !File.exist?(cpt_file)
         
     | 
| 
      
 136 
     | 
    
         
            +
                    # 从服务器初始化
         
     | 
| 
      
 137 
     | 
    
         
            +
                    data = initiate
         
     | 
| 
      
 138 
     | 
    
         
            +
                    return data if data
         
     | 
| 
      
 139 
     | 
    
         
            +
                  else
         
     | 
| 
      
 140 
     | 
    
         
            +
                    # 加载断点续传
         
     | 
| 
      
 141 
     | 
    
         
            +
                    states = load_checkpoint(cpt_file)
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                    # 确保上传的文件未变化
         
     | 
| 
      
 144 
     | 
    
         
            +
                    if states[:file_sha1] != @file_meta[:sha1]
         
     | 
| 
      
 145 
     | 
    
         
            +
                      raise FileInconsistentError, 'The file to upload is changed'
         
     | 
| 
      
 146 
     | 
    
         
            +
                    end
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                    @session    = states[:session]
         
     | 
| 
      
 149 
     | 
    
         
            +
                    @file_meta  = states[:file_meta]
         
     | 
| 
      
 150 
     | 
    
         
            +
                    @parts      = states[:parts]
         
     | 
| 
      
 151 
     | 
    
         
            +
                    @slice_size = states[:slice_size]
         
     | 
| 
      
 152 
     | 
    
         
            +
                    @offset     = states[:offset]
         
     | 
| 
      
 153 
     | 
    
         
            +
                  end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                  logger.info("Done rebuild session, Parts: #{@parts.count}")
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                  false
         
     | 
| 
      
 158 
     | 
    
         
            +
                end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                # 初始化分块上传
         
     | 
| 
      
 161 
     | 
    
         
            +
                def initiate
         
     | 
| 
      
 162 
     | 
    
         
            +
                  logger.info('Begin initiate session')
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
                  bucket        = config.get_bucket(options[:bucket])
         
     | 
| 
      
 165 
     | 
    
         
            +
                  sign          = http.signature.multiple(bucket)
         
     | 
| 
      
 166 
     | 
    
         
            +
                  resource_path = Util.get_resource_path(config.app_id, bucket, path, file_name)
         
     | 
| 
      
 167 
     | 
    
         
            +
                  file_size     = File.size(file_src)
         
     | 
| 
      
 168 
     | 
    
         
            +
                  file_sha1     = Util.file_sha1(file_src)
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                  payload = {
         
     | 
| 
      
 171 
     | 
    
         
            +
                      op:          'upload_slice',
         
     | 
| 
      
 172 
     | 
    
         
            +
                      slice_size:  options[:slice_size] || DEFAULT_SLICE_SIZE,
         
     | 
| 
      
 173 
     | 
    
         
            +
                      sha:         file_sha1,
         
     | 
| 
      
 174 
     | 
    
         
            +
                      filesize:    file_size,
         
     | 
| 
      
 175 
     | 
    
         
            +
                      biz_attr:    options[:biz_attr],
         
     | 
| 
      
 176 
     | 
    
         
            +
                      session:     session,
         
     | 
| 
      
 177 
     | 
    
         
            +
                      multipart:   true
         
     | 
| 
      
 178 
     | 
    
         
            +
                  }
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                  resp = http.post(resource_path, {}, sign, payload)
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                  # 上一次已传完或秒传成功
         
     | 
| 
      
 183 
     | 
    
         
            +
                  return resp if resp[:access_url]
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                  @session    = resp[:session]
         
     | 
| 
      
 186 
     | 
    
         
            +
                  @slice_size = resp[:slice_size]
         
     | 
| 
      
 187 
     | 
    
         
            +
                  @offset     = resp[:offset]
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                  @file_meta = {
         
     | 
| 
      
 190 
     | 
    
         
            +
                      :mtime => File.mtime(file_src),
         
     | 
| 
      
 191 
     | 
    
         
            +
                      :sha1  => file_sha1,
         
     | 
| 
      
 192 
     | 
    
         
            +
                      :size  => file_size
         
     | 
| 
      
 193 
     | 
    
         
            +
                  }
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
                  # 保存断点
         
     | 
| 
      
 196 
     | 
    
         
            +
                  checkpoint
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
                  logger.info("Done initiate session: #{@session}")
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                  false
         
     | 
| 
      
 201 
     | 
    
         
            +
                end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                # 上传块
         
     | 
| 
      
 204 
     | 
    
         
            +
                def upload_part(p)
         
     | 
| 
      
 205 
     | 
    
         
            +
                  logger.debug("Begin upload slice: #{p}")
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
                  bucket        = config.get_bucket(options[:bucket])
         
     | 
| 
      
 208 
     | 
    
         
            +
                  sign          = http.signature.multiple(bucket)
         
     | 
| 
      
 209 
     | 
    
         
            +
                  resource_path = Util.get_resource_path(config.app_id, bucket, path, file_name)
         
     | 
| 
      
 210 
     | 
    
         
            +
                  temp_file     = Tempfile.new("#{session}-#{p[:number]}")
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 213 
     | 
    
         
            +
                    # 复制文件分片至临时文件
         
     | 
| 
      
 214 
     | 
    
         
            +
                    IO.copy_stream(file_src, temp_file, p[:range].at(1) - p[:range].at(0), p[:range].at(0))
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
                    payload = {
         
     | 
| 
      
 217 
     | 
    
         
            +
                        op:          'upload_slice',
         
     | 
| 
      
 218 
     | 
    
         
            +
                        sha:         Util.file_sha1(temp_file),
         
     | 
| 
      
 219 
     | 
    
         
            +
                        offset:      p[:range].at(0),
         
     | 
| 
      
 220 
     | 
    
         
            +
                        session:     session,
         
     | 
| 
      
 221 
     | 
    
         
            +
                        filecontent: temp_file,
         
     | 
| 
      
 222 
     | 
    
         
            +
                        multipart:   true
         
     | 
| 
      
 223 
     | 
    
         
            +
                    }
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                    re = http.post(resource_path, {}, sign, payload)
         
     | 
| 
      
 226 
     | 
    
         
            +
                    @result = re if re[:access_url]
         
     | 
| 
      
 227 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 228 
     | 
    
         
            +
                    # 确保清除临时文件
         
     | 
| 
      
 229 
     | 
    
         
            +
                    temp_file.close
         
     | 
| 
      
 230 
     | 
    
         
            +
                    temp_file.unlink
         
     | 
| 
      
 231 
     | 
    
         
            +
                  end
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
                  sync_update_part(p.merge(done: true))
         
     | 
| 
      
 234 
     | 
    
         
            +
             
     | 
| 
      
 235 
     | 
    
         
            +
                  checkpoint
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
                  logger.debug("Done upload part: #{p}")
         
     | 
| 
      
 238 
     | 
    
         
            +
                end
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
                # 文件片段拆分
         
     | 
| 
      
 241 
     | 
    
         
            +
                def divide_parts
         
     | 
| 
      
 242 
     | 
    
         
            +
                  logger.info("Begin divide parts, file: #{file_src}")
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
                  file_size = File.size(file_src)
         
     | 
| 
      
 245 
     | 
    
         
            +
                  num_parts = (file_size - offset - 1) / slice_size + 1
         
     | 
| 
      
 246 
     | 
    
         
            +
                  @parts = (1..num_parts).map do |i|
         
     | 
| 
      
 247 
     | 
    
         
            +
                    {
         
     | 
| 
      
 248 
     | 
    
         
            +
                        :number => i,
         
     | 
| 
      
 249 
     | 
    
         
            +
                        :range  => [offset + (i-1) * slice_size, [offset + i * slice_size, file_size].min],
         
     | 
| 
      
 250 
     | 
    
         
            +
                        :done   => false
         
     | 
| 
      
 251 
     | 
    
         
            +
                    }
         
     | 
| 
      
 252 
     | 
    
         
            +
                  end
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
                  checkpoint
         
     | 
| 
      
 255 
     | 
    
         
            +
             
     | 
| 
      
 256 
     | 
    
         
            +
                  logger.info("Done divide parts, parts: #{@parts.size}")
         
     | 
| 
      
 257 
     | 
    
         
            +
                end
         
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
                # 同步获取下一片段
         
     | 
| 
      
 260 
     | 
    
         
            +
                def sync_get_todo_part
         
     | 
| 
      
 261 
     | 
    
         
            +
                  @todo_mutex.synchronize {
         
     | 
| 
      
 262 
     | 
    
         
            +
                    @todo_parts.shift
         
     | 
| 
      
 263 
     | 
    
         
            +
                  }
         
     | 
| 
      
 264 
     | 
    
         
            +
                end
         
     | 
| 
      
 265 
     | 
    
         
            +
             
     | 
| 
      
 266 
     | 
    
         
            +
                # 同步更新片段
         
     | 
| 
      
 267 
     | 
    
         
            +
                def sync_update_part(p)
         
     | 
| 
      
 268 
     | 
    
         
            +
                  @all_mutex.synchronize {
         
     | 
| 
      
 269 
     | 
    
         
            +
                    @parts[p[:number] - 1] = p
         
     | 
| 
      
 270 
     | 
    
         
            +
                  }
         
     | 
| 
      
 271 
     | 
    
         
            +
                end
         
     | 
| 
      
 272 
     | 
    
         
            +
             
     | 
| 
      
 273 
     | 
    
         
            +
                # 同步获取所有片段
         
     | 
| 
      
 274 
     | 
    
         
            +
                def sync_get_all_parts
         
     | 
| 
      
 275 
     | 
    
         
            +
                  @all_mutex.synchronize {
         
     | 
| 
      
 276 
     | 
    
         
            +
                    @parts.dup
         
     | 
| 
      
 277 
     | 
    
         
            +
                  }
         
     | 
| 
      
 278 
     | 
    
         
            +
                end
         
     | 
| 
      
 279 
     | 
    
         
            +
             
     | 
| 
      
 280 
     | 
    
         
            +
                # 确保上传中文件没有变化
         
     | 
| 
      
 281 
     | 
    
         
            +
                def ensure_file_not_changed
         
     | 
| 
      
 282 
     | 
    
         
            +
                  return if File.mtime(file_src) == @file_meta[:mtime]
         
     | 
| 
      
 283 
     | 
    
         
            +
             
     | 
| 
      
 284 
     | 
    
         
            +
                  if @file_meta[:sha1] != Util.file_sha1(file_src)
         
     | 
| 
      
 285 
     | 
    
         
            +
                    # p Util.file_sha1(file_src)
         
     | 
| 
      
 286 
     | 
    
         
            +
                    raise FileInconsistentError, 'The file to upload is changed'
         
     | 
| 
      
 287 
     | 
    
         
            +
                  end
         
     | 
| 
      
 288 
     | 
    
         
            +
                end
         
     | 
| 
      
 289 
     | 
    
         
            +
             
     | 
| 
      
 290 
     | 
    
         
            +
              end
         
     | 
| 
      
 291 
     | 
    
         
            +
             
     | 
| 
      
 292 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/cos/struct.rb
    ADDED
    
    | 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # coding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module COS
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                module Struct
         
     | 
| 
      
 6 
     | 
    
         
            +
                  class Base
         
     | 
| 
      
 7 
     | 
    
         
            +
                    module AttrHelper
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                      # 动态创建必选参数
         
     | 
| 
      
 10 
     | 
    
         
            +
                      def required_attrs(*s)
         
     | 
| 
      
 11 
     | 
    
         
            +
                        define_method(:required_attrs) {s}
         
     | 
| 
      
 12 
     | 
    
         
            +
                        attr_reader(*s)
         
     | 
| 
      
 13 
     | 
    
         
            +
                      end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                      # 动态创建可选参数
         
     | 
| 
      
 16 
     | 
    
         
            +
                      def optional_attrs(*s)
         
     | 
| 
      
 17 
     | 
    
         
            +
                        define_method(:optional_attrs) {s}
         
     | 
| 
      
 18 
     | 
    
         
            +
                        attr_reader(*s)
         
     | 
| 
      
 19 
     | 
    
         
            +
                      end
         
     | 
| 
      
 20 
     | 
    
         
            +
                    end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    extend AttrHelper
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    def initialize(options = {})
         
     | 
| 
      
 25 
     | 
    
         
            +
                      # 意外参数检测
         
     | 
| 
      
 26 
     | 
    
         
            +
                      unless optional_attrs.include?(:SKIP_EXTRA)
         
     | 
| 
      
 27 
     | 
    
         
            +
                        extra_keys = options.keys - required_attrs - optional_attrs
         
     | 
| 
      
 28 
     | 
    
         
            +
                        unless extra_keys.empty?
         
     | 
| 
      
 29 
     | 
    
         
            +
                          raise AttrError, "Unexpected extra keys: #{extra_keys.join(', ')}"
         
     | 
| 
      
 30 
     | 
    
         
            +
                        end
         
     | 
| 
      
 31 
     | 
    
         
            +
                      end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                      # 必选参数检测
         
     | 
| 
      
 34 
     | 
    
         
            +
                      required_keys = required_attrs - options.keys
         
     | 
| 
      
 35 
     | 
    
         
            +
                      unless required_keys.empty?
         
     | 
| 
      
 36 
     | 
    
         
            +
                        raise AttrError, "Keys: #{required_keys.join(', ')} is Required"
         
     | 
| 
      
 37 
     | 
    
         
            +
                      end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                      # 动态创建实例变量
         
     | 
| 
      
 40 
     | 
    
         
            +
                      (required_attrs + optional_attrs).each do |attr|
         
     | 
| 
      
 41 
     | 
    
         
            +
                        instance_variable_set("@#{attr}", options[attr])
         
     | 
| 
      
 42 
     | 
    
         
            +
                      end
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
    
        data/lib/cos/tree.rb
    ADDED
    
    | 
         @@ -0,0 +1,165 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # coding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module COS
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              class Tree < Struct::Base
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                MAX_DEPTH = 5
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                required_attrs :path
         
     | 
| 
      
 10 
     | 
    
         
            +
                optional_attrs :depth, :files_count, :files
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def initialize(options = {})
         
     | 
| 
      
 13 
     | 
    
         
            +
                  super(options)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @tree_str = ''
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @depth    = depth || MAX_DEPTH
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # 输出Object格式, 可以直接用于链式调用
         
     | 
| 
      
 19 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 20 
     | 
    
         
            +
                # {
         
     | 
| 
      
 21 
     | 
    
         
            +
                #   :resource => resource,
         
     | 
| 
      
 22 
     | 
    
         
            +
                #   :children => [
         
     | 
| 
      
 23 
     | 
    
         
            +
                #     {:resource => resource, :children => [...]},
         
     | 
| 
      
 24 
     | 
    
         
            +
                #     {:resource => resource, :children => [...]},
         
     | 
| 
      
 25 
     | 
    
         
            +
                #     ...
         
     | 
| 
      
 26 
     | 
    
         
            +
                #   ]
         
     | 
| 
      
 27 
     | 
    
         
            +
                # }
         
     | 
| 
      
 28 
     | 
    
         
            +
                def to_object
         
     | 
| 
      
 29 
     | 
    
         
            +
                  create_tree(path, [], :object)
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                # 输出Hash格式, 可以直接.to_json转化为json string
         
     | 
| 
      
 33 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 34 
     | 
    
         
            +
                # {
         
     | 
| 
      
 35 
     | 
    
         
            +
                #   :resource => {name: '', mtime: ''...},
         
     | 
| 
      
 36 
     | 
    
         
            +
                #   :children => [
         
     | 
| 
      
 37 
     | 
    
         
            +
                #     {:resource => resource, :children => [...]},
         
     | 
| 
      
 38 
     | 
    
         
            +
                #     {:resource => resource, :children => [...]},
         
     | 
| 
      
 39 
     | 
    
         
            +
                #     ...
         
     | 
| 
      
 40 
     | 
    
         
            +
                #   ]
         
     | 
| 
      
 41 
     | 
    
         
            +
                # }
         
     | 
| 
      
 42 
     | 
    
         
            +
                def to_hash
         
     | 
| 
      
 43 
     | 
    
         
            +
                  create_tree(path, [], :hash)
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                # 命令行打印树结构
         
     | 
| 
      
 47 
     | 
    
         
            +
                def print_tree
         
     | 
| 
      
 48 
     | 
    
         
            +
                  create_tree(path, [])
         
     | 
| 
      
 49 
     | 
    
         
            +
                  puts @tree_str
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                private
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                # 递归创建树
         
     | 
| 
      
 55 
     | 
    
         
            +
                def create_tree(dir, level, type = nil)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  @tree_str << row_string_for_dir(dir, level) if type.nil?
         
     | 
| 
      
 57 
     | 
    
         
            +
                  children = []
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  if level.count < depth and dir.is_a?(COS::COSDir)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    cd = child_directories(dir)
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    i = 0
         
     | 
| 
      
 63 
     | 
    
         
            +
                    while i < cd.count  do
         
     | 
| 
      
 64 
     | 
    
         
            +
                      level_dup = level.dup
         
     | 
| 
      
 65 
     | 
    
         
            +
                      is_last = i + 1 == cd.count
         
     | 
| 
      
 66 
     | 
    
         
            +
                      la = level_dup << is_last
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                      ct = create_tree(cd[i], la, type)
         
     | 
| 
      
 69 
     | 
    
         
            +
                      children << ct if type != nil
         
     | 
| 
      
 70 
     | 
    
         
            +
                      i += 1
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                  if type != nil
         
     | 
| 
      
 75 
     | 
    
         
            +
                    if type == :hash
         
     | 
| 
      
 76 
     | 
    
         
            +
                      resource = dir.to_hash
         
     | 
| 
      
 77 
     | 
    
         
            +
                    else
         
     | 
| 
      
 78 
     | 
    
         
            +
                      resource = dir
         
     | 
| 
      
 79 
     | 
    
         
            +
                    end
         
     | 
| 
      
 80 
     | 
    
         
            +
                    {resource: resource, children: children}
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                # 获取子目录结构
         
     | 
| 
      
 85 
     | 
    
         
            +
                def child_directories(dir)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  dirs = []
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                  pattern = @files ? :both : :dir_only
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  dir.list(pattern: pattern).each do |d|
         
     | 
| 
      
 91 
     | 
    
         
            +
                    if d.is_a?(COS::COSDir)
         
     | 
| 
      
 92 
     | 
    
         
            +
                      dirs << d
         
     | 
| 
      
 93 
     | 
    
         
            +
                    else
         
     | 
| 
      
 94 
     | 
    
         
            +
                      dirs << d if @files
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
                  end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                  dirs
         
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                # 打印输出目录行
         
     | 
| 
      
 102 
     | 
    
         
            +
                def row_string_for_dir(dir, level)
         
     | 
| 
      
 103 
     | 
    
         
            +
                  if dir.name == ''
         
     | 
| 
      
 104 
     | 
    
         
            +
                    # 根目录显示Bucket
         
     | 
| 
      
 105 
     | 
    
         
            +
                    dirname = "Bucket #{dir.bucket.bucket_name}"
         
     | 
| 
      
 106 
     | 
    
         
            +
                  else
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                    if dir.is_a?(COS::COSDir)
         
     | 
| 
      
 109 
     | 
    
         
            +
                      dirname = "#{dir.name}"
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                      if @files_count
         
     | 
| 
      
 112 
     | 
    
         
            +
                        counts = dir.count_files
         
     | 
| 
      
 113 
     | 
    
         
            +
                        dirname << " \033[32m(#{counts})\033[0m" if counts > 0
         
     | 
| 
      
 114 
     | 
    
         
            +
                      end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                    else
         
     | 
| 
      
 117 
     | 
    
         
            +
                      dirname = "\033[34m#{dir.name}\033[0m"
         
     | 
| 
      
 118 
     | 
    
         
            +
                      dirname << " \033[31m(#{dir.format_size})\033[0m"
         
     | 
| 
      
 119 
     | 
    
         
            +
                    end
         
     | 
| 
      
 120 
     | 
    
         
            +
                  end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                  dirname << " \033[35m[#{dir.biz_attr}]\033[0m" if dir.biz_attr != ''
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                  row_str = ''
         
     | 
| 
      
 125 
     | 
    
         
            +
                  row_str << level_header_for_row(level)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  row_str << dirname
         
     | 
| 
      
 127 
     | 
    
         
            +
                  row_str << "\n"
         
     | 
| 
      
 128 
     | 
    
         
            +
                end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                # 打印输出层级关系
         
     | 
| 
      
 131 
     | 
    
         
            +
                def level_header_for_row(level)
         
     | 
| 
      
 132 
     | 
    
         
            +
                  header_str = "\033[33m"
         
     | 
| 
      
 133 
     | 
    
         
            +
                  lc = level.count
         
     | 
| 
      
 134 
     | 
    
         
            +
                  if lc > 0
         
     | 
| 
      
 135 
     | 
    
         
            +
                    i = 0
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                    while i < lc
         
     | 
| 
      
 138 
     | 
    
         
            +
                      if i + 1 == lc
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                        if level[i]
         
     | 
| 
      
 141 
     | 
    
         
            +
                          header_str << "└── "
         
     | 
| 
      
 142 
     | 
    
         
            +
                        else
         
     | 
| 
      
 143 
     | 
    
         
            +
                          header_str << "├── "
         
     | 
| 
      
 144 
     | 
    
         
            +
                        end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                      else
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                        if level[i]
         
     | 
| 
      
 149 
     | 
    
         
            +
                          header_str << "    "
         
     | 
| 
      
 150 
     | 
    
         
            +
                        else
         
     | 
| 
      
 151 
     | 
    
         
            +
                          header_str << "│   "
         
     | 
| 
      
 152 
     | 
    
         
            +
                        end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                      end
         
     | 
| 
      
 155 
     | 
    
         
            +
                      i += 1
         
     | 
| 
      
 156 
     | 
    
         
            +
                    end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                  end
         
     | 
| 
      
 159 
     | 
    
         
            +
                  header_str << "\033[0m"
         
     | 
| 
      
 160 
     | 
    
         
            +
                  header_str
         
     | 
| 
      
 161 
     | 
    
         
            +
                end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
              end
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
            end
         
     |