rmega 0.0.6 → 0.1.0
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.
- data/README.md +108 -55
- data/lib/rmega/crypto/aes.rb +5 -3
- data/lib/rmega/crypto/aes_ctr.rb +5 -2
- data/lib/rmega/crypto/crypto.rb +13 -8
- data/lib/rmega/crypto/rsa.rb +3 -1
- data/lib/rmega/downloader.rb +13 -16
- data/lib/rmega/loggable.rb +9 -2
- data/lib/rmega/nodes/deletable.rb +16 -0
- data/lib/rmega/nodes/expandable.rb +64 -0
- data/lib/rmega/nodes/factory.rb +41 -0
- data/lib/rmega/nodes/file.rb +39 -0
- data/lib/rmega/nodes/folder.rb +30 -0
- data/lib/rmega/nodes/inbox.rb +8 -0
- data/lib/rmega/nodes/node.rb +58 -100
- data/lib/rmega/nodes/root.rb +12 -0
- data/lib/rmega/nodes/trash.rb +18 -0
- data/lib/rmega/nodes/traversable.rb +26 -0
- data/lib/rmega/options.rb +16 -0
- data/lib/rmega/pool.rb +4 -6
- data/lib/rmega/progress.rb +34 -0
- data/lib/rmega/{api_request_error.rb → request_error.rb} +7 -10
- data/lib/rmega/session.rb +20 -31
- data/lib/rmega/storage.rb +19 -68
- data/lib/rmega/uploader.rb +58 -0
- data/lib/rmega/utils.rb +35 -29
- data/lib/rmega/version.rb +1 -1
- data/lib/rmega.rb +8 -53
- data/rmega.gemspec +2 -2
- data/spec/integration/{file_operations_spec.rb → file_download_spec.rb} +4 -8
- data/spec/integration/folder_operations_spec.rb +27 -15
- data/spec/integration/login_spec.rb +6 -5
- data/spec/integration_spec_helper.rb +12 -4
- data/spec/rmega/lib/utils_spec.rb +0 -7
- data/spec/spec_helper.rb +4 -11
- metadata +18 -24
- data/lib/rmega/nodes/file_node.rb +0 -27
- data/lib/rmega/nodes/folder_node.rb +0 -29
    
        data/lib/rmega/nodes/node.rb
    CHANGED
    
    | @@ -1,122 +1,80 @@ | |
| 1 | 
            +
            require 'rmega/loggable'
         | 
| 2 | 
            +
            require 'rmega/utils'
         | 
| 3 | 
            +
            require 'rmega/crypto/crypto'
         | 
| 4 | 
            +
            require 'rmega/nodes/traversable'
         | 
| 5 | 
            +
             | 
| 1 6 | 
             
            module Rmega
         | 
| 2 | 
            -
               | 
| 3 | 
            -
                 | 
| 7 | 
            +
              module Nodes
         | 
| 8 | 
            +
                class Node
         | 
| 9 | 
            +
                  include Loggable
         | 
| 10 | 
            +
                  include Traversable
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  attr_reader :data, :session
         | 
| 4 13 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
                  @session = session
         | 
| 14 | 
            +
                  delegate :storage, :request, :to => :session
         | 
| 7 15 |  | 
| 8 | 
            -
                   | 
| 9 | 
            -
                    @ | 
| 10 | 
            -
                    @public_url = data
         | 
| 11 | 
            -
                  else
         | 
| 16 | 
            +
                  def initialize(session, data)
         | 
| 17 | 
            +
                    @session = session
         | 
| 12 18 | 
             
                    @data = data
         | 
| 13 19 | 
             
                  end
         | 
| 14 | 
            -
                end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                def self.fabricate session, data
         | 
| 17 | 
            -
                  type_name = mega_url?(data) ? :file : type_by_number(data['t'])
         | 
| 18 | 
            -
                  node_class = Rmega.const_get("#{type_name}_node".camelize) rescue nil
         | 
| 19 | 
            -
                  node_class ||= Rmega::Node
         | 
| 20 | 
            -
                  node_class.new session, data
         | 
