module Rb1drvTools::Command

Public Class Methods

cmd_cd(args) click to toggle source
# File lib/rb1drv-tools/command/cd.rb, line 3
def self.cmd_cd(args)
  Profile.profile
  dir = args.first
  dir_obj = get_obj(dir)
  if dir_obj.is_a? OneDriveDir
    CLI.cwd = dir_obj
  else
    dir = File.join(CLI.cwd.absolute_path, dir)
    puts "cd: cannot access '#{dir}': No such file or directory"
  end
end
cmd_help(args) click to toggle source
# File lib/rb1drv-tools/command/help.rb, line 3
    def self.cmd_help(args)
      cmd_version(args)
      me = File.basename($0, File.extname($0))
      puts <<~EOF
        Usage:
          #{me} [:<profile>] <Command> [-,- <Command>]...

        Command:
          help                              Display this help

          profile new                       Create a new profile to access your files
          profile ls                        List profiles
          profile default                   Set chosen profile as default
          profile del                       Delete profile to revoke access

          ls [-ustrl] [<target>]            List information about files
          ll [-ustrl] [<target>]            Long listing format of ls
              -u                                Do not sort by name
              -s                                Sort by size, smallest first
              -t                                Sort by modified time, oldest first
              -r                                Reverse order while sorting
              -l                                Use a long listing format
              directory                         List contents of this directory
              file                              List information of this file

          cd <directory>                    Change current directory, only effective in single execution

          mkdir <directory>                 Create the directory recursively

          upload [-fs] <files> [@target]    Upload files into directory recursively, skipping existing files
          download [-f] <r-files> [@target] Download files into directory recursively, skipping existing files
              -f                                Overwrite existing files regardless
              -s                                Overwrite existing files with different size or older date
              -t                                Reset last modified time if remote file has newer date and same size
              @directory/                       Upload or download into directory as children, if:
                                                  - target directory exists
                                                  - target ends with a slash
                                                  - multiple source files are given
                                                  - wildcard matching multiple source files
              @file                             Upload or download as a single file, if:
                                                  - target file exists
                                                  - target does not end with a slash, and only one source file is given
              files                             Local source files to upload, wildcard accepted in Ruby glob syntax
              r-files                           Remote source files to download, wildcard accepted with limitations:
                                                  - no ** matching
                                                  - can only match names under current directory

          info                               Show information about this drive

        Examples:
          #{me} profile new student_account
          #{me} profile new personal
          #{me} :personal profile default -,- profile ls
          #{me} :student_account cd class101/slides -,- download slides-2\\*.pdf @slides/
          #{me} upload slides -,- ls -l slides -,- info

      EOF
    end
cmd_info(args) click to toggle source
# File lib/rb1drv-tools/command/info.rb, line 3
def self.cmd_info(args)
  Profile.profile
  format = '%10s: %s'
  CLI.od.request('drives').dig('value').each do |drive|
    total = drive.dig('quota', 'total').to_i
    used = drive.dig('quota', 'used').to_i
    deleted = drive.dig('quota', 'deleted').to_i
    remaining = drive.dig('quota', 'remaining').to_i
    lost = total - used - deleted - remaining

    {
      'ID' => drive.dig('id'),
      'Site' => drive.dig('webUrl'),
      'Type' => drive.dig('driveType'),
      'Owner' => '%s <%s>' % [drive.dig('owner', 'user', 'displayName'), drive.dig('owner', 'user', 'email')],
      'Size' => Utils.humanize_size(total),
      'Used' => Utils.humanize_size(used),
      'Deleted' => Utils.humanize_size(deleted),
      'Free' => Utils.humanize_size(remaining),
      'Lost' => Utils.humanize_size(lost) # Lost in universe (Version History)
    }.each do |row|
      puts format % row
    end
    puts
  end

end
cmd_ll(args) click to toggle source
# File lib/rb1drv-tools/command/ls.rb, line 3
def self.cmd_ll(args)
  args.unshift('-l')
  cmd_ls(args)
end
cmd_ls(args) click to toggle source
# File lib/rb1drv-tools/command/ls.rb, line 7
def self.cmd_ls(args)
  Profile.profile
  column = :name
  order = :asc
  long = false
  dirs = []
  args.each do |arg|
    next dirs << arg if arg[0] != '-'
    arg[1..-1].each_char do |flag|
      case flag
      when 'l'
        long = true
      when 'u'
        column = nil
      when 's'
        column = :size
      when 't'
        column = :mtime
      when 'r'
        order = :desc
      else
        puts "Warning: unknown flag '#{flag}'."
      end
    end
  end
  dirs << '.' if dirs.empty?
  dirs.each do |dir|
    dir_obj = get_obj(dir)
    if dir_obj.is_a? OneDrive404
      dir = File.join(CLI.cwd.absolute_path, dir)
      puts "ls: cannot access '#{dir}': No such file or directory"
    elsif dir_obj.file?
      puts Utils.ls([dir_obj], column, order, long)
    else
      puts Utils.ls(dir_obj.children, column, order, long)
      puts "#{dir_obj.child_count} items"
    end
  end
