lib/rapidshare-ext/api.rb in rapidshare-ext-0.0.6 vs lib/rapidshare-ext/api.rb in rapidshare-ext-0.1.0

- old
+ new

@@ -1,532 +1,532 @@ -module Rapidshare - module Ext - module API - - FILE_COLUMNS = "downloads,lastdownload,filename,size,serverid,type,x,y,realfolder,killdeadline,uploadtime,comment,md5hex,licids,sentby" - - # @param [String] path Folder name with absolute path to be created - # @param [Hash] params - # @return [Integer] - # - # Creates a folder in a Rapidshare virtual filesystem - # - # api.add_folder("/a/b/c") #=> <Random folder ID from Rapidshare>, 1234 for example - def add_folder(path, params = {}) - path = path_trim path - - @tree = folders_hierarchy - i = 1 - parent = 0 - folder_id = nil - while i <= path.split('/').count do - base_path = path.split('/')[0,i].join('/') - folder_id = self.folder_id base_path - if folder_id - parent = folder_id - i += 1 - else - # Create folder - folder_name = path.split('/')[i-1] - add_folder_params = { - :name => folder_name, - :parent => parent - }.merge params - - # The following code deals with #{} because of rest client #to_i returns HTTP code - folder_id = "#{addrealfolder(add_folder_params)}".to_i - raise "error while creating folder" if parent < 0 - @tree[folder_id] = { - :parent => parent, - :name => folder_name, - :path => path_canonize((@tree[parent] || {})[:path].to_s + ('/' if @tree[parent]).to_s + folder_name) - } - parent = folder_id - path == base_path + '/' + folder_name - i += 1 - next - end - end - folder_id - end - - # Removes a specified folder - # - # @param [String] path - # @param [Hash] params - # @return [Array] - # - # api.remove_folder("/a/b/c") - def remove_folder(path, params = {}) - folder_id = self.folder_id path_trim(path) - raise Exception, "Folder #{path} could not be found" if folder_id.nil? - - # TODO - tree = folders_hierarchy :from => path - tree.each_pair do |child_folder_id, data| - delrealfolder_params = { - :realfolder => child_folder_id - }.merge params - - delrealfolder delrealfolder_params - @tree.delete folder_id - end - - params = { - :realfolder => folder_id - }.merge params - - delrealfolder params - - @tree.delete folder_id - end - - # Moves folder into a specified one - # - # @param [String] source_path - # @param [Hash] params - # :to => <destination folder path>, default: "/" - # - # api.move_folder("/a/b/c", :to => "/a") - def move_folder(source_path, params = {}) - dest_path = (params.delete(:to) || '/') - source_folder_id = folder_id(source_path) - dest_folder_id = folder_id(dest_path) - - params = { - :realfolder => source_folder_id, - :newparent => dest_folder_id - }.merge params - - moverealfolder params - - @tree = folders_hierarchy - @tree[source_folder_id][:parent] = dest_folder_id - @tree[source_folder_id][:path] = path_canonize "#{folder_path(dest_folder_id)}/#{@tree[source_folder_id][:name]}" - true - end - - # Upload file to a specified folder - # - # @param [String] file_path - # @param [Hash] params - # <tt>:to</tt>:: - # Folder to place uploaded file to, default: "/" - # <tt>:as</tt>:: - # The name file will have in storage after it has been uploaded - # <tt>:overwrite</tt>:: - # Overwrite file if it already exists in the given folder - # - # api.upload("/home/odiszapc/my_damn_cat.mov", :to => "/gallery/video", :as => "cat1.mov") - def upload(file_path, params = {}) - raise Exception unless File.exist? file_path - dest_path = path_trim(params.delete(:to) || '/') - folder_id = self.add_folder dest_path - file_name = params.delete(:as) || File.basename(file_path) - overwrite = params.delete :overwrite - - # Check file already exists within a folder - listfiles_params = { - :realfolder => folder_id, - :filename => "#{file_name}", - :fields => "md5hex,size", - :parser => :csv - } - listfiles_response = self.listfiles listfiles_params - - file_already_exists = ("NONE" != listfiles_response[0][0]) - remove_file "#{dest_path}/#{file_name}" if file_already_exists && overwrite - - # In case of file is not existing then upload it - if !file_already_exists || overwrite - upload_server = "rs#{self.nextuploadserver}.rapidshare.com" - - upload_params = { - :server => upload_server, - :folder => folder_id, - :filename => file_name, - :filecontent => file_path, - :method => :post, - :parser => :csv - }.merge params - - resp = request(:upload, upload_params) - raise Exception, "File uploading failed: #{resp.inspect}" unless "COMPLETE" == resp[0][0] - - id = resp[1][0].to_i - md5_hash = resp[1][3] - size = resp[1][2].to_i - already_exists = false - else - id = listfiles_response[0][0].to_i - md5_hash = listfiles_response[0][1] - size = listfiles_response[0][2].to_i - already_exists = true - end - - raise Exception, "Invalid File ID: #{resp.inspect}" unless id - raise Exception, "Invalid MD5 hash: #{resp.inspect}" unless md5_hash - raise Exception, "Invalid File Size: #{resp.inspect}" unless size - - { - :id => id, - :size => size, - :checksum => md5_hash.downcase, - :url => "https://rapidshare.com/files/#{id}/#{URI::encode file_name}", - :already_exists? => already_exists - } - end - - # Delete file - # - # @param [String] path - # @param [Hash] params - # - # api.remove_file("/putin/is/a/good/reason/to/live/abroad/ticket_to_Nikaragua.jpg") - def remove_file(path, params = {}) - params = { - :files => file_id(path).to_s - }.merge params - - deletefiles params - end - - # Rename file - # - # @param [String] remote_path - # @param [String] name - # @param [Hash] params - # - # api.rename_file("/foo/bar.rar", "baz.rar") - def rename_file(remote_path, name, params = {}) - file_id = file_id remote_path - - params = { - :fileid => file_id, - :newname => name - }.merge params - - renamefile params - # TODO: duplicates check - end - - # Moves file to a specified folder - # - # @param [String] remote_path - # @param [Hash] params - # <tt>:to</tt>:: - # Destination folder path, default: "/" - # - # api.move_file("/foo/bar/baz.rar", :to => "/foo") - # api.move_file("/foo/bar/baz.rar") # move to a root folder - def move_file(remote_path, params = {}) - file_id = file_id remote_path - dest_path = path_trim(params.delete(:to) || '/') - - params = { - :files => file_id, - :realfolder => folder_id(dest_path) - }.merge params - - movefilestorealfolder params - end - - # See #folders_hierarchy method - def folders_hierarchy!(params = {}) - params[:force] = true - folders_hierarchy params - end - - alias :reload! :folders_hierarchy! - - # Build folders hierarchy in the following format: - # { - # <folder ID> => { - # :parent => <parent folder ID>, - # :name => <folder name>, - # :path => <folder absolute path> - # }, - # ... - # } - # - # @param [Hash] params - # <tt>:force</tt>:: - # Invalidate cached tree, default: false - # After each call of this method the generated tree will be saved as cache - # to avoid unnecessary queries to be performed fpr a future calls - # <tt>:validate</tt>:: - # Validate tree after it has been generated, default: true - # <tt>:consistent</tt>:: - # Delete all found orphans, default: false - # Ignored if :validate is set to false - def folders_hierarchy(params = {}) - force_load = params.delete :force - from_folder_path = path_trim(params.delete(:from) || '/') - remove_orphans = params.delete(:consistent) - perform_validation = params.delete(:validate) - perform_validation = true if perform_validation.nil? - remove_orphans = false unless perform_validation - - if @tree && !force_load - if from_folder_path.empty? - return @tree - else - return slice_tree @tree, :from => from_folder_path - end - end - - return @tree if @tree && !force_load # TODO: about slices here (:from parameter) - @tree = {} - - from_folder_id = folder_id from_folder_path - raise Exception, "Folder #{from_folder_path} could not be found" if from_folder_id.nil? - - response = listrealfolders - - if 'NONE' == response - @tree = {} - else - intermediate = response.split(' ').map do |str| - params = str.split ',' - [params[0].to_i, {:parent => params[1].to_i, :name => params[2]}] - end - - @tree = Hash[intermediate] - end - - # Kill orphans - remove_orphans! if remove_orphans - - @tree.each_pair do |folder_id, data| - @tree[folder_id][:path] = folder_path folder_id - end - - if perform_validation - # Validate folder tree consistency - @tree.each_pair do |folder_id, data| - parent_id = data[:parent] - if !parent_id.zero? && @tree[parent_id].nil? - error = "Directory tree consistency error. Parent folder ##{data[:parent]} for the folder \"#{data[:path]}\" [#{folder_id}] could not be found" - raise error - end - end - end - - @tree = slice_tree @tree, :from => from_folder_path unless from_folder_path.empty? - @tree - end - - # Build tree relative to a specified folder - # If the source tree is: - # tree = { - # 1 => {:parent => 0, :name => "a", :path => "a"}, - # 2 => {:parent => 1, :name => "b", :path => "a/b"}, - # 3 => {:parent => 2, :name => "c", :path => "a/b/c"}, - # ... - # } - # slice_tree tree, :from => "/a" - # Result will be as follows: - # { - # 2 => {:parent => 1, :name => "b", :path => "b"}, - # 3 => {:parent => 2, :name => "c", :path => "b/c"}, - # ... - # } - def slice_tree(tree, params = {}) - from_folder_path = path_trim(params.delete(:from) || '/') - - result_tree = tree.dup - - unless from_folder_path == '' - - result_tree.keep_if do |folder_id, data| - path_trim(data[:path]).start_with? "#{from_folder_path}/" - end - - result_tree.each_pair do |folder_id, data| - path = result_tree[folder_id][:path] - result_tree[folder_id][:path] = path_canonize path_trim(path.gsub /#{from_folder_path.gsub /\//, '\/'}\//, '') - end - end - - result_tree - end - - # Fix inconsistent folder tree (Yes, getting a broken folder hierarchy is possible with a stupid Rapidshare API) - # by deleting orphan folders (folders with no parent folder), this folders are invisible in Rapidshare File Manager - # So, this method deletes orphan folders - def remove_orphans! - @tree = folders_hierarchy :validate => false - @tree.each_pair do |folder_id, data| - @tree.delete_if do |folder_id, data| - if orphan? folder_id - delrealfolder :realfolder => folder_id - true - end - end - end - end - - # Places all existing orphan folders under the specific folder - # Orphan folder is a folder with non existing parent (yes, it's possible) - # - # Example: - # move_orphans :to => "/" - def move_orphans(params = {}) - new_folder = path_trim(params.delete(:to) || '/') - gaps = detect_gaps - - if gaps.any? - params = { - :realfolder => gaps.join(','), - :newparent => new_folder - }.merge params - moverealfolder params - end - end - - # Returns gap list between folders - # See #gap? for example - def detect_gaps - @tree = folders_hierarchy :validate => false - @tree.dup.keep_if do |folder_id, data| - gap? folder_id # This is wrong - end.keys - end - - # The name speaks for itself - # WARNING!!! All data will be lost!!! - # Use it carefully - def erase_all_data! - @tree = folders_hierarchy! :validate => false - @tree.keys.each do |folder_id| - delrealfolder :realfolder => folder_id - end - folders_hierarchy! - end - - # Check if folder with given id placed on the bottom of folder hierarchy - def root_folder?(folder_id) - @tree = folders_hierarchy :validate => false - return false if @tree[folder_id].nil? - @tree[folder_id][:parent].zero? - end - - # Check if the given folder has no parent - def gap?(folder_id) - @tree = folders_hierarchy :validate => false - parent_id = @tree[folder_id][:parent] - @tree[parent_id].nil? - end - - # Check if folder has any gaps in it hierarchy - # For example we have the following hierarchy: - # - # ROOT - # `-a <- if we remove just this folder then the folder "c" and "b" will become orphans - # `-b - # `-c - def orphan?(folder_id) - @tree = folders_hierarchy :validate => false - return false if @tree[folder_id].nil? - parent_id = @tree[folder_id][:parent] - return false if root_folder? folder_id - return true if gap? folder_id - orphan?(parent_id) - end - - # Translate folder ID to a human readable path - # - # api.folder_path(123) # -> "foo/bar/baz" - def folder_path(folder_id) - @tree = folders_hierarchy - - folder_data = @tree[folder_id] || {:parent => 0, :name => "<undefined>", :path => "<undefined>"} - - parent_id = folder_data[:parent] - path = (folder_path(parent_id) if parent_id.nonzero?).to_s + ('/' if parent_id.nonzero?).to_s + folder_data[:name] - parent_id.zero? ? "/#{path}" : path - end - - # Get folder ID by path - # - # api.folder_id("foo/bar/baz") # -> 123 - def folder_id(folder_path) - folder_path = path_trim(folder_path) - return 0 if folder_path.empty? - - @tree = folders_hierarchy - index = @tree.find_index do |folder_id, data| - path_trim(data[:path]) == path_trim(folder_path) - end - @tree.keys[index] unless index.nil? - end - - # Get file info in the following format: - # - # { - # :downloads, - # :lastdownload, - # :filename, - # :size, - # :serverid, - # :type, - # :x, - # :y, - # :realfolder, - # :killdeadline, - # :uploadtime, - # :comment, - # :md5hex, - # :licids, - # :sentby - # } - # See the http://images.rapidshare.com/apidoc.txt for more details - def file_info(file_path, params = {}) - folder_path = File.dirname file_path - file_name = File.basename file_path - - folder_id = folder_id folder_path - - listfiles_params = { - :realfolder => folder_id, - :filename => "#{file_name}", - :fields => FILE_COLUMNS, - :parser => :csv - }.merge params - - resp = listfiles(listfiles_params)[0] - return nil if "NONE" == resp[0] - - response = {} - - fields = listfiles_params[:fields].split(',') - fields.unshift "id" - fields.each_with_index do |value, index| - response[value.to_sym] = resp[index] - end - - response[:url] = "https://rapidshare.com/files/#{response[:id]}/#{URI::encode response[:filename]}" if response[:filename] - - response - end - - # Returns file ID by absolute path - # - # api.file_id("foo/bar/baz/file.rar") # => <FILE_ID> - def file_id(file_path, params = {}) - params[:fields] = "" - file_info = file_info file_path, params - (file_info || {})[:id].to_i - end - - protected - - def path_trim(path) - path.gsub(/\A\/+/, '').gsub(/\/+\Z/, '') - end - - def path_canonize(path) - "/" + path_trim(path) - end - end - end +module Rapidshare + module Ext + module API + + FILE_COLUMNS = 'downloads,lastdownload,filename,size,serverid,type,x,y,realfolder,killdeadline,uploadtime,comment,md5hex,licids,sentby' + + # @param [String] path Folder name with absolute path to be created + # @param [Hash] params + # @return [Integer] + # + # Creates a folder in a Rapidshare virtual filesystem + # + # api.add_folder('/a/b/c') #=> <Random folder ID from Rapidshare>, 1234 for example + def add_folder(path, params = {}) + path = path_trim path + + @tree = folders_hierarchy + i = 1 + parent = 0 + folder_id = nil + while i <= path.split('/').count do + base_path = path.split('/')[0,i].join('/') + folder_id = self.folder_id base_path + if folder_id + parent = folder_id + i += 1 + else + # Create folder + folder_name = path.split('/')[i-1] + add_folder_params = { + :name => folder_name, + :parent => parent + }.merge params + + # The following code deals with #{} because of rest client #to_i returns HTTP code + folder_id = "#{addrealfolder(add_folder_params)}".to_i + raise 'error while creating folder' if parent < 0 + @tree[folder_id] = { + :parent => parent, + :name => folder_name, + :path => path_canonize((@tree[parent] || {})[:path].to_s + ('/' if @tree[parent]).to_s + folder_name) + } + parent = folder_id + path == base_path + '/' + folder_name + i += 1 + next + end + end + folder_id + end + + # Removes a specified folder + # + # @param [String] path + # @param [Hash] params + # @return [Array] + # + # api.remove_folder('/a/b/c') + def remove_folder(path, params = {}) + folder_id = self.folder_id path_trim(path) + raise Exception, "Folder #{path} could not be found" if folder_id.nil? + + # TODO + tree = folders_hierarchy :from => path + tree.each_pair do |child_folder_id, data| + delrealfolder_params = { + :realfolder => child_folder_id + }.merge params + + delrealfolder delrealfolder_params + @tree.delete folder_id + end + + params = { + :realfolder => folder_id + }.merge params + + delrealfolder params + + @tree.delete folder_id + end + + # Moves folder into a specified one + # + # @param [String] source_path + # @param [Hash] params + # :to => <destination folder path>, default: '/' + # + # api.move_folder('/a/b/c', :to => '/a') + def move_folder(source_path, params = {}) + dest_path = (params.delete(:to) || '/') + source_folder_id = folder_id(source_path) + dest_folder_id = folder_id(dest_path) + + params = { + :realfolder => source_folder_id, + :newparent => dest_folder_id + }.merge params + + moverealfolder params + + @tree = folders_hierarchy + @tree[source_folder_id][:parent] = dest_folder_id + @tree[source_folder_id][:path] = path_canonize "#{folder_path(dest_folder_id)}/#{@tree[source_folder_id][:name]}" + true + end + + # Upload file to a specified folder + # + # @param [String] file_path + # @param [Hash] params + # <tt>:to</tt>:: + # Folder to place uploaded file to, default: '/' + # <tt>:as</tt>:: + # The name file will have in storage after it has been uploaded + # <tt>:overwrite</tt>:: + # Overwrite file if it already exists in the given folder + # + # api.upload('/home/odiszapc/my_damn_cat.mov', :to => '/gallery/video', :as => 'cat1.mov') + def upload(file_path, params = {}) + raise Exception unless File.exist? file_path + dest_path = path_trim(params.delete(:to) || '/') + folder_id = self.add_folder dest_path + file_name = params.delete(:as) || File.basename(file_path) + overwrite = params.delete :overwrite + + # Check file already exists within a folder + listfiles_params = { + :realfolder => folder_id, + :filename => "#{file_name}", + :fields => 'md5hex,size', + :parser => :csv + } + listfiles_response = self.listfiles listfiles_params + + file_already_exists = ('NONE' != listfiles_response[0][0]) + remove_file "#{dest_path}/#{file_name}" if file_already_exists && overwrite + + # In case of file is not existing then upload it + if !file_already_exists || overwrite + upload_server = "rs#{self.nextuploadserver}.rapidshare.com" + + upload_params = { + :server => upload_server, + :folder => folder_id, + :filename => file_name, + :filecontent => file_path, + :method => :post, + :parser => :csv + }.merge params + + resp = request(:upload, upload_params) + raise Exception, "File uploading failed: #{resp.inspect}" unless "COMPLETE" == resp[0][0] + + id = resp[1][0].to_i + md5_hash = resp[1][3] + size = resp[1][2].to_i + already_exists = false + else + id = listfiles_response[0][0].to_i + md5_hash = listfiles_response[0][1] + size = listfiles_response[0][2].to_i + already_exists = true + end + + raise Exception, "Invalid File ID: #{resp.inspect}" unless id + raise Exception, "Invalid MD5 hash: #{resp.inspect}" unless md5_hash + raise Exception, "Invalid File Size: #{resp.inspect}" unless size + + { + :id => id, + :size => size, + :checksum => md5_hash.downcase, + :url => "https://rapidshare.com/files/#{id}/#{URI::encode file_name}", + :already_exists? => already_exists + } + end + + # Delete file + # + # @param [String] path + # @param [Hash] params + # + # api.remove_file('/putin/is/a/good/reason/to/live/abroad/ticket_to_Nikaragua.jpg') + def remove_file(path, params = {}) + params = { + :files => file_id(path).to_s + }.merge params + + deletefiles params + end + + # Rename file + # + # @param [String] remote_path + # @param [String] name + # @param [Hash] params + # + # api.rename_file('/foo/bar.rar', 'baz.rar') + def rename_file(remote_path, name, params = {}) + file_id = file_id remote_path + + params = { + :fileid => file_id, + :newname => name + }.merge params + + renamefile params + # TODO: duplicates check + end + + # Moves file to a specified folder + # + # @param [String] remote_path + # @param [Hash] params + # <tt>:to</tt>:: + # Destination folder path, default: '/' + # + # api.move_file('/foo/bar/baz.rar', :to => '/foo') + # api.move_file('/foo/bar/baz.rar') # move to a root folder + def move_file(remote_path, params = {}) + file_id = file_id remote_path + dest_path = path_trim(params.delete(:to) || '/') + + params = { + :files => file_id, + :realfolder => folder_id(dest_path) + }.merge params + + movefilestorealfolder params + end + + # See #folders_hierarchy method + def folders_hierarchy!(params = {}) + params[:force] = true + folders_hierarchy params + end + + alias :reload! :folders_hierarchy! + + # Build folders hierarchy in the following format: + # { + # <folder ID> => { + # :parent => <parent folder ID>, + # :name => <folder name>, + # :path => <folder absolute path> + # }, + # ... + # } + # + # @param [Hash] params + # <tt>:force</tt>:: + # Invalidate cached tree, default: false + # After each call of this method the generated tree will be saved as cache + # to avoid unnecessary queries to be performed fpr a future calls + # <tt>:validate</tt>:: + # Validate tree after it has been generated, default: true + # <tt>:consistent</tt>:: + # Delete all found orphans, default: false + # Ignored if :validate is set to false + def folders_hierarchy(params = {}) + force_load = params.delete :force + from_folder_path = path_trim(params.delete(:from) || '/') + remove_orphans = params.delete(:consistent) + perform_validation = params.delete(:validate) + perform_validation = true if perform_validation.nil? + remove_orphans = false unless perform_validation + + if @tree && !force_load + if from_folder_path.empty? + return @tree + else + return slice_tree @tree, :from => from_folder_path + end + end + + return @tree if @tree && !force_load # TODO: about slices here (:from parameter) + @tree = {} + + from_folder_id = folder_id from_folder_path + raise Exception, "Folder #{from_folder_path} could not be found" if from_folder_id.nil? + + response = listrealfolders + + if 'NONE' == response + @tree = {} + else + intermediate = response.split(' ').map do |str| + params = str.split ',' + [params[0].to_i, {:parent => params[1].to_i, :name => params[2]}] + end + + @tree = Hash[intermediate] + end + + # Kill orphans + remove_orphans! if remove_orphans + + @tree.each_pair do |folder_id, data| + @tree[folder_id][:path] = folder_path folder_id + end + + if perform_validation + # Validate folder tree consistency + @tree.each_pair do |folder_id, data| + parent_id = data[:parent] + if !parent_id.zero? && @tree[parent_id].nil? + error = "Directory tree consistency error. Parent folder ##{data[:parent]} for the folder \"#{data[:path]}\" [#{folder_id}] could not be found" + raise error + end + end + end + + @tree = slice_tree @tree, :from => from_folder_path unless from_folder_path.empty? + @tree + end + + # Build tree relative to a specified folder + # If the source tree is: + # tree = { + # 1 => {:parent => 0, :name => 'a', :path => 'a'}, + # 2 => {:parent => 1, :name => 'b', :path => 'a/b'}, + # 3 => {:parent => 2, :name => 'c', :path => 'a/b/c'}, + # ... + # } + # slice_tree tree, :from => '/a' + # Result will be as follows: + # { + # 2 => {:parent => 1, :name => 'b', :path => 'b'}, + # 3 => {:parent => 2, :name => 'c', :path => 'b/c'}, + # ... + # } + def slice_tree(tree, params = {}) + from_folder_path = path_trim(params.delete(:from) || '/') + + result_tree = tree.dup + + unless from_folder_path == '' + + result_tree.keep_if do |folder_id, data| + path_trim(data[:path]).start_with? "#{from_folder_path}/" + end + + result_tree.each_pair do |folder_id, data| + path = result_tree[folder_id][:path] + result_tree[folder_id][:path] = path_canonize path_trim(path.gsub /#{from_folder_path.gsub /\//, '\/'}\//, '') + end + end + + result_tree + end + + # Fix inconsistent folder tree (Yes, getting a broken folder hierarchy is possible with a stupid Rapidshare API) + # by deleting orphan folders (folders with no parent folder), this folders are invisible in Rapidshare File Manager + # So, this method deletes orphan folders + def remove_orphans! + @tree = folders_hierarchy :validate => false + @tree.each_pair do |folder_id, data| + @tree.delete_if do |folder_id, data| + if orphan? folder_id + delrealfolder :realfolder => folder_id + true + end + end + end + end + + # Places all existing orphan folders under the specific folder + # Orphan folder is a folder with non existing parent (yes, it's possible) + # + # Example: + # move_orphans :to => '/' + def move_orphans(params = {}) + new_folder = path_trim(params.delete(:to) || '/') + gaps = detect_gaps + + if gaps.any? + params = { + :realfolder => gaps.join(','), + :newparent => new_folder + }.merge params + moverealfolder params + end + end + + # Returns gap list between folders + # See #gap? for example + def detect_gaps + @tree = folders_hierarchy :validate => false + @tree.dup.keep_if do |folder_id, data| + gap? folder_id # This is wrong + end.keys + end + + # The name speaks for itself + # WARNING!!! All data will be lost!!! + # Use it carefully + def erase_all_data! + @tree = folders_hierarchy! :validate => false + @tree.keys.each do |folder_id| + delrealfolder :realfolder => folder_id + end + folders_hierarchy! + end + + # Check if folder with given id placed on the bottom of folder hierarchy + def root_folder?(folder_id) + @tree = folders_hierarchy :validate => false + return false if @tree[folder_id].nil? + @tree[folder_id][:parent].zero? + end + + # Check if the given folder has no parent + def gap?(folder_id) + @tree = folders_hierarchy :validate => false + parent_id = @tree[folder_id][:parent] + @tree[parent_id].nil? + end + + # Check if folder has any gaps in it hierarchy + # For example we have the following hierarchy: + # + # ROOT + # `-a <- if we remove just this folder then the folder 'c' and 'b' will become orphans + # `-b + # `-c + def orphan?(folder_id) + @tree = folders_hierarchy :validate => false + return false if @tree[folder_id].nil? + parent_id = @tree[folder_id][:parent] + return false if root_folder? folder_id + return true if gap? folder_id + orphan?(parent_id) + end + + # Translate folder ID to a human readable path + # + # api.folder_path(123) # -> 'foo/bar/baz' + def folder_path(folder_id) + @tree = folders_hierarchy + + folder_data = @tree[folder_id] || {:parent => 0, :name => '<undefined>', :path => '<undefined>'} + + parent_id = folder_data[:parent] + path = (folder_path(parent_id) if parent_id.nonzero?).to_s + ('/' if parent_id.nonzero?).to_s + folder_data[:name] + parent_id.zero? ? "/#{path}" : path + end + + # Get folder ID by path + # + # api.folder_id('foo/bar/baz') # -> 123 + def folder_id(folder_path) + folder_path = path_trim(folder_path) + return 0 if folder_path.empty? + + @tree = folders_hierarchy + index = @tree.find_index do |folder_id, data| + path_trim(data[:path]) == path_trim(folder_path) + end + @tree.keys[index] unless index.nil? + end + + # Get file info in the following format: + # + # { + # :downloads, + # :lastdownload, + # :filename, + # :size, + # :serverid, + # :type, + # :x, + # :y, + # :realfolder, + # :killdeadline, + # :uploadtime, + # :comment, + # :md5hex, + # :licids, + # :sentby + # } + # See the http://images.rapidshare.com/apidoc.txt for more details + def file_info(file_path, params = {}) + folder_path = File.dirname file_path + file_name = File.basename file_path + + folder_id = folder_id folder_path + + listfiles_params = { + :realfolder => folder_id, + :filename => "#{file_name}", + :fields => FILE_COLUMNS, + :parser => :csv + }.merge params + + resp = listfiles(listfiles_params)[0] + return nil if 'NONE' == resp[0] + + response = {} + + fields = listfiles_params[:fields].split(',') + fields.unshift 'id' + fields.each_with_index do |value, index| + response[value.to_sym] = resp[index] + end + + response[:url] = "https://rapidshare.com/files/#{response[:id]}/#{URI::encode response[:filename]}" if response[:filename] + + response + end + + # Returns file ID by absolute path + # + # api.file_id('foo/bar/baz/file.rar') # => <FILE_ID> + def file_id(file_path, params = {}) + params[:fields] = '' + file_info = file_info file_path, params + (file_info || {})[:id].to_i + end + + protected + + def path_trim(path) + path.gsub(/\A\/+/, '').gsub(/\/+\Z/, '') + end + + def path_canonize(path) + '/' + path_trim(path) + end + end + end end \ No newline at end of file