rapidshare-ext 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rapidshare-ext.gemspec
4
+ gemspec
data/History.md ADDED
@@ -0,0 +1,17 @@
1
+ ## ver.0.0.1 2012-11-17
2
+
3
+ It has began. We have the features as follows:
4
+
5
+ * [added] Creating folders
6
+ * [added] Removing folders
7
+ * [added] Moving folders
8
+ * [added] Uploading files
9
+ * [added] Removing files
10
+ * [added] Renaming files
11
+ * [added] Moving files
12
+ * [added] Viewing folders hierarchy
13
+ * [added] Dealing with orphan folders
14
+ * [added] Erasing all account data
15
+ * [added] Get file info
16
+ * [added] Folder/file identification by path
17
+ * [added] Integration tests (Forkers, be careful, account is fully erased after each test !!!)
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 odiszapc
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # Rapidshare::Ext
2
+
3
+ Makes your interactions with Rapidshare API more pleasant by providing new handy features: creating/moving/deleting files/folders in a user friendly way, upload files, etc.
4
+
5
+ This gem extends the existing one - https://github.com/defkode/rapidshare, so it has all features implemented in the source library. In addition, it much simplifies operations with data in your Rapidshare account.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your Gemfile:
10
+
11
+ gem 'rapidshare-ext'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install rapidshare-ext
20
+
21
+ ## Usage
22
+
23
+ First, create an instance:
24
+ ```ruby
25
+ api = Rapidshare::API.new(:login => 'my_login', :password => 'my_password')
26
+ api = Rapidshare::API.new(:cookie => 'cookie_here') # More preferable way
27
+ ```
28
+
29
+ ### Folders
30
+ As you note you can have a hierarchy of folders in your account.
31
+
32
+ Creating folders:
33
+ ```ruby
34
+ folder_id = api.add_folder "a/b/c" # => <FOLDER ID>
35
+ ```
36
+
37
+ Deleting folders:
38
+ ```ruby
39
+ api.remove_folder("/a/b/c")
40
+ ```
41
+
42
+ Moving folders:
43
+ ```ruby
44
+ api.move_folder("/a/b/c", :to => "/a")
45
+ ```
46
+ This moves folder "c" from directory "/a/b/" and places it under the directory "/a"
47
+
48
+ Get hierarchy of all folders in account:
49
+ ```ruby
50
+ api.folders_hierarchy
51
+ # => {
52
+ # <folder ID> => {
53
+ # :parent => <parent folder ID>,
54
+ # :name => <folder name>,
55
+ # :path => <folder absolute path>
56
+ # },
57
+ # ...
58
+ # }
59
+ ```
60
+
61
+ Note, that after the folder hierarhy is generated first time the data is cached permanently to improve performance.
62
+
63
+ So, if you want to invalidate the cache just call the above method with trailing "!":
64
+ ```ruby
65
+ api.folders_hierarchy!
66
+ ```
67
+
68
+ If folder tree is inconsistent (orphans are found) the Exception will be thrown. To automatically normalize the tree, call the method with :consistent flag:
69
+ ```ruby
70
+ api.folders_hierarchy :consistent => true
71
+ ```
72
+ Be careful with a tree consistency, orphan folders may contain a critical data.
73
+
74
+ A more secure way to deal with consistency is to fix orphans first and then generate folders tree:
75
+ ```ruby
76
+ api.add_folder "/garbage"
77
+ api.move_orphans :to => "/garbage" # Collect all orphans and place them under the /garbage folder
78
+ tree = api.folders_hierarchy
79
+ ```
80
+
81
+ ### Orphans
82
+ Ok, the Rapidshare has its common problem: orphan folders. What is this? For example we have the following directory tree:
83
+ ```
84
+ ROOT
85
+ `-a <- RS API allows us to delete JUST THIS folder, so hierarchy relation between folders will be lost and the folders "c" and "b" will become orphans
86
+ `-b
87
+ `-c
88
+ ```
89
+ Orphans is invisible in your File Manager on the Rapidshare web site, so you may want to hide data in that way (stupid idea)
90
+ We can fix it by detecting all orphan fodlers and moving them to a specific fodler:
91
+ ```ruby
92
+ move_orphans :to => "/"
93
+ ```
94
+
95
+ Or we can just kill'em all:
96
+ ```ruby
97
+ remove_orphans!
98
+ ```
99
+
100
+ Get folder ID or path:
101
+ ```ruby
102
+ id = api.folder_id("/foo/bar") # <ID>
103
+ api.folder_path(id) # "/foo/bar"
104
+ ```
105
+
106
+ ### Files
107
+
108
+ File uploading is simple now:
109
+ ```ruby
110
+ api.upload("/home/odiszapc/my_damn_cat.mov", :to => "/gallery/video", :as => "cat1.mov")
111
+ # => {
112
+ # :id => 1,
113
+ # :size => 12345, # File size in bytes
114
+ # :checksum => <MD5>,
115
+ # :url => <DOWNLOAD_URL>, # https://rapidshare/.......
116
+ # :already_exists? => true/false # Does the file already exists within a specific folder, real uploading will not being performed in this case
117
+ #}
118
+ ```
119
+ After uploading has been completed the file will be stored in a Rapidshare as "/gallery/video/cat1.mov"
120
+ To get download url after uploading:
121
+ ```ruby
122
+ result = api.upload("/home/odiszapc/my_damn_cat.mov", :to => "/gallery/video", :as => "cat1.mov")
123
+ result[:url]
124
+ ```
125
+
126
+ By default, file is uploaded to root folder:
127
+ ```ruby
128
+ api.upload("/home/odiszapc/my_damn_humster.mov")
129
+ ```
130
+
131
+ Deleting files:
132
+ ```ruby
133
+ api.remove_file("/putin/is/a/good/reason/to/live/abroad/ticket_to_Nikaragua.jpg")
134
+ ```
135
+
136
+ Renaming files:
137
+ ```ruby
138
+ api.rename_file("/foo/bar.rar", "baz.rar")
139
+ ```
140
+
141
+ Moving files:
142
+ ```ruby
143
+ api.move_file("/foo/bar/baz.rar", :to => "/foo") # new file path: "/foo/baz.rar"
144
+ api.move_file("/foo/bar/baz.rar") # move to a root folder
145
+ ```
146
+
147
+ Get file ID:
148
+ ```ruby
149
+ api.file_id("/foo/bar/baz.rar") # => <ID>
150
+ ```
151
+
152
+ ### Account
153
+ You can null your account by deleting all data involved. Be carefull with it, all data will be lost:
154
+ ```ruby
155
+ api.erase_all_data!
156
+ ```
157
+
158
+ ## Contributing
159
+
160
+ 1. Fork it
161
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
162
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
163
+ 4. Push to the branch (`git push origin my-new-feature`)
164
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+ include Rake::DSL
5
+
6
+ desc "Run tests"
7
+ task :test => ['test:unit', 'test:integration']
8
+
9
+ task :default => :test
10
+
11
+ namespace :test do
12
+ Rake::TestTask.new :unit do |t|
13
+ t.libs << 'test'
14
+ t.pattern = 'test/unit/*_test.rb'
15
+ end
16
+
17
+ Rake::TestTask.new :integration do |t|
18
+ t.libs << 'test'
19
+ t.pattern = 'test/integration/*_test.rb'
20
+ end
21
+ end
22
+
23
+
24
+
@@ -0,0 +1,61 @@
1
+ module Rapidshare
2
+ class API
3
+ # TODO this class is getting long. keep general request-related and helper
4
+ # method here and move specific method calls like :getaccountdetails to other
5
+ # class (service)?
6
+ #
7
+ # TODO enable users to define their own parsers and pass them as code blocks?
8
+ # not really that practical, but it would be a cool piece of code :)
9
+
10
+ # Calls specific RapidShare API service and returns result.
11
+ #
12
+ # Throws exception if error is received from RapidShare API.
13
+ #
14
+ # Params:
15
+ # * *service_name* - name of the RapidShare service, for example +checkfiles+
16
+ # * *params* - hash of service parameters and options (listed below)
17
+ # * *parser* - option, determines how the response body will be parsed:
18
+ # * *none* - default value, returns response body as it is
19
+ # * *csv* - comma-separated values, for example: _getrapidtranslogs_.
20
+ # Returns array or arrays, one array per each line.
21
+ # * *hash* - lines with key and value separated by "=", for example:
22
+ # _getaccountdetails_. Returns hash.
23
+ # * *server* - option, determines which server will be used to send request
24
+ #
25
+ def self.request(service_name, params = {})
26
+ params.symbolize_keys!
27
+
28
+ parser = (params.delete(:parser) || :none).to_sym
29
+ unless [:none, :csv, :hash].include?(parser)
30
+ raise Rapidshare::API::Error.new("Invalid parser for request method: #{parser}")
31
+ end
32
+
33
+ server = params.delete(:server)
34
+ server_url = server ? "https://#{server}/cgi-bin/rsapi.cgi?sub=%s&%s" : URL
35
+
36
+ http_method = (params.delete(:method) || :get).to_sym
37
+ raise Exception, "invalid HTTP method #{http_method}" unless self.respond_to? http_method
38
+
39
+ case http_method
40
+ when :get
41
+ response = self.send(http_method, server_url % [service_name, params.to_query])
42
+ else
43
+ params[:sub] = service_name
44
+ response = self.send(http_method, server_url.gsub(/\?sub=%s&%s$/,''), params)
45
+ end
46
+
47
+ if response.start_with?(ERROR_PREFIX)
48
+ case error = response.sub(ERROR_PREFIX, "").split('.').first
49
+ when "Login failed"
50
+ raise Rapidshare::API::Error::LoginFailed
51
+ when "Invalid routine called"
52
+ raise Rapidshare::API::Error::InvalidRoutineCalled.new(service_name)
53
+ else
54
+ raise Rapidshare::API::Error.new(error)
55
+ end
56
+ end
57
+
58
+ self.parse_response(parser, response)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,12 @@
1
+ module Rapidshare
2
+ module Utils
3
+ def post(url, params)
4
+ params[:filecontent] = File.new(params[:filecontent])
5
+ ::RestClient.post(url, params)
6
+ end
7
+
8
+ def get(url)
9
+ ::RestClient.get(url)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,499 @@
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
+ @tree = folders_hierarchy
16
+ i = 1
17
+ parent = 0
18
+ folder_id = nil
19
+ while i <= path.split('/').count do
20
+ base_path = path.split('/')[0,i].join('/')
21
+ folder_id = self.folder_id base_path
22
+ if folder_id
23
+ parent = folder_id
24
+ i += 1
25
+ else
26
+ # Create folder
27
+ folder_name = path.split('/')[i-1]
28
+ add_folder_params = {
29
+ :name => folder_name,
30
+ :parent => parent
31
+ }.merge params
32
+
33
+ # The following code deals with #{} because of rest client #to_i returns HTTP code
34
+ folder_id = "#{addrealfolder(add_folder_params)}".to_i
35
+ raise "error while creating folder" if parent < 0
36
+ @tree[folder_id] = {
37
+ :parent => parent,
38
+ :name => folder_name,
39
+ :path => (@tree[parent] || {})[:path].to_s + ('/' if @tree[parent]).to_s + folder_name
40
+ }
41
+ parent = folder_id
42
+ path == base_path + '/' + folder_name
43
+ i += 1
44
+ next
45
+ end
46
+ end
47
+ folder_id
48
+ end
49
+
50
+ # Removes a specified folder
51
+ #
52
+ # @param [String] path
53
+ # @param [Hash] params
54
+ # @return [Array]
55
+ #
56
+ # api.remove_folder("/a/b/c")
57
+ def remove_folder(path, params = {})
58
+ folder_id = self.folder_id path_trim(path)
59
+ raise Exception, "Folder #{path} could not be found" if folder_id.nil?
60
+
61
+ # TODO
62
+ tree = folders_hierarchy :from => path
63
+ tree.each_pair do |child_folder_id, data|
64
+ delrealfolder_params = {
65
+ :realfolder => child_folder_id
66
+ }.merge params
67
+
68
+ delrealfolder delrealfolder_params
69
+ @tree.delete folder_id
70
+ end
71
+
72
+ params = {
73
+ :realfolder => folder_id
74
+ }.merge params
75
+
76
+ delrealfolder params
77
+
78
+ @tree.delete folder_id
79
+ end
80
+
81
+ # Moves folder into a specified one
82
+ #
83
+ # @param [String] source_path
84
+ # @param [Hash] params
85
+ # :to => <destination folder path>, default: "/"
86
+ #
87
+ # api.move_folder("/a/b/c", :to => "/a")
88
+ def move_folder(source_path, params = {})
89
+ dest_path = path_trim(params.delete(:to) || '/')
90
+ source_folder_id = folder_id(source_path)
91
+ dest_folder_id = folder_id(dest_path)
92
+
93
+ params = {
94
+ :realfolder => source_folder_id,
95
+ :newparent => dest_folder_id
96
+ }.merge params
97
+
98
+ moverealfolder params
99
+
100
+ @tree = folders_hierarchy
101
+ @tree[source_folder_id][:parent] = dest_folder_id
102
+ @tree[source_folder_id][:path] = "#{folder_path(dest_folder_id)}/#{@tree[source_folder_id][:name]}"
103
+ true
104
+ end
105
+
106
+ # Upload file to a specified folder
107
+ #
108
+ # @param [String] file_path
109
+ # @param [Hash] params
110
+ # <tt>:to</tt>::
111
+ # Folder to place uploaded file to, default: "/"
112
+ # <tt>:as</tt>::
113
+ # The name file will have in storage after it has been uploaded
114
+ #
115
+ # api.upload("/home/odiszapc/my_damn_cat.mov", :to => "/gallery/video", :as => "cat1.mov")
116
+ def upload(file_path, params = {})
117
+ raise Exception unless File.exist? file_path
118
+ dest_path = path_trim(params.delete(:to) || '/')
119
+ folder_id = self.add_folder dest_path
120
+ file_name = params.delete(:as) || File.basename(file_path)
121
+
122
+ # Check file already exists within a folder
123
+ listfiles_params = {
124
+ :realfolder => folder_id,
125
+ :filename => "#{file_name}",
126
+ :fields => "md5hex,size",
127
+ :parser => :csv
128
+ }
129
+ listfiles_response = self.listfiles listfiles_params
130
+
131
+ # In case of file is not existing upload it
132
+ if "NONE" == listfiles_response[0][0]
133
+ upload_server = "rs#{self.nextuploadserver}.rapidshare.com"
134
+
135
+ upload_params = {
136
+ :server => upload_server,
137
+ :folder => folder_id,
138
+ :filename => file_name,
139
+ :filecontent => file_path,
140
+ :method => :post,
141
+ :parser => :csv
142
+ }.merge params
143
+
144
+ resp = request(:upload, upload_params)
145
+ raise Exception, "File uploading failed: #{resp.inspect}" unless "COMPLETE" == resp[0][0]
146
+
147
+ id = resp[1][0].to_i
148
+ md5_hash = resp[1][3]
149
+ size = resp[1][2].to_i
150
+ already_exists = false
151
+ else
152
+ id = listfiles_response[0][0].to_i
153
+ md5_hash = listfiles_response[0][1]
154
+ size = listfiles_response[0][2].to_i
155
+ already_exists = true
156
+ end
157
+
158
+ raise Exception, "Invalid File ID: #{resp.inspect}" unless id
159
+ raise Exception, "Invalid MD5 hash: #{resp.inspect}" unless md5_hash
160
+ raise Exception, "Invalid File Size: #{resp.inspect}" unless size
161
+
162
+ {
163
+ :id => id,
164
+ :size => size,
165
+ :checksum => md5_hash.downcase,
166
+ :url => "https://rapidshare.com/files/#{id}/#{URI::encode file_name}",
167
+ :already_exists? => already_exists
168
+ }
169
+ end
170
+
171
+ # Delete file
172
+ #
173
+ # @param [String] path
174
+ # @param [Hash] params
175
+ #
176
+ # api.remove_file("/putin/is/a/good/reason/to/live/abroad/ticket_to_Nikaragua.jpg")
177
+ def remove_file(path, params = {})
178
+ params = {
179
+ :files => file_id(path).to_s
180
+ }.merge params
181
+
182
+ deletefiles params
183
+ end
184
+
185
+ # Rename file
186
+ #
187
+ # @param [String] remote_path
188
+ # @param [String] name
189
+ # @param [Hash] params
190
+ #
191
+ # api.rename_file("/foo/bar.rar", "baz.rar")
192
+ def rename_file(remote_path, name, params = {})
193
+ file_id = file_id remote_path
194
+
195
+ params = {
196
+ :fileid => file_id,
197
+ :newname => name
198
+ }.merge params
199
+
200
+ renamefile params
201
+ # TODO: duplicates check
202
+ end
203
+
204
+ # Moves file to a specified folder
205
+ #
206
+ # @param [String] remote_path
207
+ # @param [Hash] params
208
+ # <tt>:to</tt>::
209
+ # Destination folder path, default: "/"
210
+ #
211
+ # api.move_file("/foo/bar/baz.rar", :to => "/foo")
212
+ # api.move_file("/foo/bar/baz.rar") # move to a root folder
213
+ def move_file(remote_path, params = {})
214
+ file_id = file_id remote_path
215
+ dest_path = path_trim(params.delete(:to) || '/')
216
+
217
+ params = {
218
+ :files => file_id,
219
+ :realfolder => folder_id(dest_path)
220
+ }.merge params
221
+
222
+ movefilestorealfolder params
223
+ end
224
+
225
+ # See #folders_hierarchy method
226
+ def folders_hierarchy!(params = {})
227
+ params[:force] = true
228
+ folders_hierarchy params
229
+ end
230
+
231
+ alias :reload! :folders_hierarchy!
232
+
233
+ # Build folders hierarchy in the following format:
234
+ # {
235
+ # <folder ID> => {
236
+ # :parent => <parent folder ID>,
237
+ # :name => <folder name>,
238
+ # :path => <folder absolute path>
239
+ # },
240
+ # ...
241
+ # }
242
+ #
243
+ # @param [Hash] params
244
+ # <tt>:force</tt>::
245
+ # Invalidate cached tree, default: false
246
+ # After each call of this method the generated tree will be saved as cache
247
+ # to avoid unnecessary queries to be performed fpr a future calls
248
+ # <tt>:consistent</tt>::
249
+ # Delete all found orphans, default: false
250
+ def folders_hierarchy(params = {})
251
+ force_load = params.delete :force
252
+ from_folder_path = path_trim(params.delete(:from) || '/')
253
+ remove_orphans = params.delete(:consistent)
254
+
255
+ if @tree && !force_load
256
+ if from_folder_path.empty?
257
+ return @tree
258
+ else
259
+ return slice_tree @tree, :from => from_folder_path
260
+ end
261
+ end
262
+
263
+ return @tree if @tree && !force_load # TODO: about slices here (:from parameter)
264
+
265
+ from_folder_id = folder_id from_folder_path
266
+ raise Exception, "Folder #{from_folder_path} could not be found" if from_folder_id.nil?
267
+
268
+ response = listrealfolders
269
+
270
+ if 'NONE' == response
271
+ @tree = {}
272
+ else
273
+ intermediate = response.split(' ').map do |str|
274
+ params = str.split ','
275
+ [params[0].to_i, {:parent => params[1].to_i, :name => params[2]}]
276
+ end
277
+
278
+ @tree = Hash[intermediate]
279
+ end
280
+
281
+ # Kill orphans
282
+ remove_orphans! if remove_orphans
283
+
284
+ # Validate folder tree consistency
285
+ @tree.each_pair do |folder_id, data|
286
+ parent_id = data[:parent]
287
+ if !parent_id.zero? && @tree[parent_id].nil?
288
+
289
+ error = "There is no parent folder with id ##{data[:parent]} for the folder \"#{data[:name]}\" [#{folder_id}]"
290
+ raise error
291
+ end
292
+ end
293
+
294
+ @tree.each_pair do |folder_id, data|
295
+ @tree[folder_id][:path] = folder_path folder_id
296
+ end
297
+
298
+ @tree = slice_tree @tree, :from => from_folder_path unless from_folder_path.empty?
299
+ @tree
300
+ end
301
+
302
+ # Build tree relative to a specified folder
303
+ # If the source tree is:
304
+ # tree = {
305
+ # 1 => {:parent => 0, :name => "a", :path => "a"},
306
+ # 2 => {:parent => 1, :name => "b", :path => "a/b"},
307
+ # 3 => {:parent => 2, :name => "c", :path => "a/b/c"},
308
+ # ...
309
+ # }
310
+ # slice_tree tree, :from => "/a"
311
+ # Result will be as follows:
312
+ # {
313
+ # 2 => {:parent => 1, :name => "b", :path => "b"},
314
+ # 3 => {:parent => 2, :name => "c", :path => "b/c"},
315
+ # ...
316
+ # }
317
+ def slice_tree(tree, params = {})
318
+ from_folder_path = path_trim(params.delete(:from) || '/')
319
+
320
+ result_tree = tree.dup
321
+
322
+ unless from_folder_path == ''
323
+
324
+ result_tree.keep_if do |folder_id, data|
325
+ data[:path].start_with? "#{from_folder_path}/"
326
+ end
327
+
328
+ result_tree.each_pair do |folder_id, data|
329
+ path = result_tree[folder_id][:path]
330
+ result_tree[folder_id][:path] = path.gsub /#{from_folder_path.gsub /\//, '\/'}\//, ''
331
+ end
332
+ end
333
+
334
+ result_tree
335
+ end
336
+
337
+ # Fix inconsistent folder tree (Yes, getting a broken folder hierarchy is possible with a stupid Rapidshare API)
338
+ # by deleting orphan folders (folders with no parent folder), this folders are invisible in Rapidshare File Manager
339
+ # So, this method deletes orphan folders
340
+ def remove_orphans!
341
+ @tree = folders_hierarchy
342
+ @tree.each_pair do |folder_id, data|
343
+ @tree.delete_if do |folder_id, data|
344
+ if orphan? folder_id
345
+ delrealfolder :realfolder => folder_id
346
+ true
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ # Places all existing orphan folders under the specific folder
353
+ # Orphan folder is a folder with non existing parent (yes, it's possible)
354
+ #
355
+ # Example:
356
+ # move_orphans :to => "/"
357
+ def move_orphans(params = {})
358
+ new_folder = folder_id params[:to].to_s
359
+ orphans = detect_gaps.join(',')
360
+ if orphans.any?
361
+ moverealfolder :realfolder => orphans.join(','), :newparent => new_folder
362
+ end
363
+ end
364
+
365
+ # Returns gap list between folders
366
+ # See #gap? for example
367
+ def detect_gaps
368
+ @tree = folders_hierarchy
369
+ @tree.keep_if do |folder_id, data|
370
+ gap? folder_id # This is wrong
371
+ end.keys
372
+ end
373
+
374
+ # The name speaks for itself
375
+ # WARNING!!! All data will be lost!!!
376
+ # Use it carefully
377
+ def erase_all_data!
378
+ @tree = folders_hierarchy
379
+ @tree.keys.each do |folder_id|
380
+ delrealfolder :realfolder => folder_id
381
+ end
382
+ folders_hierarchy!
383
+ end
384
+
385
+ # Check if folder with given id placed on the bottom of folder hierarchy
386
+ def root_folder?(folder_id)
387
+ @tree = folders_hierarchy
388
+ @tree[folder_id][:parent].zero?
389
+ end
390
+
391
+ # Check if the given folder has no parent
392
+ def gap?(folder_id)
393
+ @tree = folders_hierarchy
394
+ parent_id = @tree[folder_id][:parent]
395
+ @tree[parent_id].nil?
396
+ end
397
+
398
+ # Check if folder has any gaps in it hierarchy
399
+ # For example we have the following hierarchy:
400
+ #
401
+ # ROOT
402
+ # `-a <- if we remove just this folder then the folder "c" and "b" will become orphans
403
+ # `-b
404
+ # `-c
405
+ def orphan?(folder_id)
406
+ @tree = folders_hierarchy
407
+ parent_id = @tree[folder_id][:parent]
408
+ return false if root_folder? folder_id
409
+ return true if gap? folder_id
410
+ orphan?(parent_id)
411
+ end
412
+
413
+ # Translate folder ID to a human readable path
414
+ #
415
+ # api.folder_path(123) # -> "foo/bar/baz"
416
+ def folder_path(folder_id)
417
+ @tree = folders_hierarchy
418
+ parent_id = @tree[folder_id][:parent]
419
+ (folder_path(parent_id) if parent_id.nonzero?).to_s + ('/' if parent_id.nonzero?).to_s + @tree[folder_id][:name]
420
+ end
421
+
422
+ # Get folder ID by path
423
+ #
424
+ # api.folder_id("foo/bar/baz") # -> 123
425
+ def folder_id(folder_path)
426
+ folder_path = path_trim(folder_path)
427
+ return 0 if folder_path.empty?
428
+
429
+ @tree = folders_hierarchy
430
+ index = @tree.find_index do |folder_id, data|
431
+ data[:path] == folder_path
432
+ end
433
+ @tree.keys[index] unless index.nil?
434
+ end
435
+
436
+ # Get file info in the following format:
437
+ #
438
+ # {
439
+ # :downloads,
440
+ # :lastdownload,
441
+ # :filename,
442
+ # :size,
443
+ # :serverid,
444
+ # :type,
445
+ # :x,
446
+ # :y,
447
+ # :realfolder,
448
+ # :killdeadline,
449
+ # :uploadtime,
450
+ # :comment,
451
+ # :md5hex,
452
+ # :licids,
453
+ # :sentby
454
+ # }
455
+ # See http://images.rapidshare.com/apidoc.txt for more details
456
+ def file_info(file_path, params = {})
457
+ folder_path = File.dirname file_path
458
+ file_name = File.basename file_path
459
+
460
+ folder_id = folder_id folder_path
461
+
462
+ listfiles_params = {
463
+ :realfolder => folder_id,
464
+ :filename => "#{file_name}",
465
+ :fields => FILE_COLUMNS,
466
+ :parser => :csv
467
+ }.merge params
468
+
469
+ resp = listfiles(listfiles_params)[0]
470
+ return nil if "NONE" == resp[0]
471
+
472
+ response = {}
473
+
474
+ fields = listfiles_params[:fields].split(',')
475
+ fields.unshift "id"
476
+ fields.each_with_index do |value, index|
477
+ response[value.to_sym] = resp[index]
478
+ end
479
+
480
+ response
481
+ end
482
+
483
+ # Get file ID by absolute path
484
+ #
485
+ # api.file_id("foo/bar/baz/file.rar") # -> 456
486
+ def file_id(file_path, params = {})
487
+ params[:fields] = ""
488
+ file_info = file_info file_path, params
489
+ (file_info || {})[:id].to_i
490
+ end
491
+
492
+ protected
493
+
494
+ def path_trim(path)
495
+ path.gsub(/\A\/+/, '').gsub(/\/+\Z/, '')
496
+ end
497
+ end
498
+ end
499
+ end
@@ -0,0 +1,5 @@
1
+ module Rapidshare
2
+ module Ext
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require "rest-client"
2
+ require "rapidshare"
3
+ require "rapidshare-base/utils"
4
+ require "rapidshare-base/api"
5
+ require "rapidshare-ext/api"
6
+ require "rapidshare-ext/version"
7
+
8
+ class Rapidshare::API
9
+ include Rapidshare::Ext::API
10
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rapidshare-ext/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "rapidshare-ext"
6
+ gem.version = Rapidshare::Ext::VERSION
7
+ gem.date = '2012-11-18'
8
+ gem.authors = ["odiszapc"]
9
+ gem.email = ["odiszapc@gmail.com"]
10
+ gem.description = %q{Makes your interactions with Rapidshare API more pleasant by providing new handy features: creating/moving/deleting files/folders in a user friendly way, upload files, etc}
11
+ gem.summary = %q{Simplifies interactions with Rapidshare API with handy features}
12
+ gem.homepage = "http://github.com/odiszapc/rapidshare-ext"
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+
19
+
20
+ gem.add_dependency('rapidshare', '~> 0.5.3')
21
+ gem.add_dependency('rest-client', '~> 1.6.7')
22
+
23
+ gem.add_development_dependency('test-unit')
24
+ gem.add_development_dependency('shoulda')
25
+ gem.add_development_dependency('simplecov')
26
+ gem.add_development_dependency('fakeweb')
27
+ end
@@ -0,0 +1 @@
1
+ Test file for upload
@@ -0,0 +1,16 @@
1
+ accountid=12345
2
+ servertime=1217244932
3
+ addtime=127273393
4
+ username=valid_account
5
+ directstart=1
6
+ country=CZ
7
+ mailflags=
8
+ language=
9
+ jsconfig=1000
10
+ email=valid_account@email.com
11
+ curfiles=100
12
+ curspace=103994340
13
+ rapids=100
14
+ billeduntil=1320093121
15
+ nortuntil=1307123910
16
+ cookie=F0EEB41B38363A41F0D125102637DB7236468731F8DB760DC57934B4714C8D13
@@ -0,0 +1,173 @@
1
+ # encoding: utf-8
2
+ require 'digest/md5'
3
+ require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
4
+
5
+ class RapidshareExtTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ FakeWeb.allow_net_connect = true
9
+ @rs = Rapidshare::API.new :cookie => ENV['RAPIDSHARE_COOKIE']
10
+ @rs.erase_all_data!
11
+ end
12
+
13
+ context "Api" do
14
+ should "Upload file" do
15
+ assertion = ->(resp, size_local, digest_local, remote_filename) do
16
+ assert_instance_of Hash, resp
17
+ assert_kind_of Integer, resp[:id]
18
+ assert_kind_of Integer, resp[:size]
19
+ assert_equal size_local, resp[:size]
20
+ assert_instance_of String, resp[:checksum]
21
+ assert_match /[a-z0-9]{32}/, resp[:checksum]
22
+ assert_equal digest_local, resp[:checksum]
23
+ assert_instance_of String, resp[:url]
24
+ assert_equal "https://rapidshare.com/files/#{resp[:id]}/#{URI::encode(remote_filename)}", resp[:url]
25
+ end
26
+
27
+ file_info_assertion = ->(info, file_id, digest_local, size_local, remote_filename, remote_dir) do
28
+ assert_equal info[:filename], remote_filename
29
+ assert_equal info[:id].to_i, file_id
30
+ assert_equal info[:md5hex].downcase, digest_local
31
+ assert_equal info[:realfolder].to_i, @rs.folder_id(remote_dir)
32
+ assert_equal info[:size].to_i, size_local
33
+ end
34
+
35
+ local_path = File.expand_path(File.dirname(__FILE__) + "/../fixtures/files/upload1.txt")
36
+ remote_filename = "upload_file_1.txt"
37
+ remote_dir = "a/b/c"
38
+ remote_path = "#{remote_dir}/#{remote_filename}"
39
+ digest_local = Digest::MD5.hexdigest(File.read(local_path))
40
+ size_local = File.size local_path
41
+
42
+ # Initial upload
43
+ response = @rs.upload local_path, :as => remote_filename, :to => remote_dir
44
+ assertion.call response, size_local, digest_local, remote_filename
45
+ assert_false response[:already_exists?]
46
+
47
+ # Check file ID
48
+ file_id = @rs.file_id remote_path
49
+ assert_kind_of Integer, file_id
50
+ assert_equal file_id, response[:id]
51
+
52
+ # Check file info
53
+ info = @rs.file_info remote_path
54
+ file_info_assertion.call info, file_id, digest_local, size_local, remote_filename, remote_dir
55
+
56
+ # Upload the same file again
57
+ response = @rs.upload local_path, :as => remote_filename, :to => remote_dir
58
+ assertion.call response, size_local, digest_local, remote_filename
59
+ assert_true response[:already_exists?]
60
+
61
+ # Rename file
62
+ remote_filename_2 ="foo.txt"
63
+ remote_path_2 = "#{remote_dir}/#{remote_filename_2}"
64
+ @rs.rename_file remote_path, remote_filename_2
65
+ info = @rs.file_info remote_path_2
66
+ file_info_assertion.call info, @rs.file_id(remote_path_2), digest_local, size_local, remote_filename_2, remote_dir
67
+
68
+ # Move file
69
+ remote_dir_3 = "a/b"
70
+ remote_path_3 = "#{remote_dir_3}/#{remote_filename_2}"
71
+ @rs.move_file remote_path_2, :to => remote_dir_3
72
+
73
+ info = @rs.file_info remote_path_3
74
+ file_info_assertion.call info, @rs.file_id(remote_path_3), digest_local, size_local, remote_filename_2, remote_dir_3
75
+
76
+ # Delete file
77
+ @rs.remove_file remote_path_3
78
+
79
+ info = @rs.file_info remote_path_3
80
+ assert_nil info
81
+ end
82
+
83
+ should "Create folder" do
84
+ folder_id = @rs.add_folder "a/b/c"
85
+ assert_kind_of Integer, folder_id
86
+ assert_not_equal 0, folder_id
87
+ tree = @rs.folders_hierarchy
88
+
89
+ assert_equal 3, tree.count
90
+ assert_equal "a/b/c", tree[folder_id][:path]
91
+ assert_equal "a/b", tree[tree[folder_id][:parent]][:path]
92
+ assert_equal "a", tree[tree[tree[folder_id][:parent]][:parent]][:path]
93
+ end
94
+
95
+ should "Move folder" do
96
+ folder_id = @rs.add_folder "a/b/c"
97
+ assert_kind_of Integer, folder_id
98
+ assert_not_equal 0, folder_id
99
+ tree = @rs.folders_hierarchy
100
+
101
+ assert_equal 3, tree.count
102
+ assert_equal "a/b/c", tree[folder_id][:path]
103
+ assert_equal "a/b", tree[tree[folder_id][:parent]][:path]
104
+ assert_equal "a", tree[tree[tree[folder_id][:parent]][:parent]][:path]
105
+
106
+ @rs.move_folder "a/b/c", :to => 'a'
107
+
108
+ tree = @rs.reload!
109
+
110
+ assert_equal 3, tree.count
111
+ assert_equal "a/c", tree[folder_id][:path]
112
+ assert_equal @rs.folder_id("a"), tree[folder_id][:parent]
113
+ end
114
+
115
+ should "Build folder tree" do
116
+ # Create folder
117
+ folder_id = @rs.add_folder "a/b/c"
118
+ assert_kind_of Integer, folder_id
119
+ assert_not_equal 0, folder_id
120
+ tree = @rs.folders_hierarchy
121
+
122
+ # Validate tree
123
+ assert_equal 3, tree.count
124
+ assert_equal "a/b/c", tree[folder_id][:path]
125
+ assert_equal "a/b", tree[tree[folder_id][:parent]][:path]
126
+ assert_equal "a", tree[tree[tree[folder_id][:parent]][:parent]][:path]
127
+
128
+ # Validate subtree
129
+ sub_tree = @rs.folders_hierarchy :from => 'a/b'
130
+ assert_equal 1, sub_tree.count
131
+ assert_equal "c", sub_tree[folder_id][:path]
132
+ end
133
+
134
+ should "Remove folder" do
135
+ folder_id = @rs.add_folder "a/b/c"
136
+ assert_kind_of Integer, folder_id
137
+ assert_not_equal 0, folder_id
138
+ tree = @rs.folders_hierarchy
139
+ assert_equal 3, tree.count
140
+
141
+ @rs.remove_folder "a/b/c"
142
+
143
+ tree = @rs.folders_hierarchy!
144
+ assert_equal 2, tree.count
145
+
146
+
147
+ folder_id = @rs.add_folder "a/b/c"
148
+ assert_kind_of Integer, folder_id
149
+ assert_not_equal 0, folder_id
150
+ tree = @rs.folders_hierarchy!
151
+ assert_equal 3, tree.count
152
+
153
+ @rs.remove_folder "a"
154
+ tree = @rs.folders_hierarchy!
155
+ assert_equal 0, tree.count
156
+ end
157
+
158
+ should "Erase account" do
159
+ folder_id = @rs.add_folder "a/b/c"
160
+ assert_kind_of Integer, folder_id
161
+
162
+ folder_ids = @rs.folders_hierarchy.keys
163
+ assert_true folder_ids.count > 0
164
+
165
+ # Delete all data from account
166
+ @rs.erase_all_data!
167
+
168
+ folder_ids = @rs.folders_hierarchy.keys
169
+ assert_equal 0, folder_ids.count
170
+ end
171
+ end
172
+ end
173
+
@@ -0,0 +1,40 @@
1
+ require 'test/unit'
2
+ require 'shoulda'
3
+ require 'fakeweb'
4
+
5
+ require 'rapidshare-ext'
6
+
7
+ class Test::Unit::TestCase
8
+
9
+ # don't allow internet connections for testing (we should use fixtures, except
10
+ # integration testing)
11
+ FakeWeb.allow_net_connect = false
12
+
13
+ def read_fixture(filename, extension = 'txt')
14
+ # add extension to file unless it already has it
15
+ filename += ".#{extension}" unless (filename =~ /\.\w+$/)
16
+
17
+ File.read File.expand_path(File.dirname(__FILE__) + "/fixtures/#{filename}")
18
+ end
19
+
20
+ # general setup, can be overriden or extended in specific tests
21
+ #
22
+ def setup
23
+ @cookie = 'F0EEB41B38363A41F0D125102637DB7236468731F8DB760DC57934B4714C8D13'
24
+
25
+ # mock http requests for login into Rapidshare
26
+ #
27
+ FakeWeb.register_uri(:get,
28
+ 'https://api.rapidshare.com/cgi-bin/rsapi.cgi?sub=getaccountdetails&login=valid_login&password=valid_password&withcookie=1&cookie=',
29
+ :body => read_fixture('getaccountdetails_valid.txt')
30
+ )
31
+
32
+ FakeWeb.register_uri(:get,
33
+ "https://api.rapidshare.com/cgi-bin/rsapi.cgi?sub=getaccountdetails&cookie=#{@cookie}",
34
+ :body => read_fixture('getaccountdetails_valid.txt')
35
+ )
36
+
37
+ @rs = Rapidshare::API.new(:login => 'valid_login', :password => 'valid_password')
38
+ end
39
+
40
+ end
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ class RapidshareExtTest < Test::Unit::TestCase
4
+ context "Interface" do
5
+ should "Respond to certain methods" do
6
+ assert_respond_to @rs, :add_folder
7
+ assert_respond_to @rs, :remove_folder
8
+ assert_respond_to @rs, :move_folder
9
+ assert_respond_to @rs, :upload
10
+ assert_respond_to @rs, :remove_file
11
+ assert_respond_to @rs, :rename_file
12
+ assert_respond_to @rs, :folders_hierarchy
13
+ assert_respond_to @rs, :folders_hierarchy!
14
+ assert_respond_to @rs, :slice_tree
15
+ assert_respond_to @rs, :remove_orphans!
16
+ assert_respond_to @rs, :move_orphans
17
+ assert_respond_to @rs, :detect_gaps
18
+ assert_respond_to @rs, :erase_all_data!
19
+ assert_respond_to @rs, :root_folder?
20
+ assert_respond_to @rs, :gap?
21
+ assert_respond_to @rs, :orphan?
22
+ assert_respond_to @rs, :folder_path
23
+ assert_respond_to @rs, :folder_id
24
+ assert_respond_to @rs, :file_info
25
+ assert_respond_to @rs, :file_id
26
+ end
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rapidshare-ext
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - odiszapc
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rapidshare
16
+ requirement: &19166544 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.5.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *19166544
25
+ - !ruby/object:Gem::Dependency
26
+ name: rest-client
27
+ requirement: &19165800 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.6.7
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *19165800
36
+ - !ruby/object:Gem::Dependency
37
+ name: test-unit
38
+ requirement: &19165332 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *19165332
47
+ - !ruby/object:Gem::Dependency
48
+ name: shoulda
49
+ requirement: &19164948 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *19164948
58
+ - !ruby/object:Gem::Dependency
59
+ name: simplecov
60
+ requirement: &19164600 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *19164600
69
+ - !ruby/object:Gem::Dependency
70
+ name: fakeweb
71
+ requirement: &19164180 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *19164180
80
+ description: ! 'Makes your interactions with Rapidshare API more pleasant by providing
81
+ new handy features: creating/moving/deleting files/folders in a user friendly way,
82
+ upload files, etc'
83
+ email:
84
+ - odiszapc@gmail.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - .gitignore
90
+ - Gemfile
91
+ - History.md
92
+ - LICENSE
93
+ - README.md
94
+ - Rakefile
95
+ - lib/rapidshare-base/api.rb
96
+ - lib/rapidshare-base/utils.rb
97
+ - lib/rapidshare-ext.rb
98
+ - lib/rapidshare-ext/api.rb
99
+ - lib/rapidshare-ext/version.rb
100
+ - rapidshare-ext.gemspec
101
+ - test/fixtures/files/upload1.txt
102
+ - test/fixtures/getaccountdetails_valid.txt
103
+ - test/integration/rapidshare-ext_test.rb
104
+ - test/test_helper.rb
105
+ - test/unit/rapidshare-ext_test.rb
106
+ homepage: http://github.com/odiszapc/rapidshare-ext
107
+ licenses: []
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 1.8.16
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: Simplifies interactions with Rapidshare API with handy features
130
+ test_files:
131
+ - test/fixtures/files/upload1.txt
132
+ - test/fixtures/getaccountdetails_valid.txt
133
+ - test/integration/rapidshare-ext_test.rb
134
+ - test/test_helper.rb
135
+ - test/unit/rapidshare-ext_test.rb