end
cmd_profile(args) click to toggle source
# File lib/rb1drv-tools/command/profile.rb, line 4
def self.cmd_profile(args)
  request = args.shift
  case request
  when nil, 'ls'
    cmd_profile_ls(args)
  when 'new'
    cmd_profile_new(args)
  when 'default'
    cmd_profile_default(args)
  when 'del', 'delete', 'rm'
    cmd_profile_del(args)
  else
    puts "Unknown request #{request}"
  end
end
cmd_profile_default(args) click to toggle source
# File lib/rb1drv-tools/command/profile.rb, line 59
def self.cmd_profile_default(args)
  Profile.default_profile_index = Profile.profile_index
end
cmd_profile_del(args) click to toggle source
# File lib/rb1drv-tools/command/profile.rb, line 49
def self.cmd_profile_del(args)
  name = Profile.profile.name
  path = Profile.profile.file
  prompt = TTY::Prompt.new
  puts "You asked to delete your local profile #{name}."
  puts "Doing so will revoke our access to your files."
  puts "You can regain access by `profile add` later."
  File.unlink(path) if prompt.yes?("Proceed to delete?", convert: :bool, default: false)
end
cmd_profile_ls(args) click to toggle source
# File lib/rb1drv-tools/command/profile.rb, line 20
def self.cmd_profile_ls(args)
  Profile.all.map(&:name).each do |p|
    puts "Profile: #{p}"
  end
  puts "Default: #{Profile.default_profile_index || 'none'}"
end
cmd_profile_new(args) click to toggle source
# File lib/rb1drv-tools/command/profile.rb, line 27
    def self.cmd_profile_new(args)
      name = args.first || SecureRandom.hex(4)
      print <<~EOF.chomp
        Creating profile #{name}.

        To create a new OneDrive profile, open the following link in your browser, sign in with your account, and approve our access to your files.

        #{CLI.od.auth_url}

        Paste your authorization code here: 
      EOF
      auth_code = ''
      loop do
        auth_code = STDIN.gets.strip
        break unless auth_code.empty?
      end
      access_token = CLI.od.auth_access(auth_code)
      p = Profile.new
      p.name = name
      p.update_token(access_token)
    end
cmd_upload(args) click to toggle source
# File lib/rb1drv-tools/command/upload.rb, line 3
def self.cmd_upload(args)
  Profile.profile
  return if args.empty?
  file_mode = true
  overwrite = false
  overwrite_smarter = false
  time_sync = false

  items = []
  args.each do |arg|
    next items << arg if arg[0] != '-'
    arg[1..-1].each_char do |flag|
      case flag
      when 's'
        overwrite_smarter = true
      when 'f'
        overwrite = true
      when 't'
        time_sync = true
      else
        puts "Warning: unknown flag '#{flag}'."
      end
    end
  end

  if overwrite && overwrite_smarter
    puts "Error: -s and -f are mutually exclusive, make your decision first."
    return
  end

  target = '.'
  target = items.pop[1..-1] if items.last[0] == '@'
  target_obj = get_obj(target)
  return if items.empty?

  # p items
  # p target_obj

  # Directory mode if:
  #   target directory exists
  file_mode = false if target_obj.dir?
  #   target ends with a slash
  file_mode = false if target.end_with?('/')
  #   multiple source files are given
  file_mode = false if items.size > 1
  file_mode = false if File.directory?(items.first)
  #   wildcard matching files on server
  file_mode = false if Dir[items.first].size > 1

  overwrite_policy = if overwrite
                       :overwrite
                     elsif overwrite_smarter
                       :overwrite_smarter
                     else
                       :skip
                     end

  if file_mode
    target_dir = get_obj(File.dirname(target))
    CLI.cwd.mkdir(File.dirname(target))
    target_name = File.basename(target)
    upload(items.first, target_dir, File.basename(target), overwrite_policy: overwrite_policy, time_sync: time_sync)
  else
    items.each do |item|
      unless target_obj.dir?
        base = if target[0] == '/'
                 CLI.root
               else
                 CLI.cwd
               end
        target = File.expand_path('/./' + target)
        target_obj = base.mkdir(target)
      end
      Dir[item].each do |match|
        if File.directory?(match)
          traverse_dir(match, target_obj.mkdir(File.basename(match)), overwrite_policy: overwrite_policy, time_sync: time_sync)
        else
          upload(match, target_obj, File.basename(match), overwrite_policy: overwrite_policy, time_sync: time_sync)
        end
      end
    end
  end
