rapidshare-ext 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|