rb1drv 0.1.2 → 0.1.4
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/Gemfile.lock +1 -1
 - data/lib/rb1drv/auth.rb +4 -0
 - data/lib/rb1drv/onedrive.rb +2 -0
 - data/lib/rb1drv/onedrive_404.rb +16 -0
 - data/lib/rb1drv/onedrive_dir.rb +125 -47
 - data/lib/rb1drv/onedrive_item.rb +39 -4
 - data/lib/rb1drv/sliced_io.rb +13 -4
 - data/lib/rb1drv/version.rb +1 -1
 - data/lib/rb1drv.rb +10 -8
 - metadata +3 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 554c9b9b3a7969cffb6d024ca89d77fd2725a449bdc1fec2d06eeec87ecba026
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 6a4e906dbb95f1036cea931b0748b404e3ad9f5a42cfd40fa9a29773ab25fd19
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 331d44af9844787776f36f3ba638a7d8e65e1761875189bcb482a78ea7276559456711b945c4d29085a7aac34c7cf6fdb05f5d16d2c39265c75e568781f77cb1
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 9a2bc0126d20e02ed2d35ccd0ec3ca620a92404e634d424c2e1289314ae021e88697a9296c68860324c1dde02d013637ddf089f6c118325583d1c0b520bb2336
         
     | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/lib/rb1drv/auth.rb
    CHANGED
    
    
    
        data/lib/rb1drv/onedrive.rb
    CHANGED
    
    
    
        data/lib/rb1drv/onedrive_dir.rb
    CHANGED
    
    | 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'time'
         
     | 
| 
       1 
2 
     | 
    
         
             
            require 'rb1drv/sliced_io'
         
     | 
| 
       2 
3 
     | 
    
         | 
| 
       3 
4 
     | 
    
         
             
            module Rb1drv
         
     | 
| 
         @@ -6,6 +7,7 @@ module Rb1drv 
     | 
|
| 
       6 
7 
     | 
    
         
             
                def initialize(od, api_hash)
         
     | 
| 
       7 
8 
     | 
    
         
             
                  super
         
     | 
| 
       8 
9 
     | 
    
         
             
                  @child_count = api_hash.dig('folder', 'childCount')
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @cached_gets = {}
         
     | 
| 
       9 
11 
     | 
    
         
             
                end
         
     | 
| 
       10 
12 
     | 
    
         | 
| 
       11 
13 
     | 
    
         
             
                # Lists contents of current directory.
         
     | 
| 
         @@ -13,21 +15,31 @@ module Rb1drv 
     | 
|
| 
       13 
15 
     | 
    
         
             
                # @return [Array<OneDriveDir,OneDriveFile>] directories and files whose parent is current directory
         
     | 
| 
       14 
16 
     | 
    
         
             
                def children
         
     | 
| 
       15 
17 
     | 
    
         
             
                  return [] if child_count <= 0
         
     | 