| 21 | 
            -
                end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                def self.types
         | 
| 24 | 
            -
                  {file: 0, folder: 1, root: 2, inbox: 3, trash: 4}
         | 
| 25 | 
            -
                end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                def self.type_by_number number
         | 
| 28 | 
            -
                  founded_type = types.find { |k, v| number == v }
         | 
| 29 | 
            -
                  founded_type.first if founded_type
         | 
| 30 | 
            -
                end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                def self.mega_url? url
         | 
| 33 | 
            -
                  !!(url.to_s =~ /^https:\/\/mega\.co\.nz\/#!.*$/i)
         | 
| 34 | 
            -
                end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                def logger
         | 
| 37 | 
            -
                  Rmega.logger
         | 
| 38 | 
            -
                end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                # Member actions
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                def public_url
         | 
| 43 | 
            -
                  return @public_url if @public_url
         | 
| 44 | 
            -
                  return nil if type != :file
         | 
| 45 | 
            -
                  b64_dec_key = Utils.a32_to_base64 decrypted_file_key[0..7]
         | 
| 46 | 
            -
                  "https://mega.co.nz/#!#{public_handle}!#{b64_dec_key}"
         | 
| 47 | 
            -
                end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                def trash
         | 
| 50 | 
            -
                  trash_node_public_handle = storage.trash_node.public_handle
         | 
| 51 | 
            -
                  request a: 'm', n: handle, t: trash_node_public_handle
         | 
| 52 | 
            -
                end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
                # Delegate to session
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                delegate :storage, :request, :to => :session
         | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
                # Other methods
         | 
| 61 20 |  | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
                  @public_handle ||= request(a: 'l', n: handle)
         | 
| 69 | 
            -
                end
         | 
| 21 | 
            +
                  def public_url
         | 
| 22 | 
            +
                    @public_url ||= begin
         | 
| 23 | 
            +
                      b64_dec_key = Utils.a32_to_base64 decrypted_file_key[0..7]
         | 
| 24 | 
            +
                      "https://mega.co.nz/#!#{public_handle}!#{b64_dec_key}"
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 70 27 |  | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 28 | 
            +
                  def public_url=(url)
         | 
| 29 | 
            +
                    @public_url = url
         | 
| 30 | 
            +
                  end
         | 
| 74 31 |  | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 32 | 
            +
                  def public_handle
         | 
| 33 | 
            +
                    @public_handle ||= request(a: 'l', n: handle)
         | 
| 34 | 
            +
                  end
         | 
| 78 35 |  | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 36 | 
            +
                  def handle
         | 
| 37 | 
            +
                    data['h']
         | 
| 38 | 
            +
                  end
         | 
| 82 39 |  | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 40 | 
            +
                  def parent_handle
         | 
| 41 | 
            +
                    data['p']
         | 
| 42 | 
            +
                  end
         | 
| 86 43 |  | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 44 | 
            +
                  def owner_key
         | 
| 45 | 
            +
                    data['k'].split(':').first
         | 
| 46 | 
            +
                  end
         | 
| 90 47 |  | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 48 | 
            +
                  def name
         | 
| 49 | 
            +
                    return attributes['n'] if attributes
         | 
| 50 | 
            +
                  end
         | 
| 94 51 |  | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
                    Crypto.decrypt_key session.master_key, Utils.base64_to_a32(file_key)
         | 
| 98 | 
            -
                  else
         | 
| 99 | 
            -
                    Utils.base64_to_a32 public_url.split('!').last
         | 
| 52 | 
            +
                  def file_key
         | 
| 53 | 
            +
                    data['k'].split(':').last
         | 
| 100 54 | 
             
                  end
         | 
| 101 | 
            -
                end
         | 
| 102 55 |  | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 56 | 
            +
                  def decrypted_file_key
         | 
| 57 | 
            +
                    if data['k']
         | 
| 58 | 
            +
                      Crypto.decrypt_key session.master_key, Utils.base64_to_a32(file_key)
         | 
| 59 | 
            +
                    else
         | 
| 60 | 
            +
                      Utils.base64_to_a32 public_url.split('!').last
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                  end
         | 
| 106 63 |  | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
                    return nil unless can_decrypt_attributes?
         | 
| 110 | 
            -
                    Crypto.decrypt_attributes decrypted_file_key, (data['a'] || data['at'])
         | 
| 64 | 
            +
                  def can_decrypt_attributes?
         | 
| 65 | 
            +
                    !data['u'] or data['u'] == owner_key
         | 
| 111 66 | 
             
                  end
         | 
| 112 | 
            -
                end
         | 
| 113 67 |  | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 68 | 
            +
                  def attributes
         | 
| 69 | 
            +
                    @attributes ||= begin
         | 
| 70 | 
            +
                      return nil unless can_decrypt_attributes?
         | 
| 71 | 
            +
                      Crypto.decrypt_attributes decrypted_file_key, (data['a'] || data['at'])
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
                  end
         | 
| 117 74 |  | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 75 | 
            +
                  def type
         | 
| 76 | 
            +
                    Factory.type(data['t'])
         | 
| 77 | 
            +
                  end
         | 
| 120 78 | 
             
                end
         | 
| 121 79 | 
             
              end
         | 
| 122 80 | 
             
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            require 'rmega/nodes/node'
         | 
| 2 | 
            +
            require 'rmega/nodes/traversable'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Rmega
         | 
| 5 | 
            +
              module Nodes
         | 
| 6 | 
            +
                class Trash < Node
         | 
| 7 | 
            +
                  include Traversable
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def empty!
         | 
| 10 | 
            +
                    children.each do |node|
         | 
| 11 | 
            +
                      node.delete if node.respond_to?(:delete)
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    empty?
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            module Rmega
         | 
| 2 | 
            +
              module Nodes
         | 
| 3 | 
            +
                module Traversable
         | 
| 4 | 
            +
                  def children
         | 
| 5 | 
            +
                    storage.nodes.select { |node| node.parent_handle == handle }
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def folders
         | 
| 9 | 
            +
                    children.select { |node| node.type == :folder }
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def files
         | 
| 13 | 
            +
                    children.select { |node| node.type == :file }
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def parent
         | 
| 17 | 
            +
                    return unless parent_handle
         | 
| 18 | 
            +
                    storage.nodes.find { |node| node.handle == parent_handle }
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def empty?
         | 
| 22 | 
            +
                    children.size == 0
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            require 'ostruct'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rmega
         | 
| 4 | 
            +
              def self.default_options
         | 
| 5 | 
            +
                {
         | 
| 6 | 
            +
                  show_progress:        true,
         | 
| 7 | 
            +
                  upload_timeout:       120,
         | 
| 8 | 
            +
                  api_request_timeout:  20,
         | 
| 9 | 
            +
                  api_url:              'https://eu.api.mega.co.nz/cs'
         | 
| 10 | 
            +
                }
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def self.options
         | 
| 14 | 
            +
                @options ||= OpenStruct.new(default_options)
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
    
        data/lib/rmega/pool.rb
    CHANGED
    
    | @@ -1,13 +1,11 @@ | |
| 1 1 | 
             
            require 'thread'
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
              # Helper to create a Pool instance.
         | 
| 5 | 
            -
              def self.pool(max)
         | 
| 6 | 
            -
                Pool.new(max)
         | 
| 7 | 
            -
              end
         | 
| 8 | 
            -
             | 
| 3 | 
            +
            module Rmega
         | 
| 9 4 | 
             
              class Pool
         | 
| 5 | 
            +
                MAX = 5
         | 
| 6 | 
            +
             | 
| 10 7 | 
             
                def initialize(max)
         | 
| 8 | 
            +
                  max ||= MAX
         | 
| 11 9 | 
             
                  Thread.abort_on_exception = true
         | 
| 12 10 | 
             
                  @mutex = Mutex.new
         | 
| 13 11 | 
             
                  @threads = Array.new(max)
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            module Rmega
         | 
| 2 | 
            +
              class Progress
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def initialize(params)
         | 
| 5 | 
            +
                  @filesize = params[:filesize]
         | 
| 6 | 
            +
                  @verb = params[:verb].capitalize
         | 
| 7 | 
            +
                  @progress = 0
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  render
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def render
         | 
| 13 | 
            +
                  percentage = (100.0 * @progress / @filesize).round(2)
         | 
| 14 | 
            +
                  message = "#{@verb} in progress #{format_bytes(@progress)} of #{format_bytes(@filesize)} (#{percentage}%)"
         | 
| 15 | 
            +
                  rtrn = "\n" if @filesize == @progress
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  print "\r#{' '*(message.size + 15)}\r#{message}#{rtrn}"
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def increment(bytes)
         | 
| 21 | 
            +
                  @progress += bytes
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  render
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def format_bytes(bytes, round = 2)
         | 
| 27 | 
            +
                  units = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB']
         | 
| 28 | 
            +
                  e = (bytes == 0 ? 0 : Math.log(bytes)) / Math.log(1024)
         | 
| 29 | 
            +
                  value = bytes.to_f / (1024 ** e.floor)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  "#{value.round(round)}#{units[e]}"
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
| @@ -1,18 +1,15 @@ | |
| 1 1 | 
             
            module Rmega
         | 
| 2 | 
            -
              class  | 
| 3 | 
            -
                 | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
                  @code = error_code
         | 
| 7 | 
            -
                  error_description = self.class.all_messages[@code]
         | 
| 8 | 
            -
                  super "Received error code #{@code}. #{error_description}".strip
         | 
| 2 | 
            +
              class RequestError < StandardError
         | 
| 3 | 
            +
                def initialize(error_code)
         | 
| 4 | 
            +
                  message = self.class.errors[error_code]
         | 
| 5 | 
            +
                  super("Error #{error_code}: #{message}")
         | 
| 9 6 | 
             
                end
         | 
| 10 7 |  | 
| 11 | 
            -
                def self. | 
| 12 | 
            -
                  ( | 
| 8 | 
            +
                def self.error_code?(number)
         | 
| 9 | 
            +
                  number.respond_to?(:to_i) and number.to_i < 0
         | 
| 13 10 | 
             
                end
         | 
| 14 11 |  | 
| 15 | 
            -
                def self. | 
| 12 | 
            +
                def self.errors
         | 
| 16 13 | 
             
                  {
         | 
| 17 14 | 
             
                    -1  => 'An internal error has occurred. Please submit a bug report, detailing the exact circumstances in which this error occurred.',
         | 
| 18 15 | 
             
                    -2  => 'You have passed invalid arguments to this command.',
         | 
    
        data/lib/rmega/session.rb
    CHANGED
    
    | @@ -1,63 +1,52 @@ | |
| 1 | 
            +
            require 'rmega/storage'
         | 
| 2 | 
            +
            require 'rmega/request_error'
         | 
| 3 | 
            +
            require 'rmega/crypto/crypto'
         | 
| 4 | 
            +
            require 'rmega/utils'
         | 
| 5 | 
            +
             | 
| 1 6 | 
             
            module Rmega
         | 
| 7 | 
            +
              def self.login(email, password)
         | 
| 8 | 
            +
                Session.new(email, password).storage
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 2 11 | 
             
              class Session
         | 
| 12 | 
            +
                include Loggable
         | 
| 13 | 
            +
             | 
| 3 14 | 
             
                attr_accessor :email, :request_id, :sid, :master_key
         | 
| 4 15 |  | 
| 5 | 
            -
                def initialize | 
| 16 | 
            +
                def initialize(email, password)
         | 
| 6 17 | 
             
                  self.email = email
         | 
| 7 18 | 
             
                  self.request_id = random_request_id
         | 
| 8 19 |  | 
| 9 | 
            -
                  login | 
| 20 | 
            +
                  login(password)
         | 
| 10 21 | 
             
                end
         | 
| 11 22 |  | 
| 12 | 
            -
                def logger
         | 
| 13 | 
            -
                  Rmega.logger
         | 
| 14 | 
            -
                end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
                # Delegate to Rmega.options
         | 
| 18 | 
            -
             | 
| 19 23 | 
             
                def options
         | 
| 20 24 | 
             
                  Rmega.options
         | 
| 21 25 | 
             
                end
         | 
| 22 26 |  | 
| 23 | 
            -
                 | 
| 24 | 
            -
                  options.api_request_timeout
         | 
| 25 | 
            -
                end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                def api_url
         | 
| 28 | 
            -
                  options.api_url
         | 
| 29 | 
            -
                end
         | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
                # Cache the Storage class
         | 
| 27 | 
            +
                delegate :api_url, :api_request_timeout, to: :options
         | 
| 33 28 |  | 
| 34 29 | 
             
                def storage
         | 
| 35 | 
            -
                  @storage ||= Storage.new | 
| 30 | 
            +
                  @storage ||= Storage.new(self)
         | 
| 36 31 | 
             
                end
         | 
| 37 32 |  | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
                def login password_str
         | 
| 42 | 
            -
                  uh = Crypto.stringhash Crypto.prepare_key_pw(password_str), email
         | 
| 33 | 
            +
                def login(password)
         | 
| 34 | 
            +
                  uh = Crypto.stringhash Crypto.prepare_key_pw(password), email
         | 
| 43 35 | 
             
                  resp = request a: 'us', user: email, uh: uh
         | 
| 44 36 |  | 
| 45 37 | 
             
                  # Decrypt the master key
         | 
| 46 | 
            -
                  encrypted_key = Crypto.prepare_key_pw  | 
| 38 | 
            +
                  encrypted_key = Crypto.prepare_key_pw password
         | 
| 47 39 | 
             
                  self.master_key = Crypto.decrypt_key encrypted_key, Utils.base64_to_a32(resp['k'])
         | 
| 48 40 |  | 
| 49 41 | 
             
                  # Generate the session id
         | 
| 50 42 | 
             
                  self.sid = Crypto.decrypt_sid master_key, resp['csid'], resp['privk']
         | 
| 51 43 | 
             
                end
         | 
| 52 44 |  | 
| 53 | 
            -
             | 
| 54 | 
            -
                # Api requests methods
         | 
| 55 | 
            -
             | 
| 56 45 | 
             
                def random_request_id
         | 
| 57 46 | 
             
                  rand(1E7..1E9).to_i
         | 
| 58 47 | 
             
                end
         | 
| 59 48 |  | 
| 60 | 
            -
                def request | 
| 49 | 
            +
                def request(body)
         | 
| 61 50 | 
             
                  self.request_id += 1
         | 
| 62 51 | 
             
                  url = "#{api_url}?id=#{request_id}"
         | 
| 63 52 | 
             
                  url << "&sid=#{sid}" if sid
         | 
| @@ -66,7 +55,7 @@ module Rmega | |
| 66 55 | 
             
                  response = HTTPClient.new.post url, [body].to_json, timeout: api_request_timeout
         | 
| 67 56 | 
             
                  logger.debug "#{response.code}\n#{response.body}"
         | 
| 68 57 | 
             
                  resp = JSON.parse(response.body).first
         | 
| 69 | 
            -
                  raise  | 
| 58 | 
            +
                  raise RequestError.new(resp) if RequestError.error_code?(resp)
         | 
| 70 59 | 
             
                  resp
         | 
| 71 60 | 
             
                end
         | 
| 72 61 | 
             
              end
         | 
    
        data/lib/rmega/storage.rb
    CHANGED
    
    | @@ -1,19 +1,17 @@ | |
| 1 | 
            +
            require 'rmega/utils'
         | 
| 2 | 
            +
            require 'rmega/crypto/crypto'
         | 
| 3 | 
            +
            require 'rmega/nodes/factory'
         | 
| 4 | 
            +
             | 
| 1 5 | 
             
            module Rmega
         | 
| 2 6 | 
             
              class Storage
         | 
| 7 | 
            +
                include Loggable
         | 
| 3 8 |  | 
| 4 9 | 
             
                attr_reader :session
         | 
| 5 10 |  | 
| 6 | 
            -
                def initialize | 
| 11 | 
            +
                def initialize(session)
         | 
| 7 12 | 
             
                  @session = session
         | 
| 8 13 | 
             
                end
         | 
| 9 14 |  | 
| 10 | 
            -
                def logger
         | 
| 11 | 
            -
                  Rmega.logger
         | 
| 12 | 
            -
                end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
                # Quota-related methods
         | 
| 16 | 
            -
             | 
| 17 15 | 
             
                def used_space
         | 
| 18 16 | 
             
                  quota['cstrg']
         | 
| 19 17 | 
             
                end
         | 
| @@ -26,81 +24,34 @@ module Rmega | |
| 26 24 | 
             
                  session.request a: 'uq', strg: 1
         | 
| 27 25 | 
             
                end
         | 
| 28 26 |  | 
| 29 | 
            -
             | 
| 30 | 
            -
                # Nodes management
         | 
| 31 | 
            -
             | 
| 32 27 | 
             
                def nodes
         | 
| 33 | 
            -
                   | 
| 34 | 
            -
                   | 
| 35 | 
            -
                end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                def nodes_by_type type
         | 
| 38 | 
            -
                  nodes.select { |n| n.type == type }
         | 
| 39 | 
            -
                end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                def nodes_by_name name_regexp
         | 
| 42 | 
            -
                  nodes.select do |node|
         | 
| 43 | 
            -
                    node.name and node.name =~ name_regexp
         | 
| 44 | 
            -
                  end
         | 
| 45 | 
            -
                end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                def trash_node
         | 
| 48 | 
            -
                  @trash ||= nodes_by_type(:trash).first
         | 
| 28 | 
            +
                  result = session.request(a: 'f', c: 1)
         | 
| 29 | 
            +
                  result['f'].map { |node_data| Nodes::Factory.build(session, node_data) }
         | 
| 49 30 | 
             
                end
         | 
| 50 31 |  | 
| 51 | 
            -
                def  | 
| 52 | 
            -
                  @ | 
| 32 | 
            +
                def trash
         | 
| 33 | 
            +
                  @trash ||= nodes.find { |n| n.type == :trash }
         | 
| 53 34 | 
             
                end
         | 
| 54 35 |  | 
| 55 | 
            -
                def  | 
| 56 | 
            -
                   | 
| 36 | 
            +
                def root
         | 
| 37 | 
            +
                  @root_node ||= nodes.find { |n| n.type == :root }
         | 
| 57 38 | 
             
                end
         | 
| 58 39 |  | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
                def self.chunks size
         | 
| 63 | 
            -
                  list = {}
         | 
| 64 | 
            -
                  p = 0
         | 
| 65 | 
            -
                  pp = 0
         | 
| 66 | 
            -
                  i = 1
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                  while i <= 8 and p < size - (i * 0x20000)
         | 
| 69 | 
            -
                    list[p] = i * 0x20000
         | 
| 70 | 
            -
                    pp = p
         | 
| 71 | 
            -
                    p += list[p]
         | 
| 72 | 
            -
                    i += 1
         | 
| 73 | 
            -
                  end
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                  while p < size
         | 
| 76 | 
            -
                    list[p] = 0x100000
         | 
| 77 | 
            -
                    pp = p
         | 
| 78 | 
            -
                    p += list[p]
         | 
| 79 | 
            -
                  end
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                  if size - pp > 0
         | 
| 82 | 
            -
                    list[pp] = size - pp
         | 
| 83 | 
            -
                  end
         | 
| 84 | 
            -
                  list
         | 
| 85 | 
            -
                end
         | 
| 86 | 
            -
             | 
| 87 | 
            -
                def download public_url, path
         | 
| 88 | 
            -
                  Node.fabricate(session, public_url).download(path)
         | 
| 40 | 
            +
                def download(public_url, path)
         | 
| 41 | 
            +
                  Nodes::Factory.build_from_url(session, public_url).download(path)
         | 
| 89 42 | 
             
                end
         | 
| 90 43 |  | 
| 91 | 
            -
             | 
| 92 | 
            -
                 | 
| 93 | 
            -
             | 
| 94 | 
            -
                def upload_url filesize
         | 
| 44 | 
            +
                # TODO: refactor upload part
         | 
| 45 | 
            +
                def upload_url(filesize)
         | 
| 95 46 | 
             
                  session.request(a: 'u', s: filesize)['p']
         | 
| 96 47 | 
             
                end
         | 
| 97 48 |  | 
| 98 | 
            -
                def upload_chunk | 
| 49 | 
            +
                def upload_chunk(url, start, chunk)
         | 
| 99 50 | 
             
                  response = HTTPClient.new.post "#{url}/#{start}", chunk, timeout: Rmega.options.upload_timeout
         | 
| 100 51 | 
             
                  response.body
         | 
| 101 52 | 
             
                end
         | 
| 102 53 |  | 
| 103 | 
            -
                def upload | 
| 54 | 
            +
                def upload(local_path, parent_node = root)
         | 
| 104 55 | 
             
                  local_path = File.expand_path local_path
         | 
| 105 56 | 
             
                  filesize = File.size local_path
         | 
| 106 57 | 
             
                  upld_url = upload_url filesize
         | 
| @@ -114,7 +65,7 @@ module Rmega | |
| 114 65 |  | 
| 115 66 | 
             
                  Utils.show_progress :upload, filesize
         | 
| 116 67 |  | 
| 117 | 
            -
                   | 
| 68 | 
            +
                  Utils.chunks(filesize).each do |chunk_start, chunk_size|
         | 
| 118 69 | 
             
                    buffer = local_file.read chunk_size
         | 
| 119 70 |  | 
| 120 71 | 
             
                    # TODO: should be (chunk_start/0x1000000000) >>> 0, (chunk_start/0x10) >>> 0
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            require 'rmega/loggable'
         | 
| 2 | 
            +
            require 'rmega/utils'
         | 
| 3 | 
            +
            require 'rmega/pool'
         | 
| 4 | 
            +
            require 'rmega/progress'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Rmega
         | 
| 7 | 
            +
              class Uploader
         | 
| 8 | 
            +
                include Loggable
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                attr_reader :pool, :base_url, :filesize, :local_path, :last_result
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def initialize(params)
         | 
| 13 | 
            +
                  @pool = Pool.new(params[:threads])
         | 
| 14 | 
            +
                  @filesize = params[:filesize]
         | 
| 15 | 
            +
                  @base_url = params[:base_url]
         | 
| 16 | 
            +
                  @local_path = params[:local_path]
         | 
| 17 | 
            +
                  @last_result = nil
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def upload_chunk(start, buffer)
         | 
| 21 | 
            +
                  size = buffer.length
         | 
| 22 | 
            +
                  stop = start + size - 1
         | 
| 23 | 
            +
                  url = "#{base_url}/#{start}-#{stop}"
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  HTTPClient.new.post(url, buffer).body
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def read_chunk(start, size)
         | 
| 29 | 
            +
                  @local_file.seek(start)
         | 
| 30 | 
            +
                  @local_file.read(size)
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def chunks
         | 
| 34 | 
            +
                  Utils.chunks(filesize)
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def upload(&block)
         | 
| 38 | 
            +
                  @local_file = ::File.open(local_path, 'rb')
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  progress = Progress.new(filesize: filesize, verb: 'upload')
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  chunks.each do |start, size|
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    pool.defer do
         | 
| 45 | 
            +
                      clean_buffer = pool.syncronize { read_chunk(start, size) }
         | 
| 46 | 
            +
                      encrypted_buffer = yield(start, clean_buffer)
         | 
| 47 | 
            +
                      @last_result = upload_chunk(start, encrypted_buffer)
         | 
| 48 | 
            +
                      progress.increment(buffer.size)
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  pool.wait_done
         | 
| 53 | 
            +
                  pool.shutdown
         | 
| 54 | 
            +
                ensure
         | 
| 55 | 
            +
                  @local_file.close
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         |