end
cmd_version(args) click to toggle source
# File lib/rb1drv-tools/command/help.rb, line 62
    def self.cmd_version(args)
      puts <<~EOF
        Ruby-OneDrive-Tools #{Rb1drvTools::VERSION}, SDK #{Rb1drv::VERSION}
        Copyright (c) 2018 Xinyue Lu, The MIT License.

        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
        You may not hold the author liable. Check LICENSE.txt for Full Text.

      EOF
    end
get_obj(target) click to toggle source
# File lib/rb1drv-tools/command/common.rb, line 3
def self.get_obj(target)
  if target == '.'
    CLI.cwd
  elsif target == '/'
    CLI.od.root
  elsif target[0] == '/'
    target = File.expand_path('/./' + target)
    CLI.root.get(target)
  else
    target = File.expand_path('/./' + target)
    CLI.cwd.get(target)
  end
end
traverse_dir(source_directory, target_directory, overwrite_policy: :skip, time_sync: false) click to toggle source
# File lib/rb1drv-tools/command/upload.rb, line 87
def self.traverse_dir(source_directory, target_directory, overwrite_policy: :skip, time_sync: false)
  Dir.entries(source_directory).sort.each do |child|
    next if child == '.' || child == '..'
    new_path = File.join(source_directory, child)
    if File.directory?(new_path)
      new_dir = target_directory.get_child(child)
      new_dir = target_directory.mkdir(child) unless new_dir.dir?
      traverse_dir(new_path, new_dir, overwrite_policy: overwrite_policy, time_sync: time_sync)
    else
      upload(new_path, target_directory, child, overwrite_policy: overwrite_policy, time_sync: time_sync)
    end
  end
end
upload(source_file, target_directory, target_name, overwrite_policy: :skip, time_sync: false) click to toggle source
# File lib/rb1drv-tools/command/upload.rb, line 101
def self.upload(source_file, target_directory, target_name, overwrite_policy: :skip, time_sync: false)
  return if source_file.include?('.1drv_upload')
  printf "%s => %s :: %s\n", source_file, target_directory.absolute_path, target_name
  target_obj = target_directory.get_child(target_name)
  return puts 'Target is a directory, skipped' if target_obj.dir?
  mtime = File.mtime(source_file)
  file_size = File.size(source_file)
  if overwrite_policy != :overwrite && target_obj.file?
    if time_sync && target_obj.size == file_size && target_obj.mtime - mtime > 2
      target_obj.set_mtime(mtime)
      print '<sync mtime> '
    end
    return puts 'Skipped' if overwrite_policy == :skip
    return puts 'Remote file is up to date, skipped' if target_obj.mtime >= File.mtime(source_file) - 2
  end
  screen_width = TTY::Screen.width
  if STDOUT.tty? && screen_width >= 50
    multibar = TTY::ProgressBar::Multi.new
    bar = multibar.register("%s :current_byte / :total_byte :byte_rate/s [:bar] :eta :percent" % source_file[0..29], total: file_size, frequency: 2, head: '>')
    bar.start
  end
  subbar = nil

  if file_size >= 524_288_000 # 500MiB
    fragment_size = 209_715_200 # 200MiB
  elsif file_size >= 209_715_200 # 200MiB
    fragment_size = 83_886_080 # 80MiB
  else
    fragment_size = 20_971_520 # 20MiB
  end

  target_directory.upload(source_file, target_name: target_name, overwrite: true, fragment_size: fragment_size) do |ev, st|
    case ev
    when :new_segment
      subbar = multibar.register("%4d-%-4d :current_byte / :total_byte [:bar]" % [st[:from] / 1048576, st[:to] / 1048576], total: st[:to] - st[:from] + 1, frequency: 2, head: '>')
    when :finish_segment
      subbar.finish
      multibar.instance_variable_get('@bars').delete_at(1)
      multibar.instance_variable_set('@rows', multibar.rows - 1)
      print TTY::Cursor.up(1)
      print TTY::Cursor.clear_line_after
    when :progress
      bar.current = st[:from] + st[:progress]
      subbar.current = st[:progress]
    when :retry
      bar.current = st[:from]
      subbar.reset
    end
  end

  bar.finish
end