rapidshare-ext 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/.gitignore +16 -17
- data/History.md +8 -0
- data/README.md +243 -221
- data/Rakefile +3 -3
- data/lib/rapidshare-base/api.rb +136 -6
- data/lib/rapidshare-base/utils.rb +39 -0
- data/lib/rapidshare-ext.rb +17 -11
- data/lib/rapidshare-ext/api.rb +531 -531
- data/lib/rapidshare-ext/download.rb +79 -7
- data/lib/rapidshare-ext/version.rb +5 -5
- data/rapidshare-ext.gemspec +11 -10
- data/test/fixtures/checkfiles_multi.txt +3 -0
- data/test/fixtures/checkfiles_single.txt +1 -0
- data/test/fixtures/files/upload3.txt +100 -0
- data/test/fixtures/getrapidtranslogs.txt +5 -0
- data/test/integration/rapidshare-base_test.rb +278 -0
- data/test/integration/rapidshare-ext_test.rb +176 -95
- data/test/test_helper.rb +43 -43
- data/test/unit/rapidshare-ext_test.rb +27 -27
- data/tmp/.gitignore +2 -0
- metadata +56 -29
data/Rakefile
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
|
-
require
|
2
|
+
require 'bundler/gem_tasks'
|
3
3
|
require 'rake/testtask'
|
4
4
|
include Rake::DSL
|
5
5
|
|
6
|
-
desc
|
7
|
-
task :test =>
|
6
|
+
desc 'Run tests'
|
7
|
+
task :test => %w{test:unit test:integration}
|
8
8
|
|
9
9
|
task :default => :test
|
10
10
|
|
data/lib/rapidshare-base/api.rb
CHANGED
@@ -1,5 +1,51 @@
|
|
1
1
|
module Rapidshare
|
2
2
|
class API
|
3
|
+
include Rapidshare::Utils
|
4
|
+
extend Rapidshare::Utils
|
5
|
+
|
6
|
+
attr_reader :cookie
|
7
|
+
|
8
|
+
ERROR_PREFIX = 'ERROR: ' unless defined?(ERROR_PREFIX)
|
9
|
+
|
10
|
+
# custom errors for Rapidshare::API class
|
11
|
+
class Error < StandardError; end
|
12
|
+
class Error::LoginFailed < StandardError; end
|
13
|
+
class Error::InvalidRoutineCalled < StandardError; end
|
14
|
+
|
15
|
+
# Request method uses this string to construct GET requests
|
16
|
+
#
|
17
|
+
URL = 'https://api.rapidshare.com/cgi-bin/rsapi.cgi?sub=%s&%s'
|
18
|
+
|
19
|
+
# Connects to Rapidshare API (which basically means: uses login and password
|
20
|
+
# to retrieve cookie for future service calls)
|
21
|
+
#
|
22
|
+
# Params:
|
23
|
+
# * *login* - premium account login
|
24
|
+
# * *password* - premium account password
|
25
|
+
# * *cookie* - cookie can be provided instead of login and password
|
26
|
+
# * *free_user* (boolean) - if user identifies himself as free user by setting this to *true*, skip login
|
27
|
+
#
|
28
|
+
# Instead of params hash, you can pass only cookie as a string
|
29
|
+
#
|
30
|
+
# PS: *free_user* option is a beta feature, to be properly implemented
|
31
|
+
#
|
32
|
+
def initialize(params)
|
33
|
+
# if there's "just one param", it's a cookie
|
34
|
+
params = { :cookie => params } if params.is_a? String
|
35
|
+
|
36
|
+
# skip login for free users
|
37
|
+
return nil if params[:free_user]
|
38
|
+
|
39
|
+
if params[:cookie]
|
40
|
+
@cookie = params[:cookie]
|
41
|
+
# throws LoginFailed exception if cookie is invalid
|
42
|
+
get_account_details()
|
43
|
+
else
|
44
|
+
response = get_account_details(params.merge(:withcookie => 1))
|
45
|
+
@cookie = response[:cookie]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
3
49
|
# TODO this class is getting long. keep general request-related and helper
|
4
50
|
# method here and move specific method calls like :getaccountdetails to other
|
5
51
|
# class (service)?
|
@@ -18,7 +64,7 @@ module Rapidshare
|
|
18
64
|
# * *none* - default value, returns response body as it is
|
19
65
|
# * *csv* - comma-separated values, for example: _getrapidtranslogs_.
|
20
66
|
# Returns array or arrays, one array per each line.
|
21
|
-
# * *hash* - lines with key and value separated by
|
67
|
+
# * *hash* - lines with key and value separated by '=', for example:
|
22
68
|
# _getaccountdetails_. Returns hash.
|
23
69
|
# * *server* - option, determines which server will be used to send request
|
24
70
|
#
|
@@ -45,10 +91,10 @@ module Rapidshare
|
|
45
91
|
end
|
46
92
|
|
47
93
|
if response.start_with?(ERROR_PREFIX)
|
48
|
-
case error = response.sub(ERROR_PREFIX,
|
49
|
-
when
|
94
|
+
case error = response.sub(ERROR_PREFIX, '').split('.').first
|
95
|
+
when 'Login failed'
|
50
96
|
raise Rapidshare::API::Error::LoginFailed
|
51
|
-
when
|
97
|
+
when 'Invalid routine called'
|
52
98
|
raise Rapidshare::API::Error::InvalidRoutineCalled.new(service_name)
|
53
99
|
else
|
54
100
|
raise Rapidshare::API::Error.new(error)
|
@@ -58,14 +104,98 @@ module Rapidshare
|
|
58
104
|
self.parse_response(parser, response)
|
59
105
|
end
|
60
106
|
|
61
|
-
|
107
|
+
# Provides instance interface to class method +request+.
|
108
|
+
#
|
109
|
+
def request(service_name, params = {})
|
110
|
+
self.class.request(service_name, params.merge(:cookie => @cookie))
|
111
|
+
end
|
112
|
+
|
113
|
+
# Parses response from +request+ method (parser options are listed there)
|
114
|
+
#
|
115
|
+
def self.parse_response(parser, response)
|
116
|
+
case parser.to_sym
|
117
|
+
when :none
|
118
|
+
response
|
119
|
+
when :csv
|
120
|
+
# PS: we could use gem for csv parsing, but that's an overkill in this
|
121
|
+
# case, IMHO
|
122
|
+
response.to_s.strip.split(/\s*\n\s*/).map { |line| line.split(',') }
|
123
|
+
when :hash
|
124
|
+
text_to_hash(response)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Attempts to do Rapidshare service call. If it doesn't recognize the method
|
129
|
+
# name, this gem assumes that user wants to make a Rapidshare service call.
|
130
|
+
#
|
131
|
+
# This method also handles aliases for service calls:
|
132
|
+
# get_account_details -> getaccountdetails
|
133
|
+
#
|
134
|
+
def method_missing(service_name, params = {})
|
135
|
+
# remove user-friendly underscores from service names
|
136
|
+
service_name = service_name.to_s.gsub('_', '')
|
137
|
+
|
138
|
+
if respond_to?(service_name)
|
139
|
+
send(service_name, params)
|
140
|
+
else
|
141
|
+
request(service_name, params)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns account details in hash.
|
146
|
+
#
|
147
|
+
def getaccountdetails(params = {})
|
148
|
+
request :getaccountdetails, params.merge( :parser => :hash)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Retrieves information about RapidShare files.
|
152
|
+
#
|
153
|
+
# *Input:* array of files
|
154
|
+
#
|
155
|
+
# Examples: +checkfiles(file1)+, +checkfiles(file1,file2)+ or +checkfiles([file1,file2])+
|
156
|
+
#
|
157
|
+
# *Output:* array of hashes, which contain information about files
|
158
|
+
# * *:file_id* (string) - part of url
|
159
|
+
#
|
160
|
+
# Example: https://rapidshare.com/files/829628035/HornyRhinos.jpg -> +829628035+
|
161
|
+
# * *:file_name* (string) - part of url
|
162
|
+
#
|
163
|
+
# Example: https://rapidshare.com/files/829628035/HornyRhinos.jpg -> +HornyRhinos.jpg+
|
164
|
+
# * *:file_size* (integer) - in bytes. returns 0 if files does not exists
|
165
|
+
# * *:file_status* - decoded file status: +:ok+ or +:error+
|
166
|
+
# * *:short_host* - used to construct download url
|
167
|
+
# * *:server_id* - used to construct download url
|
168
|
+
# * *:md5*
|
169
|
+
#
|
170
|
+
def checkfiles(*urls)
|
171
|
+
raise Rapidshare::API::Error if urls.empty?
|
172
|
+
|
173
|
+
files, filenames = urls.flatten.map { |url| fileid_and_filename(url) }.transpose
|
174
|
+
|
175
|
+
response = request(:checkfiles, :files => files.join(','), :filenames => filenames.join(','))
|
176
|
+
|
177
|
+
response.strip.split(/\s*\n\s*/).map do |r|
|
178
|
+
data = r.split ','
|
179
|
+
{
|
180
|
+
:file_id => data[0],
|
181
|
+
:file_name => data[1],
|
182
|
+
:file_size => data[2],
|
183
|
+
:server_id => data[3],
|
184
|
+
:file_status => decode_file_status(data[4].to_i),
|
185
|
+
:short_host => data[5],
|
186
|
+
:md5 => data[6]
|
187
|
+
}
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def download(file, params= {}, &block)
|
62
192
|
if file.match /\Ahttps?:\/\//
|
63
193
|
url = file
|
64
194
|
else
|
65
195
|
url = file_info(file)[:url]
|
66
196
|
end
|
67
197
|
|
68
|
-
Rapidshare::Ext::Download.new(url, self, params).perform
|
198
|
+
Rapidshare::Ext::Download.new(url, self, params).perform &block
|
69
199
|
end
|
70
200
|
end
|
71
201
|
end
|
@@ -1,5 +1,11 @@
|
|
1
1
|
module Rapidshare
|
2
|
+
|
3
|
+
# Contains utility methods which can be called both as class and instance methods
|
4
|
+
#
|
2
5
|
module Utils
|
6
|
+
|
7
|
+
# From historical purposes GET and POST methods a distinguished.
|
8
|
+
# They will be merged in a future release
|
3
9
|
def post(url, params)
|
4
10
|
params[:filecontent] = File.new(params[:filecontent])
|
5
11
|
::RestClient.post(url, params)
|
@@ -8,5 +14,38 @@ module Rapidshare
|
|
8
14
|
def get(url)
|
9
15
|
::RestClient.get(url)
|
10
16
|
end
|
17
|
+
|
18
|
+
# Convert file status code (returned by checkfiles method) to +:ok+ or +:error+ symbol.
|
19
|
+
#
|
20
|
+
def decode_file_status(status_code)
|
21
|
+
# TODO in checkfiles, return both file_status as is and decoded file status
|
22
|
+
# or just boolean value if file is OK and can be downloaded
|
23
|
+
|
24
|
+
case status_code
|
25
|
+
when 0 then :error # File not found
|
26
|
+
when 1 then :ok # File OK
|
27
|
+
when 3 then :error # Server down
|
28
|
+
when 4 then :error # File marked as illegal
|
29
|
+
else :error # unknown status, this shouldn't happen
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Extracts file id and file name from Rapidshare url. Returns both in array.
|
34
|
+
#
|
35
|
+
# Example:
|
36
|
+
# https://rapidshare.com/files/829628035/HornyRhinos.jpg -> [ '829628035', 'HornyRhinos.jpg' ]
|
37
|
+
#
|
38
|
+
def fileid_and_filename(url)
|
39
|
+
url.split('/').slice(-2,2) || ['', '']
|
40
|
+
end
|
41
|
+
|
42
|
+
# Converts rapidshare response (which is just a text in specific format) to hash.
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
# "key1=value1\nkey1=value2" -> { :key1 => 'value1', :key2 => 'value2' }
|
46
|
+
#
|
47
|
+
def text_to_hash(response)
|
48
|
+
Hash[ response.strip.split(/\s*\n\s*/).map { |param| param.split('=') } ].symbolize_keys
|
49
|
+
end
|
11
50
|
end
|
12
51
|
end
|
data/lib/rapidshare-ext.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
|
-
require 'rest-client'
|
2
|
-
require '
|
3
|
-
|
4
|
-
|
5
|
-
require '
|
6
|
-
require '
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
require 'rest-client'
|
2
|
+
#require 'progressbar'
|
3
|
+
|
4
|
+
# active_support helpers
|
5
|
+
require 'active_support/core_ext/object/to_query'
|
6
|
+
require 'active_support/core_ext/hash/keys'
|
7
|
+
|
8
|
+
require 'rapidshare-base/utils'
|
9
|
+
require 'rapidshare-base/api'
|
10
|
+
|
11
|
+
require 'rapidshare-ext/api'
|
12
|
+
require 'rapidshare-ext/download'
|
13
|
+
require 'rapidshare-ext/version'
|
14
|
+
|
15
|
+
class Rapidshare::API
|
16
|
+
include Rapidshare::Ext::API
|
17
|
+
end
|
data/lib/rapidshare-ext/api.rb
CHANGED
@@ -1,532 +1,532 @@
|
|
1
|
-
module Rapidshare
|
2
|
-
module Ext
|
3
|
-
module API
|
4
|
-
|
5
|
-
FILE_COLUMNS =
|
6
|
-
|
7
|
-
# @param [String] path Folder name with absolute path to be created
|
8
|
-
# @param [Hash] params
|
9
|
-
# @return [Integer]
|
10
|
-
#
|
11
|
-
# Creates a folder in a Rapidshare virtual filesystem
|
12
|
-
#
|
13
|
-
# api.add_folder(
|
14
|
-
def add_folder(path, params = {})
|
15
|
-
path = path_trim path
|
16
|
-
|
17
|
-
@tree = folders_hierarchy
|
18
|
-
i = 1
|
19
|
-
parent = 0
|
20
|
-
folder_id = nil
|
21
|
-
while i <= path.split('/').count do
|
22
|
-
base_path = path.split('/')[0,i].join('/')
|
23
|
-
folder_id = self.folder_id base_path
|
24
|
-
if folder_id
|
25
|
-
parent = folder_id
|
26
|
-
i += 1
|
27
|
-
else
|
28
|
-
# Create folder
|
29
|
-
folder_name = path.split('/')[i-1]
|
30
|
-
add_folder_params = {
|
31
|
-
:name => folder_name,
|
32
|
-
:parent => parent
|
33
|
-
}.merge params
|
34
|
-
|
35
|
-
# The following code deals with #{} because of rest client #to_i returns HTTP code
|
36
|
-
folder_id = "#{addrealfolder(add_folder_params)}".to_i
|
37
|
-
raise
|
38
|
-
@tree[folder_id] = {
|
39
|
-
:parent => parent,
|
40
|
-
:name => folder_name,
|
41
|
-
:path => path_canonize((@tree[parent] || {})[:path].to_s + ('/' if @tree[parent]).to_s + folder_name)
|
42
|
-
}
|
43
|
-
parent = folder_id
|
44
|
-
path == base_path + '/' + folder_name
|
45
|
-
i += 1
|
46
|
-
next
|
47
|
-
end
|
48
|
-
end
|
49
|
-
folder_id
|
50
|
-
end
|
51
|
-
|
52
|
-
# Removes a specified folder
|
53
|
-
#
|
54
|
-
# @param [String] path
|
55
|
-
# @param [Hash] params
|
56
|
-
# @return [Array]
|
57
|
-
#
|
58
|
-
# api.remove_folder(
|
59
|
-
def remove_folder(path, params = {})
|
60
|
-
folder_id = self.folder_id path_trim(path)
|
61
|
-
raise Exception, "Folder #{path} could not be found" if folder_id.nil?
|
62
|
-
|
63
|
-
# TODO
|
64
|
-
tree = folders_hierarchy :from => path
|
65
|
-
tree.each_pair do |child_folder_id, data|
|
66
|
-
delrealfolder_params = {
|
67
|
-
:realfolder => child_folder_id
|
68
|
-
}.merge params
|
69
|
-
|
70
|
-
delrealfolder delrealfolder_params
|
71
|
-
@tree.delete folder_id
|
72
|
-
end
|
73
|
-
|
74
|
-
params = {
|
75
|
-
:realfolder => folder_id
|
76
|
-
}.merge params
|
77
|
-
|
78
|
-
delrealfolder params
|
79
|
-
|
80
|
-
@tree.delete folder_id
|
81
|
-
end
|
82
|
-
|
83
|
-
# Moves folder into a specified one
|
84
|
-
#
|
85
|
-
# @param [String] source_path
|
86
|
-
# @param [Hash] params
|
87
|
-
# :to => <destination folder path>, default:
|
88
|
-
#
|
89
|
-
# api.move_folder(
|
90
|
-
def move_folder(source_path, params = {})
|
91
|
-
dest_path = (params.delete(:to) || '/')
|
92
|
-
source_folder_id = folder_id(source_path)
|
93
|
-
dest_folder_id = folder_id(dest_path)
|
94
|
-
|
95
|
-
params = {
|
96
|
-
:realfolder => source_folder_id,
|
97
|
-
:newparent => dest_folder_id
|
98
|
-
}.merge params
|
99
|
-
|
100
|
-
moverealfolder params
|
101
|
-
|
102
|
-
@tree = folders_hierarchy
|
103
|
-
@tree[source_folder_id][:parent] = dest_folder_id
|
104
|
-
@tree[source_folder_id][:path] = path_canonize "#{folder_path(dest_folder_id)}/#{@tree[source_folder_id][:name]}"
|
105
|
-
true
|
106
|
-
end
|
107
|
-
|
108
|
-
# Upload file to a specified folder
|
109
|
-
#
|
110
|
-
# @param [String] file_path
|
111
|
-
# @param [Hash] params
|
112
|
-
# <tt>:to</tt>::
|
113
|
-
# Folder to place uploaded file to, default:
|
114
|
-
# <tt>:as</tt>::
|
115
|
-
# The name file will have in storage after it has been uploaded
|
116
|
-
# <tt>:overwrite</tt>::
|
117
|
-
# Overwrite file if it already exists in the given folder
|
118
|
-
#
|
119
|
-
# api.upload(
|
120
|
-
def upload(file_path, params = {})
|
121
|
-
raise Exception unless File.exist? file_path
|
122
|
-
dest_path = path_trim(params.delete(:to) || '/')
|
123
|
-
folder_id = self.add_folder dest_path
|
124
|
-
file_name = params.delete(:as) || File.basename(file_path)
|
125
|
-
overwrite = params.delete :overwrite
|
126
|
-
|
127
|
-
# Check file already exists within a folder
|
128
|
-
listfiles_params = {
|
129
|
-
:realfolder => folder_id,
|
130
|
-
:filename => "#{file_name}",
|
131
|
-
:fields =>
|
132
|
-
:parser => :csv
|
133
|
-
}
|
134
|
-
listfiles_response = self.listfiles listfiles_params
|
135
|
-
|
136
|
-
file_already_exists = (
|
137
|
-
remove_file "#{dest_path}/#{file_name}" if file_already_exists && overwrite
|
138
|
-
|
139
|
-
# In case of file is not existing then upload it
|
140
|
-
if !file_already_exists || overwrite
|
141
|
-
upload_server = "rs#{self.nextuploadserver}.rapidshare.com"
|
142
|
-
|
143
|
-
upload_params = {
|
144
|
-
:server => upload_server,
|
145
|
-
:folder => folder_id,
|
146
|
-
:filename => file_name,
|
147
|
-
:filecontent => file_path,
|
148
|
-
:method => :post,
|
149
|
-
:parser => :csv
|
150
|
-
}.merge params
|
151
|
-
|
152
|
-
resp = request(:upload, upload_params)
|
153
|
-
raise Exception, "File uploading failed: #{resp.inspect}" unless "COMPLETE" == resp[0][0]
|
154
|
-
|
155
|
-
id = resp[1][0].to_i
|
156
|
-
md5_hash = resp[1][3]
|
157
|
-
size = resp[1][2].to_i
|
158
|
-
already_exists = false
|
159
|
-
else
|
160
|
-
id = listfiles_response[0][0].to_i
|
161
|
-
md5_hash = listfiles_response[0][1]
|
162
|
-
size = listfiles_response[0][2].to_i
|
163
|
-
already_exists = true
|
164
|
-
end
|
165
|
-
|
166
|
-
raise Exception, "Invalid File ID: #{resp.inspect}" unless id
|
167
|
-
raise Exception, "Invalid MD5 hash: #{resp.inspect}" unless md5_hash
|
168
|
-
raise Exception, "Invalid File Size: #{resp.inspect}" unless size
|
169
|
-
|
170
|
-
{
|
171
|
-
:id => id,
|
172
|
-
:size => size,
|
173
|
-
:checksum => md5_hash.downcase,
|
174
|
-
:url => "https://rapidshare.com/files/#{id}/#{URI::encode file_name}",
|
175
|
-
:already_exists? => already_exists
|
176
|
-
}
|
177
|
-
end
|
178
|
-
|
179
|
-
# Delete file
|
180
|
-
#
|
181
|
-
# @param [String] path
|
182
|
-
# @param [Hash] params
|
183
|
-
#
|
184
|
-
# api.remove_file(
|
185
|
-
def remove_file(path, params = {})
|
186
|
-
params = {
|
187
|
-
:files => file_id(path).to_s
|
188
|
-
}.merge params
|
189
|
-
|
190
|
-
deletefiles params
|
191
|
-
end
|
192
|
-
|
193
|
-
# Rename file
|
194
|
-
#
|
195
|
-
# @param [String] remote_path
|
196
|
-
# @param [String] name
|
197
|
-
# @param [Hash] params
|
198
|
-
#
|
199
|
-
# api.rename_file(
|
200
|
-
def rename_file(remote_path, name, params = {})
|
201
|
-
file_id = file_id remote_path
|
202
|
-
|
203
|
-
params = {
|
204
|
-
:fileid => file_id,
|
205
|
-
:newname => name
|
206
|
-
}.merge params
|
207
|
-
|
208
|
-
renamefile params
|
209
|
-
# TODO: duplicates check
|
210
|
-
end
|
211
|
-
|
212
|
-
# Moves file to a specified folder
|
213
|
-
#
|
214
|
-
# @param [String] remote_path
|
215
|
-
# @param [Hash] params
|
216
|
-
# <tt>:to</tt>::
|
217
|
-
# Destination folder path, default:
|
218
|
-
#
|
219
|
-
# api.move_file(
|
220
|
-
# api.move_file(
|
221
|
-
def move_file(remote_path, params = {})
|
222
|
-
file_id = file_id remote_path
|
223
|
-
dest_path = path_trim(params.delete(:to) || '/')
|
224
|
-
|
225
|
-
params = {
|
226
|
-
:files => file_id,
|
227
|
-
:realfolder => folder_id(dest_path)
|
228
|
-
}.merge params
|
229
|
-
|
230
|
-
movefilestorealfolder params
|
231
|
-
end
|
232
|
-
|
233
|
-
# See #folders_hierarchy method
|
234
|
-
def folders_hierarchy!(params = {})
|
235
|
-
params[:force] = true
|
236
|
-
folders_hierarchy params
|
237
|
-
end
|
238
|
-
|
239
|
-
alias :reload! :folders_hierarchy!
|
240
|
-
|
241
|
-
# Build folders hierarchy in the following format:
|
242
|
-
# {
|
243
|
-
# <folder ID> => {
|
244
|
-
# :parent => <parent folder ID>,
|
245
|
-
# :name => <folder name>,
|
246
|
-
# :path => <folder absolute path>
|
247
|
-
# },
|
248
|
-
# ...
|
249
|
-
# }
|
250
|
-
#
|
251
|
-
# @param [Hash] params
|
252
|
-
# <tt>:force</tt>::
|
253
|
-
# Invalidate cached tree, default: false
|
254
|
-
# After each call of this method the generated tree will be saved as cache
|
255
|
-
# to avoid unnecessary queries to be performed fpr a future calls
|
256
|
-
# <tt>:validate</tt>::
|
257
|
-
# Validate tree after it has been generated, default: true
|
258
|
-
# <tt>:consistent</tt>::
|
259
|
-
# Delete all found orphans, default: false
|
260
|
-
# Ignored if :validate is set to false
|
261
|
-
def folders_hierarchy(params = {})
|
262
|
-
force_load = params.delete :force
|
263
|
-
from_folder_path = path_trim(params.delete(:from) || '/')
|
264
|
-
remove_orphans = params.delete(:consistent)
|
265
|
-
perform_validation = params.delete(:validate)
|
266
|
-
perform_validation = true if perform_validation.nil?
|
267
|
-
remove_orphans = false unless perform_validation
|
268
|
-
|
269
|
-
if @tree && !force_load
|
270
|
-
if from_folder_path.empty?
|
271
|
-
return @tree
|
272
|
-
else
|
273
|
-
return slice_tree @tree, :from => from_folder_path
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
return @tree if @tree && !force_load # TODO: about slices here (:from parameter)
|
278
|
-
@tree = {}
|
279
|
-
|
280
|
-
from_folder_id = folder_id from_folder_path
|
281
|
-
raise Exception, "Folder #{from_folder_path} could not be found" if from_folder_id.nil?
|
282
|
-
|
283
|
-
response = listrealfolders
|
284
|
-
|
285
|
-
if 'NONE' == response
|
286
|
-
@tree = {}
|
287
|
-
else
|
288
|
-
intermediate = response.split(' ').map do |str|
|
289
|
-
params = str.split ','
|
290
|
-
[params[0].to_i, {:parent => params[1].to_i, :name => params[2]}]
|
291
|
-
end
|
292
|
-
|
293
|
-
@tree = Hash[intermediate]
|
294
|
-
end
|
295
|
-
|
296
|
-
# Kill orphans
|
297
|
-
remove_orphans! if remove_orphans
|
298
|
-
|
299
|
-
@tree.each_pair do |folder_id, data|
|
300
|
-
@tree[folder_id][:path] = folder_path folder_id
|
301
|
-
end
|
302
|
-
|
303
|
-
if perform_validation
|
304
|
-
# Validate folder tree consistency
|
305
|
-
@tree.each_pair do |folder_id, data|
|
306
|
-
parent_id = data[:parent]
|
307
|
-
if !parent_id.zero? && @tree[parent_id].nil?
|
308
|
-
error = "Directory tree consistency error. Parent folder ##{data[:parent]} for the folder \"#{data[:path]}\" [#{folder_id}] could not be found"
|
309
|
-
raise error
|
310
|
-
end
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
@tree = slice_tree @tree, :from => from_folder_path unless from_folder_path.empty?
|
315
|
-
@tree
|
316
|
-
end
|
317
|
-
|
318
|
-
# Build tree relative to a specified folder
|
319
|
-
# If the source tree is:
|
320
|
-
# tree = {
|
321
|
-
# 1 => {:parent => 0, :name =>
|
322
|
-
# 2 => {:parent => 1, :name =>
|
323
|
-
# 3 => {:parent => 2, :name =>
|
324
|
-
# ...
|
325
|
-
# }
|
326
|
-
# slice_tree tree, :from =>
|
327
|
-
# Result will be as follows:
|
328
|
-
# {
|
329
|
-
# 2 => {:parent => 1, :name =>
|
330
|
-
# 3 => {:parent => 2, :name =>
|
331
|
-
# ...
|
332
|
-
# }
|
333
|
-
def slice_tree(tree, params = {})
|
334
|
-
from_folder_path = path_trim(params.delete(:from) || '/')
|
335
|
-
|
336
|
-
result_tree = tree.dup
|
337
|
-
|
338
|
-
unless from_folder_path == ''
|
339
|
-
|
340
|
-
result_tree.keep_if do |folder_id, data|
|
341
|
-
path_trim(data[:path]).start_with? "#{from_folder_path}/"
|
342
|
-
end
|
343
|
-
|
344
|
-
result_tree.each_pair do |folder_id, data|
|
345
|
-
path = result_tree[folder_id][:path]
|
346
|
-
result_tree[folder_id][:path] = path_canonize path_trim(path.gsub /#{from_folder_path.gsub /\//, '\/'}\//, '')
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
result_tree
|
351
|
-
end
|
352
|
-
|
353
|
-
# Fix inconsistent folder tree (Yes, getting a broken folder hierarchy is possible with a stupid Rapidshare API)
|
354
|
-
# by deleting orphan folders (folders with no parent folder), this folders are invisible in Rapidshare File Manager
|
355
|
-
# So, this method deletes orphan folders
|
356
|
-
def remove_orphans!
|
357
|
-
@tree = folders_hierarchy :validate => false
|
358
|
-
@tree.each_pair do |folder_id, data|
|
359
|
-
@tree.delete_if do |folder_id, data|
|
360
|
-
if orphan? folder_id
|
361
|
-
delrealfolder :realfolder => folder_id
|
362
|
-
true
|
363
|
-
end
|
364
|
-
end
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
# Places all existing orphan folders under the specific folder
|
369
|
-
# Orphan folder is a folder with non existing parent (yes, it's possible)
|
370
|
-
#
|
371
|
-
# Example:
|
372
|
-
# move_orphans :to =>
|
373
|
-
def move_orphans(params = {})
|
374
|
-
new_folder = path_trim(params.delete(:to) || '/')
|
375
|
-
gaps = detect_gaps
|
376
|
-
|
377
|
-
if gaps.any?
|
378
|
-
params = {
|
379
|
-
:realfolder => gaps.join(','),
|
380
|
-
:newparent => new_folder
|
381
|
-
}.merge params
|
382
|
-
moverealfolder params
|
383
|
-
end
|
384
|
-
end
|
385
|
-
|
386
|
-
# Returns gap list between folders
|
387
|
-
# See #gap? for example
|
388
|
-
def detect_gaps
|
389
|
-
@tree = folders_hierarchy :validate => false
|
390
|
-
@tree.dup.keep_if do |folder_id, data|
|
391
|
-
gap? folder_id # This is wrong
|
392
|
-
end.keys
|
393
|
-
end
|
394
|
-
|
395
|
-
# The name speaks for itself
|
396
|
-
# WARNING!!! All data will be lost!!!
|
397
|
-
# Use it carefully
|
398
|
-
def erase_all_data!
|
399
|
-
@tree = folders_hierarchy! :validate => false
|
400
|
-
@tree.keys.each do |folder_id|
|
401
|
-
delrealfolder :realfolder => folder_id
|
402
|
-
end
|
403
|
-
folders_hierarchy!
|
404
|
-
end
|
405
|
-
|
406
|
-
# Check if folder with given id placed on the bottom of folder hierarchy
|
407
|
-
def root_folder?(folder_id)
|
408
|
-
@tree = folders_hierarchy :validate => false
|
409
|
-
return false if @tree[folder_id].nil?
|
410
|
-
@tree[folder_id][:parent].zero?
|
411
|
-
end
|
412
|
-
|
413
|
-
# Check if the given folder has no parent
|
414
|
-
def gap?(folder_id)
|
415
|
-
@tree = folders_hierarchy :validate => false
|
416
|
-
parent_id = @tree[folder_id][:parent]
|
417
|
-
@tree[parent_id].nil?
|
418
|
-
end
|
419
|
-
|
420
|
-
# Check if folder has any gaps in it hierarchy
|
421
|
-
# For example we have the following hierarchy:
|
422
|
-
#
|
423
|
-
# ROOT
|
424
|
-
# `-a <- if we remove just this folder then the folder
|
425
|
-
# `-b
|
426
|
-
# `-c
|
427
|
-
def orphan?(folder_id)
|
428
|
-
@tree = folders_hierarchy :validate => false
|
429
|
-
return false if @tree[folder_id].nil?
|
430
|
-
parent_id = @tree[folder_id][:parent]
|
431
|
-
return false if root_folder? folder_id
|
432
|
-
return true if gap? folder_id
|
433
|
-
orphan?(parent_id)
|
434
|
-
end
|
435
|
-
|
436
|
-
# Translate folder ID to a human readable path
|
437
|
-
#
|
438
|
-
# api.folder_path(123) # ->
|
439
|
-
def folder_path(folder_id)
|
440
|
-
@tree = folders_hierarchy
|
441
|
-
|
442
|
-
folder_data = @tree[folder_id] || {:parent => 0, :name =>
|
443
|
-
|
444
|
-
parent_id = folder_data[:parent]
|
445
|
-
path = (folder_path(parent_id) if parent_id.nonzero?).to_s + ('/' if parent_id.nonzero?).to_s + folder_data[:name]
|
446
|
-
parent_id.zero? ? "/#{path}" : path
|
447
|
-
end
|
448
|
-
|
449
|
-
# Get folder ID by path
|
450
|
-
#
|
451
|
-
# api.folder_id(
|
452
|
-
def folder_id(folder_path)
|
453
|
-
folder_path = path_trim(folder_path)
|
454
|
-
return 0 if folder_path.empty?
|
455
|
-
|
456
|
-
@tree = folders_hierarchy
|
457
|
-
index = @tree.find_index do |folder_id, data|
|
458
|
-
path_trim(data[:path]) == path_trim(folder_path)
|
459
|
-
end
|
460
|
-
@tree.keys[index] unless index.nil?
|
461
|
-
end
|
462
|
-
|
463
|
-
# Get file info in the following format:
|
464
|
-
#
|
465
|
-
# {
|
466
|
-
# :downloads,
|
467
|
-
# :lastdownload,
|
468
|
-
# :filename,
|
469
|
-
# :size,
|
470
|
-
# :serverid,
|
471
|
-
# :type,
|
472
|
-
# :x,
|
473
|
-
# :y,
|
474
|
-
# :realfolder,
|
475
|
-
# :killdeadline,
|
476
|
-
# :uploadtime,
|
477
|
-
# :comment,
|
478
|
-
# :md5hex,
|
479
|
-
# :licids,
|
480
|
-
# :sentby
|
481
|
-
# }
|
482
|
-
# See the http://images.rapidshare.com/apidoc.txt for more details
|
483
|
-
def file_info(file_path, params = {})
|
484
|
-
folder_path = File.dirname file_path
|
485
|
-
file_name = File.basename file_path
|
486
|
-
|
487
|
-
folder_id = folder_id folder_path
|
488
|
-
|
489
|
-
listfiles_params = {
|
490
|
-
:realfolder => folder_id,
|
491
|
-
:filename => "#{file_name}",
|
492
|
-
:fields => FILE_COLUMNS,
|
493
|
-
:parser => :csv
|
494
|
-
}.merge params
|
495
|
-
|
496
|
-
resp = listfiles(listfiles_params)[0]
|
497
|
-
return nil if
|
498
|
-
|
499
|
-
response = {}
|
500
|
-
|
501
|
-
fields = listfiles_params[:fields].split(',')
|
502
|
-
fields.unshift
|
503
|
-
fields.each_with_index do |value, index|
|
504
|
-
response[value.to_sym] = resp[index]
|
505
|
-
end
|
506
|
-
|
507
|
-
response[:url] = "https://rapidshare.com/files/#{response[:id]}/#{URI::encode response[:filename]}" if response[:filename]
|
508
|
-
|
509
|
-
response
|
510
|
-
end
|
511
|
-
|
512
|
-
# Returns file ID by absolute path
|
513
|
-
#
|
514
|
-
# api.file_id(
|
515
|
-
def file_id(file_path, params = {})
|
516
|
-
params[:fields] =
|
517
|
-
file_info = file_info file_path, params
|
518
|
-
(file_info || {})[:id].to_i
|
519
|
-
end
|
520
|
-
|
521
|
-
protected
|
522
|
-
|
523
|
-
def path_trim(path)
|
524
|
-
path.gsub(/\A\/+/, '').gsub(/\/+\Z/, '')
|
525
|
-
end
|
526
|
-
|
527
|
-
def path_canonize(path)
|
528
|
-
|
529
|
-
end
|
530
|
-
end
|
531
|
-
end
|
1
|
+
module Rapidshare
|
2
|
+
module Ext
|
3
|
+
module API
|
4
|
+
|
5
|
+
FILE_COLUMNS = 'downloads,lastdownload,filename,size,serverid,type,x,y,realfolder,killdeadline,uploadtime,comment,md5hex,licids,sentby'
|
6
|
+
|
7
|
+
# @param [String] path Folder name with absolute path to be created
|
8
|
+
# @param [Hash] params
|
9
|
+
# @return [Integer]
|
10
|
+
#
|
11
|
+
# Creates a folder in a Rapidshare virtual filesystem
|
12
|
+
#
|
13
|
+
# api.add_folder('/a/b/c') #=> <Random folder ID from Rapidshare>, 1234 for example
|
14
|
+
def add_folder(path, params = {})
|
15
|
+
path = path_trim path
|
16
|
+
|
17
|
+
@tree = folders_hierarchy
|
18
|
+
i = 1
|
19
|
+
parent = 0
|
20
|
+
folder_id = nil
|
21
|
+
while i <= path.split('/').count do
|
22
|
+
base_path = path.split('/')[0,i].join('/')
|
23
|
+
folder_id = self.folder_id base_path
|
24
|
+
if folder_id
|
25
|
+
parent = folder_id
|
26
|
+
i += 1
|
27
|
+
else
|
28
|
+
# Create folder
|
29
|
+
folder_name = path.split('/')[i-1]
|
30
|
+
add_folder_params = {
|
31
|
+
:name => folder_name,
|
32
|
+
:parent => parent
|
33
|
+
}.merge params
|
34
|
+
|
35
|
+
# The following code deals with #{} because of rest client #to_i returns HTTP code
|
36
|
+
folder_id = "#{addrealfolder(add_folder_params)}".to_i
|
37
|
+
raise 'error while creating folder' if parent < 0
|
38
|
+
@tree[folder_id] = {
|
39
|
+
:parent => parent,
|
40
|
+
:name => folder_name,
|
41
|
+
:path => path_canonize((@tree[parent] || {})[:path].to_s + ('/' if @tree[parent]).to_s + folder_name)
|
42
|
+
}
|
43
|
+
parent = folder_id
|
44
|
+
path == base_path + '/' + folder_name
|
45
|
+
i += 1
|
46
|
+
next
|
47
|
+
end
|
48
|
+
end
|
49
|
+
folder_id
|
50
|
+
end
|
51
|
+
|
52
|
+
# Removes a specified folder
|
53
|
+
#
|
54
|
+
# @param [String] path
|
55
|
+
# @param [Hash] params
|
56
|
+
# @return [Array]
|
57
|
+
#
|
58
|
+
# api.remove_folder('/a/b/c')
|
59
|
+
def remove_folder(path, params = {})
|
60
|
+
folder_id = self.folder_id path_trim(path)
|
61
|
+
raise Exception, "Folder #{path} could not be found" if folder_id.nil?
|
62
|
+
|
63
|
+
# TODO
|
64
|
+
tree = folders_hierarchy :from => path
|
65
|
+
tree.each_pair do |child_folder_id, data|
|
66
|
+
delrealfolder_params = {
|
67
|
+
:realfolder => child_folder_id
|
68
|
+
}.merge params
|
69
|
+
|
70
|
+
delrealfolder delrealfolder_params
|
71
|
+
@tree.delete folder_id
|
72
|
+
end
|
73
|
+
|
74
|
+
params = {
|
75
|
+
:realfolder => folder_id
|
76
|
+
}.merge params
|
77
|
+
|
78
|
+
delrealfolder params
|
79
|
+
|
80
|
+
@tree.delete folder_id
|
81
|
+
end
|
82
|
+
|
83
|
+
# Moves folder into a specified one
|
84
|
+
#
|
85
|
+
# @param [String] source_path
|
86
|
+
# @param [Hash] params
|
87
|
+
# :to => <destination folder path>, default: '/'
|
88
|
+
#
|
89
|
+
# api.move_folder('/a/b/c', :to => '/a')
|
90
|
+
def move_folder(source_path, params = {})
|
91
|
+
dest_path = (params.delete(:to) || '/')
|
92
|
+
source_folder_id = folder_id(source_path)
|
93
|
+
dest_folder_id = folder_id(dest_path)
|
94
|
+
|
95
|
+
params = {
|
96
|
+
:realfolder => source_folder_id,
|
97
|
+
:newparent => dest_folder_id
|
98
|
+
}.merge params
|
99
|
+
|
100
|
+
moverealfolder params
|
101
|
+
|
102
|
+
@tree = folders_hierarchy
|
103
|
+
@tree[source_folder_id][:parent] = dest_folder_id
|
104
|
+
@tree[source_folder_id][:path] = path_canonize "#{folder_path(dest_folder_id)}/#{@tree[source_folder_id][:name]}"
|
105
|
+
true
|
106
|
+
end
|
107
|
+
|
108
|
+
# Upload file to a specified folder
|
109
|
+
#
|
110
|
+
# @param [String] file_path
|
111
|
+
# @param [Hash] params
|
112
|
+
# <tt>:to</tt>::
|
113
|
+
# Folder to place uploaded file to, default: '/'
|
114
|
+
# <tt>:as</tt>::
|
115
|
+
# The name file will have in storage after it has been uploaded
|
116
|
+
# <tt>:overwrite</tt>::
|
117
|
+
# Overwrite file if it already exists in the given folder
|
118
|
+
#
|
119
|
+
# api.upload('/home/odiszapc/my_damn_cat.mov', :to => '/gallery/video', :as => 'cat1.mov')
|
120
|
+
def upload(file_path, params = {})
|
121
|
+
raise Exception unless File.exist? file_path
|
122
|
+
dest_path = path_trim(params.delete(:to) || '/')
|
123
|
+
folder_id = self.add_folder dest_path
|
124
|
+
file_name = params.delete(:as) || File.basename(file_path)
|
125
|
+
overwrite = params.delete :overwrite
|
126
|
+
|
127
|
+
# Check file already exists within a folder
|
128
|
+
listfiles_params = {
|
129
|
+
:realfolder => folder_id,
|
130
|
+
:filename => "#{file_name}",
|
131
|
+
:fields => 'md5hex,size',
|
132
|
+
:parser => :csv
|
133
|
+
}
|
134
|
+
listfiles_response = self.listfiles listfiles_params
|
135
|
+
|
136
|
+
file_already_exists = ('NONE' != listfiles_response[0][0])
|
137
|
+
remove_file "#{dest_path}/#{file_name}" if file_already_exists && overwrite
|
138
|
+
|
139
|
+
# In case of file is not existing then upload it
|
140
|
+
if !file_already_exists || overwrite
|
141
|
+
upload_server = "rs#{self.nextuploadserver}.rapidshare.com"
|
142
|
+
|
143
|
+
upload_params = {
|
144
|
+
:server => upload_server,
|
145
|
+
:folder => folder_id,
|
146
|
+
:filename => file_name,
|
147
|
+
:filecontent => file_path,
|
148
|
+
:method => :post,
|
149
|
+
:parser => :csv
|
150
|
+
}.merge params
|
151
|
+
|
152
|
+
resp = request(:upload, upload_params)
|
153
|
+
raise Exception, "File uploading failed: #{resp.inspect}" unless "COMPLETE" == resp[0][0]
|
154
|
+
|
155
|
+
id = resp[1][0].to_i
|
156
|
+
md5_hash = resp[1][3]
|
157
|
+
size = resp[1][2].to_i
|
158
|
+
already_exists = false
|
159
|
+
else
|
160
|
+
id = listfiles_response[0][0].to_i
|
161
|
+
md5_hash = listfiles_response[0][1]
|
162
|
+
size = listfiles_response[0][2].to_i
|
163
|
+
already_exists = true
|
164
|
+
end
|
165
|
+
|
166
|
+
raise Exception, "Invalid File ID: #{resp.inspect}" unless id
|
167
|
+
raise Exception, "Invalid MD5 hash: #{resp.inspect}" unless md5_hash
|
168
|
+
raise Exception, "Invalid File Size: #{resp.inspect}" unless size
|
169
|
+
|
170
|
+
{
|
171
|
+
:id => id,
|
172
|
+
:size => size,
|
173
|
+
:checksum => md5_hash.downcase,
|
174
|
+
:url => "https://rapidshare.com/files/#{id}/#{URI::encode file_name}",
|
175
|
+
:already_exists? => already_exists
|
176
|
+
}
|
177
|
+
end
|
178
|
+
|
179
|
+
# Delete file
|
180
|
+
#
|
181
|
+
# @param [String] path
|
182
|
+
# @param [Hash] params
|
183
|
+
#
|
184
|
+
# api.remove_file('/putin/is/a/good/reason/to/live/abroad/ticket_to_Nikaragua.jpg')
|
185
|
+
def remove_file(path, params = {})
|
186
|
+
params = {
|
187
|
+
:files => file_id(path).to_s
|
188
|
+
}.merge params
|
189
|
+
|
190
|
+
deletefiles params
|
191
|
+
end
|
192
|
+
|
193
|
+
# Rename file
|
194
|
+
#
|
195
|
+
# @param [String] remote_path
|
196
|
+
# @param [String] name
|
197
|
+
# @param [Hash] params
|
198
|
+
#
|
199
|
+
# api.rename_file('/foo/bar.rar', 'baz.rar')
|
200
|
+
def rename_file(remote_path, name, params = {})
|
201
|
+
file_id = file_id remote_path
|
202
|
+
|
203
|
+
params = {
|
204
|
+
:fileid => file_id,
|
205
|
+
:newname => name
|
206
|
+
}.merge params
|
207
|
+
|
208
|
+
renamefile params
|
209
|
+
# TODO: duplicates check
|
210
|
+
end
|
211
|
+
|
212
|
+
# Moves file to a specified folder
|
213
|
+
#
|
214
|
+
# @param [String] remote_path
|
215
|
+
# @param [Hash] params
|
216
|
+
# <tt>:to</tt>::
|
217
|
+
# Destination folder path, default: '/'
|
218
|
+
#
|
219
|
+
# api.move_file('/foo/bar/baz.rar', :to => '/foo')
|
220
|
+
# api.move_file('/foo/bar/baz.rar') # move to a root folder
|
221
|
+
def move_file(remote_path, params = {})
|
222
|
+
file_id = file_id remote_path
|
223
|
+
dest_path = path_trim(params.delete(:to) || '/')
|
224
|
+
|
225
|
+
params = {
|
226
|
+
:files => file_id,
|
227
|
+
:realfolder => folder_id(dest_path)
|
228
|
+
}.merge params
|
229
|
+
|
230
|
+
movefilestorealfolder params
|
231
|
+
end
|
232
|
+
|
233
|
+
# See #folders_hierarchy method
|
234
|
+
def folders_hierarchy!(params = {})
|
235
|
+
params[:force] = true
|
236
|
+
folders_hierarchy params
|
237
|
+
end
|
238
|
+
|
239
|
+
alias :reload! :folders_hierarchy!
|
240
|
+
|
241
|
+
# Build folders hierarchy in the following format:
|
242
|
+
# {
|
243
|
+
# <folder ID> => {
|
244
|
+
# :parent => <parent folder ID>,
|
245
|
+
# :name => <folder name>,
|
246
|
+
# :path => <folder absolute path>
|
247
|
+
# },
|
248
|
+
# ...
|
249
|
+
# }
|
250
|
+
#
|
251
|
+
# @param [Hash] params
|
252
|
+
# <tt>:force</tt>::
|
253
|
+
# Invalidate cached tree, default: false
|
254
|
+
# After each call of this method the generated tree will be saved as cache
|
255
|
+
# to avoid unnecessary queries to be performed fpr a future calls
|
256
|
+
# <tt>:validate</tt>::
|
257
|
+
# Validate tree after it has been generated, default: true
|
258
|
+
# <tt>:consistent</tt>::
|
259
|
+
# Delete all found orphans, default: false
|
260
|
+
# Ignored if :validate is set to false
|
261
|
+
def folders_hierarchy(params = {})
|
262
|
+
force_load = params.delete :force
|
263
|
+
from_folder_path = path_trim(params.delete(:from) || '/')
|
264
|
+
remove_orphans = params.delete(:consistent)
|
265
|
+
perform_validation = params.delete(:validate)
|
266
|
+
perform_validation = true if perform_validation.nil?
|
267
|
+
remove_orphans = false unless perform_validation
|
268
|
+
|
269
|
+
if @tree && !force_load
|
270
|
+
if from_folder_path.empty?
|
271
|
+
return @tree
|
272
|
+
else
|
273
|
+
return slice_tree @tree, :from => from_folder_path
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
return @tree if @tree && !force_load # TODO: about slices here (:from parameter)
|
278
|
+
@tree = {}
|
279
|
+
|
280
|
+
from_folder_id = folder_id from_folder_path
|
281
|
+
raise Exception, "Folder #{from_folder_path} could not be found" if from_folder_id.nil?
|
282
|
+
|
283
|
+
response = listrealfolders
|
284
|
+
|
285
|
+
if 'NONE' == response
|
286
|
+
@tree = {}
|
287
|
+
else
|
288
|
+
intermediate = response.split(' ').map do |str|
|
289
|
+
params = str.split ','
|
290
|
+
[params[0].to_i, {:parent => params[1].to_i, :name => params[2]}]
|
291
|
+
end
|
292
|
+
|
293
|
+
@tree = Hash[intermediate]
|
294
|
+
end
|
295
|
+
|
296
|
+
# Kill orphans
|
297
|
+
remove_orphans! if remove_orphans
|
298
|
+
|
299
|
+
@tree.each_pair do |folder_id, data|
|
300
|
+
@tree[folder_id][:path] = folder_path folder_id
|
301
|
+
end
|
302
|
+
|
303
|
+
if perform_validation
|
304
|
+
# Validate folder tree consistency
|
305
|
+
@tree.each_pair do |folder_id, data|
|
306
|
+
parent_id = data[:parent]
|
307
|
+
if !parent_id.zero? && @tree[parent_id].nil?
|
308
|
+
error = "Directory tree consistency error. Parent folder ##{data[:parent]} for the folder \"#{data[:path]}\" [#{folder_id}] could not be found"
|
309
|
+
raise error
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
@tree = slice_tree @tree, :from => from_folder_path unless from_folder_path.empty?
|
315
|
+
@tree
|
316
|
+
end
|
317
|
+
|
318
|
+
# Build tree relative to a specified folder
|
319
|
+
# If the source tree is:
|
320
|
+
# tree = {
|
321
|
+
# 1 => {:parent => 0, :name => 'a', :path => 'a'},
|
322
|
+
# 2 => {:parent => 1, :name => 'b', :path => 'a/b'},
|
323
|
+
# 3 => {:parent => 2, :name => 'c', :path => 'a/b/c'},
|
324
|
+
# ...
|
325
|
+
# }
|
326
|
+
# slice_tree tree, :from => '/a'
|
327
|
+
# Result will be as follows:
|
328
|
+
# {
|
329
|
+
# 2 => {:parent => 1, :name => 'b', :path => 'b'},
|
330
|
+
# 3 => {:parent => 2, :name => 'c', :path => 'b/c'},
|
331
|
+
# ...
|
332
|
+
# }
|
333
|
+
def slice_tree(tree, params = {})
|
334
|
+
from_folder_path = path_trim(params.delete(:from) || '/')
|
335
|
+
|
336
|
+
result_tree = tree.dup
|
337
|
+
|
338
|
+
unless from_folder_path == ''
|
339
|
+
|
340
|
+
result_tree.keep_if do |folder_id, data|
|
341
|
+
path_trim(data[:path]).start_with? "#{from_folder_path}/"
|
342
|
+
end
|
343
|
+
|
344
|
+
result_tree.each_pair do |folder_id, data|
|
345
|
+
path = result_tree[folder_id][:path]
|
346
|
+
result_tree[folder_id][:path] = path_canonize path_trim(path.gsub /#{from_folder_path.gsub /\//, '\/'}\//, '')
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
result_tree
|
351
|
+
end
|
352
|
+
|
353
|
+
# Fix inconsistent folder tree (Yes, getting a broken folder hierarchy is possible with a stupid Rapidshare API)
|
354
|
+
# by deleting orphan folders (folders with no parent folder), this folders are invisible in Rapidshare File Manager
|
355
|
+
# So, this method deletes orphan folders
|
356
|
+
def remove_orphans!
|
357
|
+
@tree = folders_hierarchy :validate => false
|
358
|
+
@tree.each_pair do |folder_id, data|
|
359
|
+
@tree.delete_if do |folder_id, data|
|
360
|
+
if orphan? folder_id
|
361
|
+
delrealfolder :realfolder => folder_id
|
362
|
+
true
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# Places all existing orphan folders under the specific folder
|
369
|
+
# Orphan folder is a folder with non existing parent (yes, it's possible)
|
370
|
+
#
|
371
|
+
# Example:
|
372
|
+
# move_orphans :to => '/'
|
373
|
+
def move_orphans(params = {})
|
374
|
+
new_folder = path_trim(params.delete(:to) || '/')
|
375
|
+
gaps = detect_gaps
|
376
|
+
|
377
|
+
if gaps.any?
|
378
|
+
params = {
|
379
|
+
:realfolder => gaps.join(','),
|
380
|
+
:newparent => new_folder
|
381
|
+
}.merge params
|
382
|
+
moverealfolder params
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# Returns gap list between folders
|
387
|
+
# See #gap? for example
|
388
|
+
def detect_gaps
|
389
|
+
@tree = folders_hierarchy :validate => false
|
390
|
+
@tree.dup.keep_if do |folder_id, data|
|
391
|
+
gap? folder_id # This is wrong
|
392
|
+
end.keys
|
393
|
+
end
|
394
|
+
|
395
|
+
# The name speaks for itself
|
396
|
+
# WARNING!!! All data will be lost!!!
|
397
|
+
# Use it carefully
|
398
|
+
def erase_all_data!
|
399
|
+
@tree = folders_hierarchy! :validate => false
|
400
|
+
@tree.keys.each do |folder_id|
|
401
|
+
delrealfolder :realfolder => folder_id
|
402
|
+
end
|
403
|
+
folders_hierarchy!
|
404
|
+
end
|
405
|
+
|
406
|
+
# Check if folder with given id placed on the bottom of folder hierarchy
|
407
|
+
def root_folder?(folder_id)
|
408
|
+
@tree = folders_hierarchy :validate => false
|
409
|
+
return false if @tree[folder_id].nil?
|
410
|
+
@tree[folder_id][:parent].zero?
|
411
|
+
end
|
412
|
+
|
413
|
+
# Check if the given folder has no parent
|
414
|
+
def gap?(folder_id)
|
415
|
+
@tree = folders_hierarchy :validate => false
|
416
|
+
parent_id = @tree[folder_id][:parent]
|
417
|
+
@tree[parent_id].nil?
|
418
|
+
end
|
419
|
+
|
420
|
+
# Check if folder has any gaps in it hierarchy
|
421
|
+
# For example we have the following hierarchy:
|
422
|
+
#
|
423
|
+
# ROOT
|
424
|
+
# `-a <- if we remove just this folder then the folder 'c' and 'b' will become orphans
|
425
|
+
# `-b
|
426
|
+
# `-c
|
427
|
+
def orphan?(folder_id)
|
428
|
+
@tree = folders_hierarchy :validate => false
|
429
|
+
return false if @tree[folder_id].nil?
|
430
|
+
parent_id = @tree[folder_id][:parent]
|
431
|
+
return false if root_folder? folder_id
|
432
|
+
return true if gap? folder_id
|
433
|
+
orphan?(parent_id)
|
434
|
+
end
|
435
|
+
|
436
|
+
# Translate folder ID to a human readable path
|
437
|
+
#
|
438
|
+
# api.folder_path(123) # -> 'foo/bar/baz'
|
439
|
+
def folder_path(folder_id)
|
440
|
+
@tree = folders_hierarchy
|
441
|
+
|
442
|
+
folder_data = @tree[folder_id] || {:parent => 0, :name => '<undefined>', :path => '<undefined>'}
|
443
|
+
|
444
|
+
parent_id = folder_data[:parent]
|
445
|
+
path = (folder_path(parent_id) if parent_id.nonzero?).to_s + ('/' if parent_id.nonzero?).to_s + folder_data[:name]
|
446
|
+
parent_id.zero? ? "/#{path}" : path
|
447
|
+
end
|
448
|
+
|
449
|
+
# Get folder ID by path
|
450
|
+
#
|
451
|
+
# api.folder_id('foo/bar/baz') # -> 123
|
452
|
+
def folder_id(folder_path)
|
453
|
+
folder_path = path_trim(folder_path)
|
454
|
+
return 0 if folder_path.empty?
|
455
|
+
|
456
|
+
@tree = folders_hierarchy
|
457
|
+
index = @tree.find_index do |folder_id, data|
|
458
|
+
path_trim(data[:path]) == path_trim(folder_path)
|
459
|
+
end
|
460
|
+
@tree.keys[index] unless index.nil?
|
461
|
+
end
|
462
|
+
|
463
|
+
# Get file info in the following format:
|
464
|
+
#
|
465
|
+
# {
|
466
|
+
# :downloads,
|
467
|
+
# :lastdownload,
|
468
|
+
# :filename,
|
469
|
+
# :size,
|
470
|
+
# :serverid,
|
471
|
+
# :type,
|
472
|
+
# :x,
|
473
|
+
# :y,
|
474
|
+
# :realfolder,
|
475
|
+
# :killdeadline,
|
476
|
+
# :uploadtime,
|
477
|
+
# :comment,
|
478
|
+
# :md5hex,
|
479
|
+
# :licids,
|
480
|
+
# :sentby
|
481
|
+
# }
|
482
|
+
# See the http://images.rapidshare.com/apidoc.txt for more details
|
483
|
+
def file_info(file_path, params = {})
|
484
|
+
folder_path = File.dirname file_path
|
485
|
+
file_name = File.basename file_path
|
486
|
+
|
487
|
+
folder_id = folder_id folder_path
|
488
|
+
|
489
|
+
listfiles_params = {
|
490
|
+
:realfolder => folder_id,
|
491
|
+
:filename => "#{file_name}",
|
492
|
+
:fields => FILE_COLUMNS,
|
493
|
+
:parser => :csv
|
494
|
+
}.merge params
|
495
|
+
|
496
|
+
resp = listfiles(listfiles_params)[0]
|
497
|
+
return nil if 'NONE' == resp[0]
|
498
|
+
|
499
|
+
response = {}
|
500
|
+
|
501
|
+
fields = listfiles_params[:fields].split(',')
|
502
|
+
fields.unshift 'id'
|
503
|
+
fields.each_with_index do |value, index|
|
504
|
+
response[value.to_sym] = resp[index]
|
505
|
+
end
|
506
|
+
|
507
|
+
response[:url] = "https://rapidshare.com/files/#{response[:id]}/#{URI::encode response[:filename]}" if response[:filename]
|
508
|
+
|
509
|
+
response
|
510
|
+
end
|
511
|
+
|
512
|
+
# Returns file ID by absolute path
|
513
|
+
#
|
514
|
+
# api.file_id('foo/bar/baz/file.rar') # => <FILE_ID>
|
515
|
+
def file_id(file_path, params = {})
|
516
|
+
params[:fields] = ''
|
517
|
+
file_info = file_info file_path, params
|
518
|
+
(file_info || {})[:id].to_i
|
519
|
+
end
|
520
|
+
|
521
|
+
protected
|
522
|
+
|
523
|
+
def path_trim(path)
|
524
|
+
path.gsub(/\A\/+/, '').gsub(/\/+\Z/, '')
|
525
|
+
end
|
526
|
+
|
527
|
+
def path_canonize(path)
|
528
|
+
'/' + path_trim(path)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
532
532
|
end
|