rapidshare-ext 0.0.1

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 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