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") #=> , 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 => , 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 # :to:: # Folder to place uploaded file to, default: "/" # :as:: # The name file will have in storage after it has been uploaded # # 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) # 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 # In case of file is not existing upload it if "NONE" == listfiles_response[0][0] 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 # :to:: # 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: # { # => { # :parent => , # :name => , # :path => # }, # ... # } # # @param [Hash] params # :force:: # 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 # :consistent:: # Delete all found orphans, default: false def folders_hierarchy(params = {}) force_load = params.delete :force from_folder_path = path_trim(params.delete(:from) || '/') remove_orphans = params.delete(:consistent) 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 # Validate folder tree consistency @tree.each_pair do |folder_id, data| parent_id = data[:parent] if !parent_id.zero? && @tree[parent_id].nil? error = "There is no parent folder with id ##{data[:parent]} for the folder \"#{data[:name]}\" [#{folder_id}]" raise error end end @tree.each_pair do |folder_id, data| @tree[folder_id][:path] = folder_path folder_id 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 @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 = folder_id params[:to].to_s orphans = detect_gaps.join(',') if orphans.any? moverealfolder :realfolder => orphans.join(','), :newparent => new_folder end end # Returns gap list between folders # See #gap? for example def detect_gaps @tree = folders_hierarchy @tree.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 @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 @tree[folder_id][:parent].zero? end # Check if the given folder has no parent def gap?(folder_id) @tree = folders_hierarchy 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 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 parent_id = @tree[folder_id][:parent] path = (folder_path(parent_id) if parent_id.nonzero?).to_s + ('/' if parent_id.nonzero?).to_s + @tree[folder_id][: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 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") # => 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