progstr-filer 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +69 -0
- data/Guardfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/lib/filer/activerecord.rb +116 -0
- data/lib/filer/attachment.rb +51 -0
- data/lib/filer/config.rb +38 -0
- data/lib/filer/file_info.rb +27 -0
- data/lib/filer/uploader.rb +68 -0
- data/lib/progstr-filer.rb +27 -0
- data/test/file_like.rb +6 -0
- data/test/test_attachment_url.rb +11 -0
- data/test/test_file_mount.rb +31 -0
- data/test/test_helper.rb +24 -0
- data/test/test_http_upload.rb +44 -0
- data/test/test_id_generation.rb +17 -0
- data/test/test_upload_on_save.rb +37 -0
- data/test/user_data.rb +48 -0
- metadata +217 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "sqlite3"
|
10
|
+
gem "guard-test"
|
11
|
+
gem "rb-inotify"
|
12
|
+
gem "libnotify"
|
13
|
+
gem "shoulda", ">= 0"
|
14
|
+
#gem "rspec", "~> 2.3.0"
|
15
|
+
gem "bundler", "~> 1.0.0"
|
16
|
+
gem "jeweler", "~> 1.6.4"
|
17
|
+
gem "rcov", ">= 0"
|
18
|
+
gem "rdoc", ">= 0"
|
19
|
+
end
|
20
|
+
|
21
|
+
gem "activerecord", ">=3.0.0"
|
22
|
+
gem "uuid"
|
23
|
+
gem "rest-client"
|
24
|
+
gem "multi_json"
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.1.3)
|
5
|
+
activesupport (= 3.1.3)
|
6
|
+
builder (~> 3.0.0)
|
7
|
+
i18n (~> 0.6)
|
8
|
+
activerecord (3.1.3)
|
9
|
+
activemodel (= 3.1.3)
|
10
|
+
activesupport (= 3.1.3)
|
11
|
+
arel (~> 2.2.1)
|
12
|
+
tzinfo (~> 0.3.29)
|
13
|
+
activesupport (3.1.3)
|
14
|
+
multi_json (~> 1.0)
|
15
|
+
arel (2.2.1)
|
16
|
+
builder (3.0.0)
|
17
|
+
ffi (1.0.11)
|
18
|
+
git (1.2.5)
|
19
|
+
guard (0.9.1)
|
20
|
+
ffi (>= 0.5.0)
|
21
|
+
thor (~> 0.14.6)
|
22
|
+
guard-test (0.4.2)
|
23
|
+
guard (>= 0.4)
|
24
|
+
test-unit (~> 2.2)
|
25
|
+
i18n (0.6.0)
|
26
|
+
jeweler (1.6.4)
|
27
|
+
bundler (~> 1.0)
|
28
|
+
git (>= 1.2.5)
|
29
|
+
rake
|
30
|
+
json (1.6.3)
|
31
|
+
libnotify (0.6.0)
|
32
|
+
macaddr (1.5.0)
|
33
|
+
systemu (>= 2.4.0)
|
34
|
+
mime-types (1.17.2)
|
35
|
+
multi_json (1.0.4)
|
36
|
+
rake (0.9.2.2)
|
37
|
+
rb-inotify (0.8.8)
|
38
|
+
ffi (>= 0.5.0)
|
39
|
+
rcov (0.9.11)
|
40
|
+
rdoc (3.12)
|
41
|
+
json (~> 1.4)
|
42
|
+
rest-client (1.6.7)
|
43
|
+
mime-types (>= 1.16)
|
44
|
+
shoulda (2.11.3)
|
45
|
+
sqlite3 (1.3.5)
|
46
|
+
systemu (2.4.2)
|
47
|
+
test-unit (2.4.3)
|
48
|
+
thor (0.14.6)
|
49
|
+
tzinfo (0.3.31)
|
50
|
+
uuid (2.3.4)
|
51
|
+
macaddr (~> 1.0)
|
52
|
+
|
53
|
+
PLATFORMS
|
54
|
+
ruby
|
55
|
+
|
56
|
+
DEPENDENCIES
|
57
|
+
activerecord (>= 3.0.0)
|
58
|
+
bundler (~> 1.0.0)
|
59
|
+
guard-test
|
60
|
+
jeweler (~> 1.6.4)
|
61
|
+
libnotify
|
62
|
+
multi_json
|
63
|
+
rb-inotify
|
64
|
+
rcov
|
65
|
+
rdoc
|
66
|
+
rest-client
|
67
|
+
shoulda
|
68
|
+
sqlite3
|
69
|
+
uuid
|
data/Guardfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# A sample Guardfile
|
4
|
+
# More info at https://github.com/guard/guard#readme
|
5
|
+
|
6
|
+
guard :test do
|
7
|
+
watch(%r{^lib/(.+)\.rb$}) { puts "REEVALUATE and run ALL tests."; Dsl.reevaluate_guardfile; "test" }
|
8
|
+
watch(%r{^test/.+_test\.rb$})
|
9
|
+
watch(%r{^test/test_.+\.rb$})
|
10
|
+
watch('test/helper.rb') { "test" }
|
11
|
+
|
12
|
+
# Rails example
|
13
|
+
# watch(%r{^app/models/(.+)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" }
|
14
|
+
# watch(%r{^app/controllers/(.+)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
|
15
|
+
# watch(%r{^app/views/.+\.rb$}) { "test/integration" }
|
16
|
+
# watch('app/controllers/application_controller.rb') { ["test/functional", "test/integration"] }
|
17
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Hristo Deshev
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= filer-gem
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to filer-gem
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
+
* Fork the project
|
10
|
+
* Start a feature/bugfix branch
|
11
|
+
* Commit and push until you are happy with your contribution
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2011 Hristo Deshev. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "progstr-filer"
|
18
|
+
gem.homepage = "http://github.com/progstr/progstr-filer-gem"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Progstr Filer API}
|
21
|
+
gem.description = %Q{Progstr Filer is a developer-friendly file and attachment hosting service that lets you easily build apps that store and share files.}
|
22
|
+
gem.email = "hristo@deshev.com"
|
23
|
+
gem.authors = ["Hristo Deshev"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
#require 'rspec/core'
|
29
|
+
#require 'rspec/core/rake_task'
|
30
|
+
#RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
#spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
#end
|
33
|
+
|
34
|
+
require 'rake/testtask'
|
35
|
+
Rake::TestTask.new(:test) do |test|
|
36
|
+
test.libs << 'lib' << 'test'
|
37
|
+
test.pattern = 'test/**/test_*.rb'
|
38
|
+
test.verbose = true
|
39
|
+
end
|
40
|
+
|
41
|
+
#RSpec::Core::RakeTask.new(:rcov) do |spec|
|
42
|
+
#spec.pattern = 'spec/**/*_spec.rb'
|
43
|
+
#spec.rcov = true
|
44
|
+
#end
|
45
|
+
require 'rcov/rcovtask'
|
46
|
+
Rcov::RcovTask.new do |test|
|
47
|
+
test.libs << 'test'
|
48
|
+
test.pattern = 'test/**/test_*.rb'
|
49
|
+
test.verbose = true
|
50
|
+
test.rcov_opts << '--exclude "gems/*"'
|
51
|
+
end
|
52
|
+
|
53
|
+
task :default => :spec
|
54
|
+
|
55
|
+
require 'rdoc/task'
|
56
|
+
Rake::RDocTask.new do |rdoc|
|
57
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
58
|
+
|
59
|
+
rdoc.rdoc_dir = 'rdoc'
|
60
|
+
rdoc.title = "filer-gem #{version}"
|
61
|
+
rdoc.rdoc_files.include('README*')
|
62
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
63
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.8.0
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Progstr
|
4
|
+
module Filer
|
5
|
+
module ActiveRecordClassMethods
|
6
|
+
def _uploaders
|
7
|
+
if @uploaders.nil?
|
8
|
+
@uploaders = {}
|
9
|
+
@uploaders = superclass._uploaders.merge(@uploaders) if superclass.respond_to?(:_uploaders)
|
10
|
+
after_save :"_filer_after_save"
|
11
|
+
before_destroy :"_filer_before_delete"
|
12
|
+
end
|
13
|
+
@uploaders
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_file(attribute, uploaderClass)
|
17
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
18
|
+
def #{attribute}
|
19
|
+
_get_attachment(:#{attribute})
|
20
|
+
end
|
21
|
+
def #{attribute}=(new_file)
|
22
|
+
_set_attachment(:#{attribute}, new_file)
|
23
|
+
end
|
24
|
+
def upload_#{attribute}!
|
25
|
+
_upload_attachment(:#{attribute})
|
26
|
+
end
|
27
|
+
RUBY
|
28
|
+
|
29
|
+
_uploaders[attribute] = uploaderClass.new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module ActiveRecordInstanceMethods
|
34
|
+
def _attachments
|
35
|
+
@_filer_attachments ||= {}
|
36
|
+
@_filer_attachments
|
37
|
+
end
|
38
|
+
|
39
|
+
def _attachments_to_delete
|
40
|
+
@_filer_attachments_to_delete ||= []
|
41
|
+
@_filer_attachments_to_delete
|
42
|
+
end
|
43
|
+
|
44
|
+
def _get_attachment(attribute)
|
45
|
+
if _attachments[attribute].nil?
|
46
|
+
id = read_attribute(attribute)
|
47
|
+
if id.nil?
|
48
|
+
_attachments[attribute] = Attachment.empty
|
49
|
+
else
|
50
|
+
_attachments[attribute] = Attachment.from_id(attribute, id)
|
51
|
+
end
|
52
|
+
else
|
53
|
+
_attachments[attribute]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def _set_attachment(attribute, file)
|
58
|
+
old_attachment = _get_attachment(attribute)
|
59
|
+
unless old_attachment.empty?
|
60
|
+
_attachments_to_delete << old_attachment
|
61
|
+
end
|
62
|
+
|
63
|
+
attachment = Attachment.from_file(attribute, file)
|
64
|
+
_attachments[attribute] = attachment
|
65
|
+
write_attribute(attribute, attachment.id)
|
66
|
+
end
|
67
|
+
|
68
|
+
def _upload_attachments
|
69
|
+
self.class._uploaders.each do |item|
|
70
|
+
attribute = item[0]
|
71
|
+
_upload_attachment(attribute)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def _upload_attachment(attribute)
|
76
|
+
attachment = _get_attachment(attribute)
|
77
|
+
if (!attachment.empty?) && (!attachment.file.nil?)
|
78
|
+
uploader = self.class._uploaders[attribute]
|
79
|
+
uploader.upload_attachment(attachment) unless uploader.nil?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def _delete_expired_attachments
|
84
|
+
_attachments_to_delete.each do |attachment|
|
85
|
+
uploader = self.class._uploaders[attachment.attribute]
|
86
|
+
uploader.delete_attachment(attachment) unless uploader.nil?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def _filer_after_save
|
91
|
+
_upload_attachments
|
92
|
+
_delete_expired_attachments
|
93
|
+
end
|
94
|
+
|
95
|
+
def _expire_all_attachments
|
96
|
+
# trigger entity load
|
97
|
+
dummy = read_attribute(:dummy)
|
98
|
+
self.class._uploaders.each do |item|
|
99
|
+
attribute = item[0]
|
100
|
+
#load attachment object
|
101
|
+
dummy = _get_attachment(attribute)
|
102
|
+
#schedule it for deletion
|
103
|
+
_set_attachment(attribute, nil)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def _filer_before_delete
|
108
|
+
_expire_all_attachments
|
109
|
+
_delete_expired_attachments
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
ActiveRecord::Base.extend Progstr::Filer::ActiveRecordClassMethods
|
116
|
+
ActiveRecord::Base.send(:include, Progstr::Filer::ActiveRecordInstanceMethods)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Progstr
|
2
|
+
module Filer
|
3
|
+
class Attachment
|
4
|
+
attr_accessor :id, :attribute, :file
|
5
|
+
|
6
|
+
@@id_generator = ::UUID.new
|
7
|
+
|
8
|
+
class EmptyAttachment
|
9
|
+
def empty?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.empty
|
15
|
+
EmptyAttachment.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.from_file(attribute, file)
|
19
|
+
result = Attachment.new
|
20
|
+
result.id = generate_id
|
21
|
+
result.attribute = attribute
|
22
|
+
result.file = file
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.generate_id
|
27
|
+
uuid = @@id_generator.generate
|
28
|
+
uuid.gsub("-", "")
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.from_id(attribute, id)
|
32
|
+
result = Attachment.new
|
33
|
+
result.id = id
|
34
|
+
result.attribute = attribute
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def empty?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def url
|
43
|
+
if !empty?
|
44
|
+
"#{Progstr::Filer.url_prefix}files/data/#{Progstr::Filer.access_key}/#{id}"
|
45
|
+
else
|
46
|
+
""
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/filer/config.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
module Progstr
|
3
|
+
module Filer
|
4
|
+
class << self
|
5
|
+
attr_accessor :host, :port, :path_prefix, :access_key, :secret_key, :session_timeout
|
6
|
+
|
7
|
+
def host
|
8
|
+
@host ||= "filer-api.progstr.com"
|
9
|
+
end
|
10
|
+
def port
|
11
|
+
@port || 80
|
12
|
+
end
|
13
|
+
def path_prefix
|
14
|
+
@path_prefix ||= '/'
|
15
|
+
end
|
16
|
+
def session_timeout
|
17
|
+
@http_read_timeout ||= 30 * 60 # 30 minutes
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_auth_token
|
21
|
+
expiration_seconds = (Time.now + session_timeout).to_i
|
22
|
+
expiration_millis = expiration_seconds * 1000
|
23
|
+
data = "#{access_key}-#{expiration_millis}-#{secret_key}"
|
24
|
+
signature = Digest::SHA1.hexdigest(data)
|
25
|
+
"#{access_key}-#{expiration_millis}-#{signature}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def url_prefix
|
29
|
+
prefix = "http://#{Progstr::Filer.host}:#{Progstr::Filer.port}#{Progstr::Filer.path_prefix}"
|
30
|
+
if prefix.end_with? "/"
|
31
|
+
prefix
|
32
|
+
else
|
33
|
+
prefix + "/"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class FileInfo
|
2
|
+
def initialize(values)
|
3
|
+
@values = values
|
4
|
+
end
|
5
|
+
|
6
|
+
def id
|
7
|
+
@values["_id"]
|
8
|
+
end
|
9
|
+
def name
|
10
|
+
@values["name"]
|
11
|
+
end
|
12
|
+
def content_type
|
13
|
+
@values["contentType"]
|
14
|
+
end
|
15
|
+
def property
|
16
|
+
@values["property"]
|
17
|
+
end
|
18
|
+
def uploader
|
19
|
+
@values["uploader"]
|
20
|
+
end
|
21
|
+
def size
|
22
|
+
@values["size"]
|
23
|
+
end
|
24
|
+
def created_at
|
25
|
+
@values["createdAt"]
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Progstr
|
2
|
+
module Filer
|
3
|
+
class UploadStatus
|
4
|
+
def initialize(parsed)
|
5
|
+
@parsed = parsed
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
@parsed["name"]
|
10
|
+
end
|
11
|
+
def success
|
12
|
+
@parsed["success"]
|
13
|
+
end
|
14
|
+
def message
|
15
|
+
@parsed["message"]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Uploader
|
20
|
+
def json_headers
|
21
|
+
{
|
22
|
+
:accept => "application/json",
|
23
|
+
:"X-Progstr-Filer-Auth-Token" => Progstr::Filer.generate_auth_token,
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def upload_attachment(attachment)
|
28
|
+
url = "#{Progstr::Filer.url_prefix}upload/new"
|
29
|
+
begin
|
30
|
+
response = RestClient.post(url, {
|
31
|
+
:upload1 => attachment.file,
|
32
|
+
:authToken => Progstr::Filer.generate_auth_token,
|
33
|
+
:property => attachment.attribute,
|
34
|
+
:uploader => self.class.name,
|
35
|
+
:id => attachment.id
|
36
|
+
}, json_headers)
|
37
|
+
UploadStatus.new MultiJson.decode(response)
|
38
|
+
rescue => e
|
39
|
+
if e.respond_to?(:response) && !e.response.nil?
|
40
|
+
raise ApiError.new(UploadStatus.new MultiJson.decode(e.response))
|
41
|
+
else
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete_attachment(attachment)
|
48
|
+
url = "#{Progstr::Filer.url_prefix}files/#{attachment.id}"
|
49
|
+
begin
|
50
|
+
response = RestClient.delete(url, json_headers)
|
51
|
+
UploadStatus.new MultiJson.decode(response)
|
52
|
+
rescue => e
|
53
|
+
raise ApiError.new(MultiJson.decode(e.response))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def file_info(attachment)
|
58
|
+
url = "#{Progstr::Filer.url_prefix}files/info/#{attachment.id}"
|
59
|
+
begin
|
60
|
+
response = RestClient.get(url, json_headers)
|
61
|
+
FileInfo.new MultiJson.decode(response)
|
62
|
+
rescue => e
|
63
|
+
raise ApiError.new(MultiJson.decode(e.response))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'digest'
|
3
|
+
require 'rest-client'
|
4
|
+
require 'uuid'
|
5
|
+
UUID.state_file = false
|
6
|
+
|
7
|
+
require 'filer/config'
|
8
|
+
require 'filer/uploader'
|
9
|
+
require 'filer/attachment'
|
10
|
+
require 'filer/file_info'
|
11
|
+
require 'filer/activerecord'
|
12
|
+
|
13
|
+
class ApiError < StandardError
|
14
|
+
attr_accessor :server_response
|
15
|
+
|
16
|
+
def initialize(server_response)
|
17
|
+
@server_response = server_response
|
18
|
+
end
|
19
|
+
|
20
|
+
def message
|
21
|
+
@server_response["message"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def http_code
|
25
|
+
@server_response["httpCode"]
|
26
|
+
end
|
27
|
+
end
|
data/test/file_like.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestIdGeneration < Test::Unit::TestCase
|
4
|
+
should "generate file URLs using accessKey and file ID" do
|
5
|
+
a = Progstr::Filer::Attachment.from_id(:avatar, "some-attachment-id")
|
6
|
+
|
7
|
+
assert_match "/files/data/DEMO/some-attachment-id", a.url
|
8
|
+
assert_match Progstr::Filer.host, a.url
|
9
|
+
assert_match Progstr::Filer.host, a.url
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestFileMount < UserTest
|
4
|
+
should "start with an empty uploader field" do
|
5
|
+
empty = User.new
|
6
|
+
assert_true empty.avatar.empty?
|
7
|
+
end
|
8
|
+
|
9
|
+
should "create an uploader when given a file" do
|
10
|
+
non_empty = User.new
|
11
|
+
non_empty.avatar = FileLike.new
|
12
|
+
assert_false non_empty.avatar.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
should "save persist attachment id only" do
|
16
|
+
u = User.new
|
17
|
+
u.avatar = FileLike.new
|
18
|
+
assert_not_nil u.avatar.id, "attachment id generated"
|
19
|
+
assert_equal u.read_attribute(:avatar), u.avatar.id, "attachment id saved to db"
|
20
|
+
end
|
21
|
+
|
22
|
+
should "save and load attachment id to db" do
|
23
|
+
u = User.new
|
24
|
+
u.avatar = FileLike.new
|
25
|
+
u.save!
|
26
|
+
|
27
|
+
loaded = User.find(u.id)
|
28
|
+
assert_equal loaded.read_attribute(:avatar), u.avatar.id, "Loaded avatar id from db"
|
29
|
+
assert_equal loaded.avatar.id, u.avatar.id, "Loaded avatar id matches the saved one."
|
30
|
+
end
|
31
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
#require 'rspec'
|
4
|
+
require 'progstr-filer'
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'shoulda'
|
8
|
+
require 'file_like'
|
9
|
+
require 'user_data'
|
10
|
+
require 'pp'
|
11
|
+
|
12
|
+
Progstr::Filer.host = "filer.local"
|
13
|
+
Progstr::Filer.port = "8080"
|
14
|
+
|
15
|
+
Progstr::Filer.access_key = "DEMO"
|
16
|
+
Progstr::Filer.secret_key = "DEMO"
|
17
|
+
|
18
|
+
# Requires supporting files with custom matchers and macros, etc,
|
19
|
+
# in ./support/ and its subdirectories.
|
20
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
21
|
+
|
22
|
+
#RSpec.configure do |config|
|
23
|
+
|
24
|
+
#end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class DemoUploader < Progstr::Filer::Uploader
|
4
|
+
end
|
5
|
+
|
6
|
+
class TestHttpUpload < Test::Unit::TestCase
|
7
|
+
# The mother of all tests calling the real API
|
8
|
+
test "upload attachment" do
|
9
|
+
attachment = Progstr::Filer::Attachment.from_file(:version, File.open("VERSION"))
|
10
|
+
uploader = DemoUploader.new
|
11
|
+
response = uploader.upload_attachment attachment
|
12
|
+
|
13
|
+
assert_equal response.name, "VERSION"
|
14
|
+
assert_equal response.success, true
|
15
|
+
assert_equal response.message, "OK"
|
16
|
+
|
17
|
+
info = uploader.file_info attachment
|
18
|
+
|
19
|
+
assert_equal info.name, "VERSION"
|
20
|
+
assert_equal info.id, attachment.id
|
21
|
+
assert_equal info.content_type, "text/plain"
|
22
|
+
assert_equal info.property, "version"
|
23
|
+
assert_equal info.uploader, "DemoUploader"
|
24
|
+
assert_true info.size > 0
|
25
|
+
|
26
|
+
response = uploader.delete_attachment attachment
|
27
|
+
assert_equal response.message, "OK"
|
28
|
+
end
|
29
|
+
|
30
|
+
test "report error on failed upload" do
|
31
|
+
begin
|
32
|
+
Progstr::Filer.secret_key = "BROKEN"
|
33
|
+
|
34
|
+
attachment = Progstr::Filer::Attachment.from_file(:version, File.open("VERSION"))
|
35
|
+
uploader = DemoUploader.new
|
36
|
+
response = uploader.upload_attachment attachment
|
37
|
+
|
38
|
+
assert_equal response.success, false
|
39
|
+
assert_match "Session expired or authorization failed", response.message,
|
40
|
+
ensure
|
41
|
+
Progstr::Filer.secret_key = "DEMO"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestIdGeneration < Test::Unit::TestCase
|
4
|
+
should "use UUIDs for attachment IDs" do
|
5
|
+
a = Progstr::Filer::Attachment.from_file(:avatar, FileLike.new)
|
6
|
+
b = Progstr::Filer::Attachment.from_file(:avatar, FileLike.new)
|
7
|
+
|
8
|
+
assert_not_nil a.id
|
9
|
+
assert_not_nil b.id
|
10
|
+
assert_not_equal a.id, b.id
|
11
|
+
end
|
12
|
+
|
13
|
+
should "have attachment IDs with no hyphens" do
|
14
|
+
a = Progstr::Filer::Attachment.from_file(:avatar, FileLike.new)
|
15
|
+
assert_match /^[^-]+$/, a.id, "IDs should contain no hyphens"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestUploadOnSave < UserTest
|
4
|
+
should "upload file on record save" do
|
5
|
+
u = User.new
|
6
|
+
f = FileLike.new
|
7
|
+
u.avatar = f
|
8
|
+
|
9
|
+
u.save!
|
10
|
+
assert_same $lastUploadedAtachment.file, f, "avatar file sent to uploader"
|
11
|
+
end
|
12
|
+
|
13
|
+
should "delete the previous attachment on save" do
|
14
|
+
u = User.new
|
15
|
+
f1 = FileLike.new
|
16
|
+
u.avatar = f1
|
17
|
+
u.save!
|
18
|
+
|
19
|
+
f2 = FileLike.new
|
20
|
+
u.avatar = f2
|
21
|
+
|
22
|
+
u.save!
|
23
|
+
assert_same $lastDeletedAtachment.file, f1, "overwritten attachment deleted"
|
24
|
+
end
|
25
|
+
|
26
|
+
should "delete attachments on record delete" do
|
27
|
+
u = User.new
|
28
|
+
f1 = FileLike.new
|
29
|
+
u.avatar = f1
|
30
|
+
u.save!
|
31
|
+
|
32
|
+
# Note that calling `delete` doesn't run the before_destroy hook and attachments won't get deleted
|
33
|
+
# u.delete
|
34
|
+
u.destroy
|
35
|
+
assert_same $lastDeletedAtachment.file, f1, "overwritten attachment deleted"
|
36
|
+
end
|
37
|
+
end
|
data/test/user_data.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
dbconfig = {
|
2
|
+
:adapter => 'sqlite3',
|
3
|
+
:database => ':memory:'
|
4
|
+
}
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection(dbconfig)
|
7
|
+
ActiveRecord::Migration.verbose = false
|
8
|
+
|
9
|
+
class TestUserMigration < ActiveRecord::Migration
|
10
|
+
def self.up
|
11
|
+
create_table :users, :force => true do |t|
|
12
|
+
t.column :name, :string
|
13
|
+
t.column :avatar, :string
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.down
|
18
|
+
drop_table :users
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
$lastUploadedAtachment = nil
|
23
|
+
$lastDeletedAtachment = nil
|
24
|
+
|
25
|
+
class MockUploader < Progstr::Filer::Uploader
|
26
|
+
def upload_attachment(attachment)
|
27
|
+
$lastUploadedAtachment = attachment
|
28
|
+
end
|
29
|
+
def delete_attachment(attachment)
|
30
|
+
$lastDeletedAtachment = attachment
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
class User < ActiveRecord::Base
|
36
|
+
has_file :avatar, MockUploader
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
class UserTest < Test::Unit::TestCase
|
41
|
+
def setup
|
42
|
+
TestUserMigration.up
|
43
|
+
end
|
44
|
+
|
45
|
+
def teardown
|
46
|
+
TestUserMigration.down
|
47
|
+
end
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: progstr-filer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Hristo Deshev
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-22 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: &15593540 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *15593540
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: uuid
|
27
|
+
requirement: &15592400 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *15592400
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rest-client
|
38
|
+
requirement: &15591680 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *15591680
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: multi_json
|
49
|
+
requirement: &15590700 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *15590700
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: sqlite3
|
60
|
+
requirement: &15590140 !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: *15590140
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-test
|
71
|
+
requirement: &15556920 !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: *15556920
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: rb-inotify
|
82
|
+
requirement: &15555680 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *15555680
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: libnotify
|
93
|
+
requirement: &15554380 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *15554380
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: shoulda
|
104
|
+
requirement: &15553320 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *15553320
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: bundler
|
115
|
+
requirement: &15552060 !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ~>
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 1.0.0
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: *15552060
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: jeweler
|
126
|
+
requirement: &15550400 !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 1.6.4
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: *15550400
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: rcov
|
137
|
+
requirement: &15549600 !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
type: :development
|
144
|
+
prerelease: false
|
145
|
+
version_requirements: *15549600
|
146
|
+
- !ruby/object:Gem::Dependency
|
147
|
+
name: rdoc
|
148
|
+
requirement: &15543260 !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ! '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
type: :development
|
155
|
+
prerelease: false
|
156
|
+
version_requirements: *15543260
|
157
|
+
description: Progstr Filer is a developer-friendly file and attachment hosting service
|
158
|
+
that lets you easily build apps that store and share files.
|
159
|
+
email: hristo@deshev.com
|
160
|
+
executables: []
|
161
|
+
extensions: []
|
162
|
+
extra_rdoc_files:
|
163
|
+
- LICENSE.txt
|
164
|
+
- README.rdoc
|
165
|
+
files:
|
166
|
+
- .document
|
167
|
+
- .rspec
|
168
|
+
- Gemfile
|
169
|
+
- Gemfile.lock
|
170
|
+
- Guardfile
|
171
|
+
- LICENSE.txt
|
172
|
+
- README.rdoc
|
173
|
+
- Rakefile
|
174
|
+
- VERSION
|
175
|
+
- lib/filer/activerecord.rb
|
176
|
+
- lib/filer/attachment.rb
|
177
|
+
- lib/filer/config.rb
|
178
|
+
- lib/filer/file_info.rb
|
179
|
+
- lib/filer/uploader.rb
|
180
|
+
- lib/progstr-filer.rb
|
181
|
+
- test/file_like.rb
|
182
|
+
- test/test_attachment_url.rb
|
183
|
+
- test/test_file_mount.rb
|
184
|
+
- test/test_helper.rb
|
185
|
+
- test/test_http_upload.rb
|
186
|
+
- test/test_id_generation.rb
|
187
|
+
- test/test_upload_on_save.rb
|
188
|
+
- test/user_data.rb
|
189
|
+
homepage: http://github.com/progstr/progstr-filer-gem
|
190
|
+
licenses:
|
191
|
+
- MIT
|
192
|
+
post_install_message:
|
193
|
+
rdoc_options: []
|
194
|
+
require_paths:
|
195
|
+
- lib
|
196
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
197
|
+
none: false
|
198
|
+
requirements:
|
199
|
+
- - ! '>='
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
segments:
|
203
|
+
- 0
|
204
|
+
hash: 2703032228924922704
|
205
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
206
|
+
none: false
|
207
|
+
requirements:
|
208
|
+
- - ! '>='
|
209
|
+
- !ruby/object:Gem::Version
|
210
|
+
version: '0'
|
211
|
+
requirements: []
|
212
|
+
rubyforge_project:
|
213
|
+
rubygems_version: 1.8.6
|
214
|
+
signing_key:
|
215
|
+
specification_version: 3
|
216
|
+
summary: Progstr Filer API
|
217
|
+
test_files: []
|