| 
       16 
     | 
    
         
            -
                  @od.request(" 
     | 
| 
      
 18 
     | 
    
         
            +
                  @cached_children ||= @od.request("#{api_path}/children")['value'].map do |child|
         
     | 
| 
       17 
19 
     | 
    
         
             
                    OneDriveItem.smart_new(@od, child)
         
     | 
| 
       18 
20 
     | 
    
         
             
                  end
         
     | 
| 
       19 
21 
     | 
    
         
             
                end
         
     | 
| 
       20 
22 
     | 
    
         | 
| 
      
 23 
     | 
    
         
            +
                # Get a child object by name inside current directory.
         
     | 
| 
      
 24 
     | 
    
         
            +
                #
         
     | 
| 
      
 25 
     | 
    
         
            +
                # @param path [String] name of a child
         
     | 
| 
      
 26 
     | 
    
         
            +
                #
         
     | 
| 
      
 27 
     | 
    
         
            +
                # @return [OneDriveDir,OneDriveFile,OneDrive404] the drive item you asked
         
     | 
| 
      
 28 
     | 
    
         
            +
                def get_child(path)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  children.find { |child| child.name == path } || OneDrive404.new
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
       21 
32 
     | 
    
         
             
                # Get an object by an arbitary path related to current directory.
         
     | 
| 
       22 
33 
     | 
    
         
             
                #
         
     | 
| 
       23 
34 
     | 
    
         
             
                # To get an absolute path, make use of OneDrive#get and not this.
         
     | 
| 
       24 
35 
     | 
    
         
             
                #
         
     | 
| 
       25 
36 
     | 
    
         
             
                # @param path [String] path relative to current directory
         
     | 
| 
       26 
37 
     | 
    
         
             
                #
         
     | 
| 
       27 
     | 
    
         
            -
                # @return [OneDriveDir,OneDriveFile] the drive item you asked
         
     | 
| 
      
 38 
     | 
    
         
            +
                # @return [OneDriveDir,OneDriveFile,OneDrive404] the drive item you asked
         
     | 
| 
       28 
39 
     | 
    
         
             
                def get(path)
         
     | 
| 
       29 
40 
     | 
    
         
             
                  path = "/#{path}" unless path[0] == '/'
         
     | 
| 
       30 
     | 
    
         
            -
                   
     | 
| 
      
 41 
     | 
    
         
            +
                  @cached_gets[path] ||=
         
     | 
| 
      
 42 
     | 
    
         
            +
                    OneDriveItem.smart_new(@od, @od.request("#{api_path}:#{path}"))
         
     | 
| 
       31 
43 
     | 
    
         
             
                end
         
     | 
| 
       32 
44 
     | 
    
         | 
| 
       33 
45 
     | 
    
         
             
                # Yes
         
     | 
| 
         @@ -54,83 +66,149 @@ module Rb1drv 
     | 
|
| 
       54 
66 
     | 
    
         
             
                # @param name [String] directories you'd like to create
         
     | 
| 
       55 
67 
     | 
    
         
             
                # @return [OneDriveDir] the directory you created
         
     | 
| 
       56 
68 
     | 
    
         
             
                def mkdir(name)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  return self if name == '.'
         
     | 
| 
      
 70 
     | 
    
         
            +
                  name = name[1..-1] if name[0] == '/'
         
     | 
| 
       57 
71 
     | 
    
         
             
                  newdir, *remainder = name.split('/')
         
     | 
| 
       58 
     | 
    
         
            -
                  subdir =  
     | 
| 
       59 
     | 
    
         
            -
                  unless subdir
         
     | 
| 
       60 
     | 
    
         
            -
                     
     | 
| 
      
 72 
     | 
    
         
            +
                  subdir = get(newdir)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  unless subdir.dir?
         
     | 
| 
      
 74 
     | 
    
         
            +
                    result = @od.request("#{api_path}/children",
         
     | 
| 
       61 
75 
     | 
    
         
             
                      name: newdir,
         
     | 
| 
       62 
76 
     | 
    
         
             
                      folder: {},
         
     | 
| 
       63 
77 
     | 
    
         
             
                      '@microsoft.graph.conflictBehavior': 'rename'
         
     | 
| 
       64 
78 
     | 
    
         
             
                    )
         
     | 
| 
      
 79 
     | 
    
         
            +
                    subdir = OneDriveDir.new(@od, result)
         
     | 
| 
       65 
80 
     | 
    
         
             
                  end
         
     | 
| 
       66 
     | 
    
         
            -
                  subdir = OneDriveDir.new(@od, subdir)
         
     | 
| 
       67 
81 
     | 
    
         
             
                  remainder.any? ? subdir.mkdir(remainder.join('/')) : subdir
         
     | 
| 
       68 
82 
     | 
    
         
             
                end
         
     | 
| 
       69 
83 
     | 
    
         | 
| 
       70 
     | 
    
         
            -
                # Uploads a local file into current remote directory 
     | 
| 
      
 84 
     | 
    
         
            +
                # Uploads a local file into current remote directory.
         
     | 
| 
      
 85 
     | 
    
         
            +
                # For files no larger than 4000KiB, uses simple upload mode.
         
     | 
| 
      
 86 
     | 
    
         
            +
                # For larger files, uses large file upload mode.
         
     | 
| 
       71 
87 
     | 
    
         
             
                #
         
     | 
| 
       72 
88 
     | 
    
         
             
                # Unfinished download is stored as +target_name.incomplete+ and renamed upon completion.
         
     | 
| 
       73 
89 
     | 
    
         
             
                #
         
     | 
| 
       74 
90 
     | 
    
         
             
                # @param filename [String] local filename you'd like to upload
         
     | 
| 
       75 
     | 
    
         
            -
                # @param overwrite [Boolean] whether to overwrite remote file, or  
     | 
| 
      
 91 
     | 
    
         
            +
                # @param overwrite [Boolean] whether to overwrite remote file, or not
         
     | 
| 
      
 92 
     | 
    
         
            +
                #   If false:
         
     | 
| 
      
 93 
     | 
    
         
            +
                #   For larger files, it renames the uploaded file
         
     | 
| 
      
 94 
     | 
    
         
            +
                #   For small files, it skips the file
         
     | 
| 
      
 95 
     | 
    
         
            +
                #   Always check existence beforehand if you need consistant behavior
         
     | 
| 
       76 
96 
     | 
    
         
             
                # @param fragment_size [Integer] fragment size for each upload session, recommended to be multiple of 320KiB
         
     | 
| 
       77 
97 
     | 
    
         
             
                # @param chunk_size [Integer] IO size for each disk read request and progress notification
         
     | 
| 
       78 
98 
     | 
    
         
             
                # @param target_name [String] desired remote filename, a relative path to current directory
         
     | 
| 
      
 99 
     | 
    
         
            +
                # @return [OneDriveFile,nil] uploaded file
         
     | 
| 
       79 
100 
     | 
    
         
             
                #
         
     | 
| 
       80 
101 
     | 
    
         
             
                # @yield [event, status] for receive progress notification
         
     | 
| 
       81 
102 
     | 
    
         
             
                # @yieldparam event [Symbol] event of this notification
         
     | 
| 
       82 
103 
     | 
    
         
             
                # @yieldparam status [{Symbol => String,Integer}] details
         
     | 
| 
       83 
104 
     | 
    
         
             
                def upload(filename, overwrite: false, fragment_size: 41_943_040, chunk_size: 1_048_576, target_name: nil, &block)
         
     | 
| 
       84 
105 
     | 
    
         
             
                  raise ArgumentError.new('File not found') unless File.exist?(filename)
         
     | 
| 
      
 106 
     | 
    
         
            +
                  conn = nil
         
     | 
| 
       85 
107 
     | 
    
         
             
                  file_size = File.size(filename)
         
     | 
| 
      
 108 
     | 
    
         
            +
                  target_name ||= File.basename(filename)
         
     | 
| 
      
 109 
     | 
    
         
            +
                  return upload_simple(filename, overwrite: overwrite, target_name: target_name) if file_size <= 4_096_000
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
       86 
111 
     | 
    
         
             
                  resume_file = "#{filename}.1drv_upload"
         
     | 
| 
       87 
112 
     | 
    
         
             
                  resume_session = JSON.parse(File.read(resume_file)) rescue nil if File.exist?(resume_file)
         
     | 
| 
       88 
113 
     | 
    
         | 
| 
       89 
     | 
    
         
            -
                   
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
       91 
     | 
    
         
            -
                     
     | 
| 
       92 
     | 
    
         
            -
             
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
      
 114 
     | 
    
         
            +
                  result = nil
         
     | 
| 
      
 115 
     | 
    
         
            +
                  loop do
         
     | 
| 
      
 116 
     | 
    
         
            +
                    catch :restart do
         
     | 
| 
      
 117 
     | 
    
         
            +
                      if resume_session && resume_session['session_url']
         
     | 
| 
      
 118 
     | 
    
         
            +
                        conn = Excon.new(resume_session['session_url'], idempotent: true)
         
     | 
| 
      
 119 
     | 
    
         
            +
                        loop do
         
     | 
| 
      
 120 
     | 
    
         
            +
                          result = JSON.parse(conn.get.body)
         
     | 
| 
      
 121 
     | 
    
         
            +
                          break unless result.dig('error', 'code') == 'accessDenied'
         
     | 
| 
      
 122 
     | 
    
         
            +
                          sleep 5
         
     | 
| 
      
 123 
     | 
    
         
            +
                        end
         
     | 
| 
      
 124 
     | 
    
         
            +
                        resume_position = result.dig('nextExpectedRanges', 0)&.split('-')&.first&.to_i or resume_session = nil
         
     | 
| 
      
 125 
     | 
    
         
            +
                      end
         
     | 
| 
       94 
126 
     | 
    
         | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
      
 127 
     | 
    
         
            +
                      resume_position ||= 0
         
     | 
| 
       96 
128 
     | 
    
         | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
       99 
     | 
    
         
            -
             
     | 
| 
      
 129 
     | 
    
         
            +
                      if resume_session
         
     | 
| 
      
 130 
     | 
    
         
            +
                        file_size == resume_session['source_size'] or resume_session = nil
         
     | 
| 
      
 131 
     | 
    
         
            +
                      end
         
     | 
| 
       100 
132 
     | 
    
         | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
             
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
       105 
     | 
    
         
            -
             
     | 
| 
       106 
     | 
    
         
            -
             
     | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
       109 
     | 
    
         
            -
             
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
      
 133 
     | 
    
         
            +
                      unless resume_session
         
     | 
| 
      
 134 
     | 
    
         
            +
                        result = @od.request("#{api_path}:/#{target_name}:/createUploadSession", item: {'@microsoft.graph.conflictBehavior': overwrite ? 'replace' : 'rename'})
         
     | 
| 
      
 135 
     | 
    
         
            +
                        resume_session = {
         
     | 
| 
      
 136 
     | 
    
         
            +
                          'session_url' => result['uploadUrl'],
         
     | 
| 
      
 137 
     | 
    
         
            +
                          'source_size' => File.size(filename),
         
     | 
| 
      
 138 
     | 
    
         
            +
                          'fragment_size' => fragment_size
         
     | 
| 
      
 139 
     | 
    
         
            +
                        }
         
     | 
| 
      
 140 
     | 
    
         
            +
                        File.write(resume_file, JSON.pretty_generate(resume_session))
         
     | 
| 
      
 141 
     | 
    
         
            +
                        conn = Excon.new(resume_session['session_url'], idempotent: true)
         
     | 
| 
      
 142 
     | 
    
         
            +
                      end
         
     | 
| 
       111 
143 
     | 
    
         | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
       113 
     | 
    
         
            -
             
     | 
| 
       114 
     | 
    
         
            -
             
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
       117 
     | 
    
         
            -
             
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
       119 
     | 
    
         
            -
             
     | 
| 
       120 
     | 
    
         
            -
             
     | 
| 
       121 
     | 
    
         
            -
             
     | 
| 
       122 
     | 
    
         
            -
             
     | 
| 
       123 
     | 
    
         
            -
             
     | 
| 
       124 
     | 
    
         
            -
             
     | 
| 
      
 144 
     | 
    
         
            +
                      new_file = nil
         
     | 
| 
      
 145 
     | 
    
         
            +
                      File.open(filename, mode: 'rb', external_encoding: Encoding::BINARY) do |f|
         
     | 
| 
      
 146 
     | 
    
         
            +
                        resume_position.step(file_size - 1, resume_session['fragment_size']) do |from|
         
     | 
| 
      
 147 
     | 
    
         
            +
                          to = [from + resume_session['fragment_size'], file_size].min - 1
         
     | 
| 
      
 148 
     | 
    
         
            +
                          len = to - from + 1
         
     | 
| 
      
 149 
     | 
    
         
            +
                          headers = {
         
     | 
| 
      
 150 
     | 
    
         
            +
                            'Content-Length': len.to_s,
         
     | 
| 
      
 151 
     | 
    
         
            +
                            'Content-Range': "bytes #{from}-#{to}/#{file_size}"
         
     | 
| 
      
 152 
     | 
    
         
            +
                          }
         
     | 
| 
      
 153 
     | 
    
         
            +
                          @od.logger.info "Uploading #{from}-#{to}/#{file_size}" if @od.logger
         
     | 
| 
      
 154 
     | 
    
         
            +
                          yield :new_segment, file: filename, from: from, to: to if block_given?
         
     | 
| 
      
 155 
     | 
    
         
            +
                          sliced_io = SlicedIO.new(f, from, to) do |progress, total|
         
     | 
| 
      
 156 
     | 
    
         
            +
                            yield :progress, file: filename, from: from, to: to, progress: progress, total: total if block_given?
         
     | 
| 
      
 157 
     | 
    
         
            +
                          end
         
     | 
| 
      
 158 
     | 
    
         
            +
                          begin
         
     | 
| 
      
 159 
     | 
    
         
            +
                            result = conn.put headers: headers, chunk_size: chunk_size, body: sliced_io, read_timeout: 15, write_timeout: 15, retry_limit: 2
         
     | 
| 
      
 160 
     | 
    
         
            +
                            raise IOError if result.body.include? 'accessDenied'
         
     | 
| 
      
 161 
     | 
    
         
            +
                          rescue Excon::Error::Timeout, IOError
         
     | 
| 
      
 162 
     | 
    
         
            +
                            conn = Excon.new(resume_session['session_url'], idempotent: true)
         
     | 
| 
      
 163 
     | 
    
         
            +
                            yield :retry, file: filename, from: from, to: to if block_given?
         
     | 
| 
      
 164 
     | 
    
         
            +
                            sleep 60
         
     | 
| 
      
 165 
     | 
    
         
            +
                            retry
         
     | 
| 
      
 166 
     | 
    
         
            +
                          end
         
     | 
| 
      
 167 
     | 
    
         
            +
                          yield :finish_segment, file: filename, from: from, to: to if block_given?
         
     | 
| 
      
 168 
     | 
    
         
            +
                          throw :restart if result.body.include?('</html>')
         
     | 
| 
      
 169 
     | 
    
         
            +
                          result = JSON.parse(result.body)
         
     | 
| 
      
 170 
     | 
    
         
            +
                          new_file = OneDriveFile.new(@od, result) if result.dig('file')
         
     | 
| 
      
 171 
     | 
    
         
            +
                        end
         
     | 
| 
       125 
172 
     | 
    
         
             
                      end
         
     | 
| 
       126 
     | 
    
         
            -
                       
     | 
| 
       127 
     | 
    
         
            -
                       
     | 
| 
       128 
     | 
    
         
            -
                       
     | 
| 
       129 
     | 
    
         
            -
                      new_file = OneDriveFile.new(@od, result) if result.dig('file')
         
     | 
| 
      
 173 
     | 
    
         
            +
                      throw :restart unless new_file&.file?
         
     | 
| 
      
 174 
     | 
    
         
            +
                      File.unlink(resume_file)
         
     | 
| 
      
 175 
     | 
    
         
            +
                      return set_mtime(new_file, File.mtime(filename))
         
     | 
| 
       130 
176 
     | 
    
         
             
                    end
         
     | 
| 
       131 
     | 
    
         
            -
                     
     | 
| 
      
 177 
     | 
    
         
            +
                    # catch :restart here
         
     | 
| 
      
 178 
     | 
    
         
            +
                    sleep 60 # and retry the whole process
         
     | 
| 
       132 
179 
     | 
    
         
             
                  end
         
     | 
| 
       133 
     | 
    
         
            -
             
     | 
| 
      
 180 
     | 
    
         
            +
                end
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                # Uploads a local file into current remote directory using simple upload mode.
         
     | 
| 
      
 183 
     | 
    
         
            +
                #
         
     | 
| 
      
 184 
     | 
    
         
            +
                # @return [OneDriveFile,nil] uploaded file
         
     | 
| 
      
 185 
     | 
    
         
            +
                def upload_simple(filename, overwrite:, target_name:)
         
     | 
| 
      
 186 
     | 
    
         
            +
                  target_file = get(filename)
         
     | 
| 
      
 187 
     | 
    
         
            +
                  exist = target_file.file?
         
     | 
| 
      
 188 
     | 
    
         
            +
                  return if exist && !overwrite
         
     | 
| 
      
 189 
     | 
    
         
            +
                  path = nil
         
     | 
| 
      
 190 
     | 
    
         
            +
                  if exist
         
     | 
| 
      
 191 
     | 
    
         
            +
                    path = "#{target_file.api_path}/content"
         
     | 
| 
      
 192 
     | 
    
         
            +
                  else
         
     | 
| 
      
 193 
     | 
    
         
            +
                    path = "#{api_path}:/#{target_name}:/content"
         
     | 
| 
      
 194 
     | 
    
         
            +
                  end
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
                  query = {
         
     | 
| 
      
 197 
     | 
    
         
            +
                    path: File.join('v1.0/me/', path),
         
     | 
| 
      
 198 
     | 
    
         
            +
                    headers: {
         
     | 
| 
      
 199 
     | 
    
         
            +
                      'Authorization': "Bearer #{@od.access_token.token}",
         
     | 
| 
      
 200 
     | 
    
         
            +
                      'Content-Type': 'application/octet-stream'
         
     | 
| 
      
 201 
     | 
    
         
            +
                    },
         
     | 
| 
      
 202 
     | 
    
         
            +
                    body: File.read(filename)
         
     | 
| 
      
 203 
     | 
    
         
            +
                  }
         
     | 
| 
      
 204 
     | 
    
         
            +
                  result = @od.conn.put(query)
         
     | 
| 
      
 205 
     | 
    
         
            +
                  result = JSON.parse(result.body)
         
     | 
| 
      
 206 
     | 
    
         
            +
                  file = OneDriveFile.new(@od, result)
         
     | 
| 
      
 207 
     | 
    
         
            +
                  set_mtime(file, File.mtime(filename))
         
     | 
| 
      
 208 
     | 
    
         
            +
                end
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
                def set_mtime(file, time)
         
     | 
| 
      
 211 
     | 
    
         
            +
                  OneDriveFile.new(@od, @od.request(file.api_path, {fileSystemInfo: {lastModifiedDateTime: time.utc.iso8601}}, :patch))
         
     | 
| 
       134 
212 
     | 
    
         
             
                end
         
     | 
| 
       135 
213 
     | 
    
         
             
              end
         
     | 
| 
       136 
214 
     | 
    
         
             
            end
         
     | 
    
        data/lib/rb1drv/onedrive_item.rb
    CHANGED
    
    | 
         @@ -1,24 +1,41 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Rb1drv
         
     | 
| 
       2 
2 
     | 
    
         
             
              class OneDriveItem
         
     | 
| 
       3 
     | 
    
         
            -
                attr_reader :id, :name, :eTag, :size, :mtime, :ctime, :muser, :cuser, :parent_path
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :id, :name, :eTag, :size, :mtime, :ctime, :muser, :cuser, :parent_path, :remote_id, :remote_drive_id
         
     | 
| 
       4 
4 
     | 
    
         
             
                protected
         
     | 
| 
       5 
5 
     | 
    
         
             
                def initialize(od, api_hash)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  raise api_hash.inspect unless api_hash['createdDateTime']
         
     | 
| 
       6 
7 
     | 
    
         
             
                  @od = od
         
     | 
| 
       7 
8 
     | 
    
         
             
                  %w(id name eTag size).each do |key|
         
     | 
| 
       8 
9 
     | 
    
         
             
                    instance_variable_set("@#{key}", api_hash[key])
         
     | 
| 
       9 
10 
     | 
    
         
             
                  end
         
     | 
| 
       10 
     | 
    
         
            -
                  @ 
     | 
| 
       11 
     | 
    
         
            -
                  @ 
     | 
| 
      
 11 
     | 
    
         
            +
                  @remote_drive_id = api_hash.dig('remoteItem', 'parentReference', 'driveId')
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @remote_id = api_hash.dig('remoteItem', 'id')
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @mtime = Time.iso8601(api_hash['lastModifiedDateTime'])
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @ctime = Time.iso8601(api_hash['createdDateTime'])
         
     | 
| 
       12 
15 
     | 
    
         
             
                  @muser = api_hash.dig('lastModifiedBy', 'user', 'displayName') || 'N/A'
         
     | 
| 
       13 
16 
     | 
    
         
             
                  @cuser = api_hash.dig('createdBy', 'user', 'displayName') || 'N/A'
         
     | 
| 
       14 
17 
     | 
    
         
             
                  @parent_path = api_hash.dig('parentReference', 'path')
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @remote = api_hash.has_key?('remoteItem')
         
     | 
| 
       15 
19 
     | 
    
         
             
                end
         
     | 
| 
       16 
20 
     | 
    
         | 
| 
       17 
21 
     | 
    
         
             
                # Create subclass instance by checking the item type
         
     | 
| 
       18 
22 
     | 
    
         
             
                #
         
     | 
| 
       19 
23 
     | 
    
         
             
                # @return [OneDriveFile, OneDriveDir] instanciated drive item
         
     | 
| 
       20 
24 
     | 
    
         
             
                def self.smart_new(od, item_hash)
         
     | 
| 
       21 
     | 
    
         
            -
                  item_hash[' 
     | 
| 
      
 25 
     | 
    
         
            +
                  if item_hash['remoteItem']
         
     | 
| 
      
 26 
     | 
    
         
            +
                    item_hash['remoteItem'].each do |key, value|
         
     | 
| 
      
 27 
     | 
    
         
            +
                      item_hash[key] ||= value
         
     | 
| 
      
 28 
     | 
    
         
            +
                    end
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  if item_hash['file']
         
     | 
| 
      
 31 
     | 
    
         
            +
                    OneDriveFile.new(od, item_hash)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  elsif item_hash['folder']
         
     | 
| 
      
 33 
     | 
    
         
            +
                    OneDriveDir.new(od, item_hash)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  elsif item_hash.dig('error', 'code') == 'itemNotFound'
         
     | 
| 
      
 35 
     | 
    
         
            +
                    OneDrive404.new
         
     | 
| 
      
 36 
     | 
    
         
            +
                  else
         
     | 
| 
      
 37 
     | 
    
         
            +
                    item_hash
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
       22 
39 
     | 
    
         
             
                end
         
     | 
| 
       23 
40 
     | 
    
         | 
| 
       24 
41 
     | 
    
         
             
                # @return [String] absolute path of current item
         
     | 
| 
         @@ -29,5 +46,23 @@ module Rb1drv 
     | 
|
| 
       29 
46 
     | 
    
         
             
                    @name
         
     | 
| 
       30 
47 
     | 
    
         
             
                  end
         
     | 
| 
       31 
48 
     | 
    
         
             
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                # TODO: API endpoint does not play well with remote files
         
     | 
| 
      
 51 
     | 
    
         
            +
                #
         
     | 
| 
      
 52 
     | 
    
         
            +
                # @return [String] api reference path of current object
         
     | 
| 
      
 53 
     | 
    
         
            +
                def api_path
         
     | 
| 
      
 54 
     | 
    
         
            +
                  if remote?
         
     | 
| 
      
 55 
     | 
    
         
            +
                    "drives/#{@remote_drive_id}/items/#{@remote_id}"
         
     | 
| 
      
 56 
     | 
    
         
            +
                  else
         
     | 
| 
      
 57 
     | 
    
         
            +
                    "drive/items/#{@id}"
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                # TODO: API endpoint does not play well with remote files
         
     | 
| 
      
 62 
     | 
    
         
            +
                #
         
     | 
| 
      
 63 
     | 
    
         
            +
                # @return [Boolean] whether it's shared by others
         
     | 
| 
      
 64 
     | 
    
         
            +
                def remote?
         
     | 
| 
      
 65 
     | 
    
         
            +
                  @remote
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
       32 
67 
     | 
    
         
             
              end
         
     | 
| 
       33 
68 
     | 
    
         
             
            end
         
     | 
    
        data/lib/rb1drv/sliced_io.rb
    CHANGED
    
    | 
         @@ -5,11 +5,11 @@ class SlicedIO 
     | 
|
| 
       5 
5 
     | 
    
         
             
                @from = from
         
     | 
| 
       6 
6 
     | 
    
         
             
                @to = to
         
     | 
| 
       7 
7 
     | 
    
         
             
                @block = block
         
     | 
| 
       8 
     | 
    
         
            -
                 
     | 
| 
      
 8 
     | 
    
         
            +
                rewind
         
     | 
| 
       9 
9 
     | 
    
         
             
              end
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         
             
              def rewind
         
     | 
| 
       12 
     | 
    
         
            -
                io.seek(from)
         
     | 
| 
      
 12 
     | 
    
         
            +
                @io.seek(@from)
         
     | 
| 
       13 
13 
     | 
    
         
             
                @current = 0
         
     | 
| 
       14 
14 
     | 
    
         
             
              end
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
         @@ -19,10 +19,19 @@ class SlicedIO 
     | 
|
| 
       19 
19 
     | 
    
         | 
| 
       20 
20 
     | 
    
         
             
              def read(len)
         
     | 
| 
       21 
21 
     | 
    
         
             
                return nil if @current >= size
         
     | 
| 
       22 
     | 
    
         
            -
                len = [len,  
     | 
| 
      
 22 
     | 
    
         
            +
                len = [len, size - @current].min
         
     | 
| 
       23 
23 
     | 
    
         
             
                # Notify before we read
         
     | 
| 
       24 
24 
     | 
    
         
             
                @block.call(@current, size)
         
     | 
| 
       25 
     | 
    
         
            -
                 
     | 
| 
      
 25 
     | 
    
         
            +
                failed_count = 0
         
     | 
| 
      
 26 
     | 
    
         
            +
                begin
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @io.read(len)
         
     | 
| 
      
 28 
     | 
    
         
            +
                rescue Errno::EIO
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @io.seek(@from + @current)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  sleep 1
         
     | 
| 
      
 31 
     | 
    
         
            +
                  failed_count += 1
         
     | 
| 
      
 32 
     | 
    
         
            +
                  retry unless failed_count > 5
         
     | 
| 
      
 33 
     | 
    
         
            +
                  raise
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
       26 
35 
     | 
    
         
             
              ensure
         
     | 
| 
       27 
36 
     | 
    
         
             
                @current += len
         
     | 
| 
       28 
37 
     | 
    
         
             
              end
         
     | 
    
        data/lib/rb1drv/version.rb
    CHANGED
    
    
    
        data/lib/rb1drv.rb
    CHANGED
    
    | 
         @@ -7,9 +7,9 @@ module Rb1drv 
     | 
|
| 
       7 
7 
     | 
    
         
             
              #
         
     | 
| 
       8 
8 
     | 
    
         
             
              # Call +#root+ or +#get+ to get an +OneDriveDir+ or +OneDriveFile+ to wotk with.
         
     | 
| 
       9 
9 
     | 
    
         
             
              class OneDrive
         
     | 
| 
       10 
     | 
    
         
            -
                attr_reader :oauth2_client, :logger, :access_token
         
     | 
| 
      
 10 
     | 
    
         
            +
                attr_reader :oauth2_client, :logger, :access_token, :conn
         
     | 
| 
       11 
11 
     | 
    
         
             
                # Instanciates with app id and secret.
         
     | 
| 
       12 
     | 
    
         
            -
                def initialize(client_id, client_secret, callback_url, logger= 
     | 
| 
      
 12 
     | 
    
         
            +
                def initialize(client_id, client_secret, callback_url, logger=nil)
         
     | 
| 
       13 
13 
     | 
    
         
             
                  @client_id = client_id
         
     | 
| 
       14 
14 
     | 
    
         
             
                  @client_secret = client_secret
         
     | 
| 
       15 
15 
     | 
    
         
             
                  @callback_url = callback_url
         
     | 
| 
         @@ -17,8 +17,8 @@ module Rb1drv 
     | 
|
| 
       17 
17 
     | 
    
         
             
                  @oauth2_client = OAuth2::Client.new client_id, client_secret,
         
     | 
| 
       18 
18 
     | 
    
         
             
                    authorize_url: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
         
     | 
| 
       19 
19 
     | 
    
         
             
                    token_url: 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
         
     | 
| 
       20 
     | 
    
         
            -
                  @conn = Excon.new('https://graph.microsoft.com/', persistent: true)
         
     | 
| 
       21 
     | 
    
         
            -
                  @conn.logger = @logger
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @conn = Excon.new('https://graph.microsoft.com/', persistent: true, idempotent: true)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @conn.logger = @logger if @logger
         
     | 
| 
       22 
22 
     | 
    
         
             
                end
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
24 
     | 
    
         
             
                # Issues requests to API endpoint.
         
     | 
| 
         @@ -29,9 +29,10 @@ module Rb1drv 
     | 
|
| 
       29 
29 
     | 
    
         
             
                #
         
     | 
| 
       30 
30 
     | 
    
         
             
                # @return [Hash] response from API.
         
     | 
| 
       31 
31 
     | 
    
         
             
                def request(uri, data=nil, verb=:post)
         
     | 
| 
       32 
     | 
    
         
            -
                  @logger.info(uri)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @logger.info(uri) if @logger
         
     | 
| 
      
 33 
     | 
    
         
            +
                  auth_check
         
     | 
| 
       33 
34 
     | 
    
         
             
                  query = {
         
     | 
| 
       34 
     | 
    
         
            -
                    path: File.join('v1.0/me/', uri),
         
     | 
| 
      
 35 
     | 
    
         
            +
                    path: File.join('v1.0/me/', URI.escape(uri)),
         
     | 
| 
       35 
36 
     | 
    
         
             
                    headers: {
         
     | 
| 
       36 
37 
     | 
    
         
             
                      'Authorization': "Bearer #{@access_token.token}"
         
     | 
| 
       37 
38 
     | 
    
         
             
                    }
         
     | 
| 
         @@ -39,8 +40,8 @@ module Rb1drv 
     | 
|
| 
       39 
40 
     | 
    
         
             
                  if data
         
     | 
| 
       40 
41 
     | 
    
         
             
                    query[:body] = JSON.generate(data)
         
     | 
| 
       41 
42 
     | 
    
         
             
                    query[:headers]['Content-Type'] = 'application/json'
         
     | 
| 
       42 
     | 
    
         
            -
                    @logger.info(query[:body])
         
     | 
| 
       43 
     | 
    
         
            -
                    verb = :post unless [:post, :put, :delete].include?(verb)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @logger.info(query[:body]) if @logger
         
     | 
| 
      
 44 
     | 
    
         
            +
                    verb = :post unless [:post, :put, :patch, :delete].include?(verb)
         
     | 
| 
       44 
45 
     | 
    
         
             
                    response = @conn.send(verb, query)
         
     | 
| 
       45 
46 
     | 
    
         
             
                  else
         
     | 
| 
       46 
47 
     | 
    
         
             
                    response = @conn.get(query)
         
     | 
| 
         @@ -55,3 +56,4 @@ require 'rb1drv/onedrive' 
     | 
|
| 
       55 
56 
     | 
    
         
             
            require 'rb1drv/onedrive_item'
         
     | 
| 
       56 
57 
     | 
    
         
             
            require 'rb1drv/onedrive_dir'
         
     | 
| 
       57 
58 
     | 
    
         
             
            require 'rb1drv/onedrive_file'
         
     | 
| 
      
 59 
     | 
    
         
            +
            require 'rb1drv/onedrive_404'
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: rb1drv
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0.1. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.1.4
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Xinyue Lu
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2018-05- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2018-05-21 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: oauth2
         
     | 
| 
         @@ -82,6 +82,7 @@ files: 
     | 
|
| 
       82 
82 
     | 
    
         
             
            - lib/rb1drv.rb
         
     | 
| 
       83 
83 
     | 
    
         
             
            - lib/rb1drv/auth.rb
         
     | 
| 
       84 
84 
     | 
    
         
             
            - lib/rb1drv/onedrive.rb
         
     | 
| 
      
 85 
     | 
    
         
            +
            - lib/rb1drv/onedrive_404.rb
         
     | 
| 
       85 
86 
     | 
    
         
             
            - lib/rb1drv/onedrive_dir.rb
         
     | 
| 
       86 
87 
     | 
    
         
             
            - lib/rb1drv/onedrive_file.rb
         
     | 
| 
       87 
88 
     | 
    
         
             
            - lib/rb1drv/onedrive_item.rb
         
     |