class Rb1drv::OneDriveDir
Attributes
Public Class Methods
# File lib/rb1drv/onedrive_dir.rb, line 7 def initialize(od, api_hash) super @child_count = api_hash.dig('folder', 'childCount') @cached_gets = {} end
Public Instance Methods
@return [String] absolute path of current item
# File lib/rb1drv/onedrive_dir.rb, line 56 def absolute_path if @parent_path File.join(@parent_path, @name) else '/' end end
Lists contents of current directory.
@return [Array<OneDriveDir,OneDriveFile>] directories and files whose parent is current directory
# File lib/rb1drv/onedrive_dir.rb, line 16 def children return [] if child_count <= 0 @cached_children ||= @od.request("#{api_path}/children?$top=1000")['value'].map do |child| OneDriveItem.smart_new(@od, child) end end
Yes
# File lib/rb1drv/onedrive_dir.rb, line 46 def dir? true end
No
# File lib/rb1drv/onedrive_dir.rb, line 51 def file? false end
Get an object by an arbitary path related to current directory.
To get an absolute path, make use of OneDrive#get
and not this.
@param path [String] path relative to current directory
@return [OneDriveDir,OneDriveFile,OneDrive404] the drive item you asked
# File lib/rb1drv/onedrive_dir.rb, line 39 def get(path) path = "/#{path}" unless path[0] == '/' @cached_gets[path] ||= OneDriveItem.smart_new(@od, @od.request("#{api_path}:#{path}")) end
Get a child object by name inside current directory.
@param path [String] name of a child
@return [OneDriveDir,OneDriveFile,OneDrive404] the drive item you asked
# File lib/rb1drv/onedrive_dir.rb, line 28 def get_child(path) children.find { |child| child.name == path } || OneDrive404.new end
Recursively creates empty directories.
@param name [String] directories you'd like to create @return [OneDriveDir] the directory you created
# File lib/rb1drv/onedrive_dir.rb, line 68 def mkdir(name) return self if name == '.' name = name[1..-1] if name[0] == '/' newdir, *remainder = name.split('/') subdir = get(newdir) unless subdir.dir? result = @od.request("#{api_path}/children", name: newdir, folder: {}, '@microsoft.graph.conflictBehavior': 'rename' ) subdir = OneDriveDir.new(@od, result) end remainder.any? ? subdir.mkdir(remainder.join('/')) : subdir end
Uploads a local file into current remote directory. For files no larger than 4000KiB, uses simple upload mode. For larger files, uses large file upload mode.
Unfinished download is stored as target_name.incomplete
and renamed upon completion.
@param filename [String] local filename you'd like to upload @param overwrite [Boolean] whether to overwrite remote file, or not
If false: For larger files, it renames the uploaded file For small files, it skips the file Always check existence beforehand if you need consistant behavior
@param fragment_size [Integer] fragment size for each upload session, recommended to be multiple of 320KiB @param chunk_size [Integer] IO size for each disk read request and progress notification @param target_name [String] desired remote filename, a relative path to current directory @return [OneDriveFile,nil] uploaded file
@yield [event, status] for receive progress notification @yieldparam event [Symbol] event of this notification @yieldparam status [{Symbol => String,Integer}] details
# File lib/rb1drv/onedrive_dir.rb, line 104 def upload(filename, overwrite: false, fragment_size: 41_943_040, chunk_size: 1_048_576, target_name: nil, &block) raise ArgumentError.new('File not found') unless File.exist?(filename) conn = nil file_size = File.size(filename) target_name ||= File.basename(filename) return upload_simple(filename, overwrite: overwrite, target_name: target_name) if file_size <= 4_096_000 resume_file = "#{filename}.1drv_upload" resume_session = JSON.parse(File.read(resume_file)) rescue nil if File.exist?(resume_file) old_file = OneDriveItem.smart_new(@od, @od.request("#{api_path}:/#{target_name}")) new_file = nil result = nil loop do catch :restart do if resume_session && resume_session['session_url'] conn = Excon.new(resume_session['session_url'], idempotent: true) loop do result = JSON.parse(conn.get.body) break unless result.dig('error', 'code') == 'accessDenied' sleep 5 end resume_position = result.dig('nextExpectedRanges', 0)&.split('-')&.first&.to_i or resume_session = nil end resume_position ||= 0 if resume_session file_size == resume_session['source_size'] or resume_session = nil end until resume_session && resume_session['session_url'] do result = @od.request("#{api_path}:/#{target_name}:/createUploadSession", item: {'@microsoft.graph.conflictBehavior': overwrite ? 'replace' : 'rename'}) if result['uploadUrl'] resume_session = { 'session_url' => result['uploadUrl'], 'source_size' => File.size(filename), 'fragment_size' => fragment_size } File.write(resume_file, JSON.pretty_generate(resume_session)) conn = Excon.new(resume_session['session_url'], idempotent: true) break end sleep 15 end new_file = nil File.open(filename, mode: 'rb', external_encoding: Encoding::BINARY) do |f| resume_position.step(file_size - 1, resume_session['fragment_size']) do |from| to = [from + resume_session['fragment_size'], file_size].min - 1 len = to - from + 1 headers = { 'Content-Length': len.to_s, 'Content-Range': "bytes #{from}-#{to}/#{file_size}" } @od.logger.info "Uploading #{from}-#{to}/#{file_size}" if @od.logger yield :new_segment, file: filename, from: from, to: to if block_given? sliced_io = SlicedIO.new(f, from, to) do |progress, total| yield :progress, file: filename, from: from, to: to, progress: progress, total: total if block_given? end begin result = conn.put headers: headers, chunk_size: chunk_size, body: sliced_io, retry_limit: 2 raise IOError if result.body.include? 'accessDenied' rescue Excon::Error::Socket, IOError # Probably server rejected this request throw :restart rescue Excon::Error::Timeout conn = Excon.new(resume_session['session_url'], idempotent: true) yield :retry, file: filename, from: from, to: to if block_given? retry ensure yield :finish_segment, file: filename, from: from, to: to if block_given? end throw :restart if result.body.include?('</html>') result = JSON.parse(result.body) new_file = OneDriveFile.new(@od, result) if result.dig('file') end end throw :restart unless new_file&.file? break end # catch :restart here 6.times do new_file = OneDriveItem.smart_new(@od, @od.request("#{api_path}:/#{target_name}")) break if new_file.file? && new_file.id != old_file.id sleep 10 # wait for server to process the previous request end break if new_file.file? && new_file.id != old_file.id # and retry the whole process end # upload completed File.unlink(resume_file) return new_file.set_mtime(File.mtime(filename)) end
Uploads a local file into current remote directory using simple upload mode.
@return [OneDriveFile,nil] uploaded file
# File lib/rb1drv/onedrive_dir.rb, line 203 def upload_simple(filename, overwrite:, target_name:) target_file = get(target_name) exist = target_file.file? return if exist && !overwrite path = nil if exist path = "#{target_file.api_path}/content" else path = "#{api_path}:/#{target_name}:/content" end query = { path: File.join('v1.0/me/', path), headers: { 'Authorization': "Bearer #{@od.access_token.token}", 'Content-Type': 'application/octet-stream' }, body: File.read(filename) } result = @od.conn.put(query) result = JSON.parse(result.body) file = OneDriveFile.new(@od, result) file.set_mtime(File.mtime(filename)) end