mountable_file_server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fc6b91b6cf9ef96d1231d0f403e7f468fd75d1f4
4
+ data.tar.gz: 9b00ac82aff5a58988d8154d91ccc8e465684d55
5
+ SHA512:
6
+ metadata.gz: f2a1ecd0b539c510c677273bf44fac23df744c0c48ee225c13ea8dd964e2a03bfa1f9f6fb1df61ba0857b6cbafd3a725ce5cfae96d8217d17baae991b3d71789
7
+ data.tar.gz: a3dbbe1fbcba59f7e70963827c17f1b506e151bc1f8c45a9300939c42aef5a5b457d8d8e15e10478e01498a00a64f6f1a400d47e0f0b5a81f77433e2a2e1c91d
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *.log
16
+ *.sqlite3
17
+ test/rails-dummy/tmp/
18
+ test/rails-dummy/uploads/
19
+ .ruby-version
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mountable_file_server.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 David Strauß
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,64 @@
1
+ The fundamental idea is that your application only deals with identifiers of uploaded files in string form. No need to handle files directly. It should not complicate testing. It should allow files that are publicly accessible and private files that are not. Uploads happen via Ajax without additional work on the application developers side. It allows easy customization and adaption of the file upload process on the frontend to give a good user experience.
2
+
3
+ While it is possible to use in an Ruby on Rails application the gem itself does not depend on it and does not assume you use it.
4
+
5
+ ## Concept
6
+ You mount a tiny Sinatra application, the `MountableFileServer::Endpoint`, at an URL path of your choosing. The endpoint accepts AJAX file uploads and can also deliver requested files that are already permanently stored. (Later on we take a closer look in which scenarios it makes sense to deliver file requests via the endpoint instead of your webserver.)
7
+
8
+ For every uploaded file the endpoint responds with an unique identifier, a simple string. This identifier allows you to identify an uploaded file later on. Therefore it is the only thing your application needs to know and persist. The JavaScript provided by the gem handles the AJAX file upload and writes the identifier to a hidden input field with the same `name` attribute as the original file input field. When the form is submitted your application only deals with a simple string value from a hidden input field instead of an actual file upload.
9
+
10
+ At first a newly uploaded file is only stored temporarly, when the user submits the form and your application deemed it valid and persists the data it is the applications responsibility to move the uploaded file to the permanent storage using the `MountableFileServer::Storage` class. This is necessary to prevent accumulating file garbage due to abandoned forms where an upload already happened.
11
+
12
+ ## Moving Parts
13
+ `MountableFileServer::Endpoint` is a Sinatra application that can be mounted anywhere you want. It accepts uploads and responds with requested files if necessary. `MountableFileServer::Storage` is responsible for working with the actual files. Both take a `MountableFileServer::Configuration` which is used to define key settings specific to your usage scenario.
14
+
15
+ ## Configuration
16
+ ~~~ruby
17
+ configuration = MountableFileServer::Configuration.new stored_at: '~/uploads', mounted_at: '/uploads'
18
+ ~~~
19
+
20
+ ~~~ruby
21
+ storage = MountableFileServer::Storage.new config
22
+ upload = MountableFileServer::Upload.new file: file_parameters[:tempfile].read, type: 'public'
23
+
24
+ identifier = storage.store_temporary upload: upload
25
+ storage.move_to_permanent_storage identifier: identifier
26
+
27
+ storage.path_for identifier: identifier
28
+ storage.url_for identifier: identifier
29
+ ~~~
30
+
31
+ ~~~html
32
+ <input type="file" name="avatar" data-endpoint="/uploads" data-type="public">
33
+ ~~~
34
+
35
+
36
+
37
+ Wieso ist MFS besser?
38
+
39
+ Losgekoppelt von der Anwendung / Active Record
40
+ Beispiel Schaltwerk beim Fahrrad im Bezug auf Versionen
41
+ Komplexe Lösungen mit hunderten Issues wo probleme erwähnt werden
42
+ Erlaubt es einfach gute UI zu machen
43
+ Upload passiert nicht im Request zum Controller
44
+ Private Uploads
45
+
46
+ Funktioniert nur wenn du Dateien auf deinem Server hast.
47
+ Nichts für Gem Installier Programmierer die erwarten das ein Gem ihren UseCase ohne zusätzlichen Aufwand löst.
48
+
49
+
50
+ Open Fragen:
51
+ Upload nur auf Gruppe X einschränken
52
+
53
+
54
+ Example1:Topic: Realestatephotography
55
+ Subtopic: Housesforsale
56
+ Angle: Picturesthatinspirebuyers Oneword: Preparation
57
+ What: Is wrong with most advertising photos? Why: Are they wrong?
58
+ How: Can they be improved?
59
+ Who: Is best to take the photos?
60
+ Objections:
61
+ Lack of time
62
+ Houses still sell Cost Deception
63
+ Example: Changingagents/changingpictures/ sale
64
+ Summary: Advantages
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test/"
6
+ t.libs << "test/acceptance"
7
+ t.pattern = "test/**/*_test.rb"
8
+ end
9
+
10
+ task :default => :test
11
+
@@ -0,0 +1,9 @@
1
+ require "mountable_file_server/version"
2
+
3
+ module MountableFileServer
4
+ require "mountable_file_server/configuration"
5
+ require "mountable_file_server/storage"
6
+ require "mountable_file_server/endpoint"
7
+ require "mountable_file_server/identifier"
8
+ require "mountable_file_server/access"
9
+ end
@@ -0,0 +1,44 @@
1
+ require 'url_safe_base64'
2
+
3
+ module MountableFileServer
4
+ class Access
5
+ NotAccessibleViaURL = Class.new(ArgumentError)
6
+
7
+ attr_reader :configuration
8
+
9
+ def initialize(configuration)
10
+ @configuration = configuration
11
+ end
12
+
13
+ def temporary_path_for(identifier:)
14
+ File.join configuration.stored_at, 'tmp', identifier
15
+ end
16
+
17
+ def path_for(identifier:)
18
+ identifier = Identifier.new identifier
19
+ File.join configuration.stored_at, identifier.type, identifier.filename
20
+ end
21
+
22
+ def url_for(identifier:, resize: nil)
23
+ identifier = Identifier.new identifier
24
+
25
+ raise NotAccessibleViaURL unless identifier.public?
26
+
27
+ if resize
28
+ resize_part = UrlSafeBase64.encode64 resize
29
+ File.join configuration.mounted_at, identifier.filename.gsub('.', ".#{resize_part}.")
30
+ else
31
+ File.join configuration.mounted_at, identifier.filename
32
+ end
33
+ end
34
+
35
+ def file_for(identifier:)
36
+ paths = [
37
+ path_for(identifier: identifier),
38
+ temporary_path_for(identifier: identifier)
39
+ ]
40
+
41
+ File.new paths.find { |path| File.file? path }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,7 @@
1
+ # module MountableFileServer
2
+ # module AccessHelper
3
+ # def path_for_upload(filename)
4
+ # File.join(MountableFileServer.configuration.mounted_at, filename)
5
+ # end
6
+ # end
7
+ # end
@@ -0,0 +1,18 @@
1
+ # module MountableFileServer
2
+ # module AjaxUploadHelper
3
+ # module FormBuilder
4
+ # def ajax_upload_field(method, options = {})
5
+ # @template.ajax_upload_field(@object_name, method, objectify_options(options))
6
+ # end
7
+ # end
8
+
9
+ # def ajax_upload_field(object_name, method, options = {})
10
+ # file_input = file_field(object_name, method, options.merge({ data: { 'upload-url' => MountableFileServer.public_upload_url } }))
11
+ # hidden_input = hidden_field(object_name, method, options)
12
+ # content_tag(:div, class: MountableFileServer.configuration.input_class) do
13
+ # concat file_input
14
+ # concat hidden_input
15
+ # end
16
+ # end
17
+ # end
18
+ # end
@@ -0,0 +1,10 @@
1
+ module MountableFileServer
2
+ class Configuration
3
+ attr_reader :mounted_at, :stored_at
4
+
5
+ def initialize(mounted_at:, stored_at:)
6
+ @mounted_at = mounted_at
7
+ @stored_at = stored_at
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,55 @@
1
+ require 'sinatra'
2
+ require 'url_safe_base64'
3
+ require 'mini_magick'
4
+
5
+ module MountableFileServer
6
+ class Endpoint < Sinatra::Base
7
+ attr_reader :configuration
8
+
9
+ def initialize(configuration)
10
+ @configuration = configuration
11
+ super
12
+ end
13
+
14
+ post '/' do
15
+ path = params[:file][:tempfile].path
16
+ filename = params[:file][:filename]
17
+ type = params[:type]
18
+ storage = Storage.new configuration
19
+
20
+ storage.store_temporary(path: path, type: type, filename: filename).to_str
21
+ end
22
+
23
+ get '/*' do
24
+ deliver_file unescape(request.path_info)
25
+ end
26
+
27
+ def deliver_file(filename)
28
+ path_to_file = File.join(configuration.stored_at, 'public', filename)
29
+
30
+ if File.file?(path_to_file)
31
+ send_file path_to_file
32
+ else
33
+ if filename.count('.') == 2
34
+ parts = filename.split('.')
35
+ base_filename = "#{parts[0]}.#{parts[2]}"
36
+ path_to_base_file = File.join(configuration.stored_at, 'public', base_filename)
37
+
38
+ if File.file?(path_to_base_file)
39
+ resize_command = UrlSafeBase64.decode64 parts[1]
40
+ image = MiniMagick::Image.open path_to_base_file
41
+ image.resize resize_command.to_s
42
+ image.write path_to_file
43
+ send_file path_to_file
44
+ else
45
+ not_found
46
+ end
47
+ else
48
+ not_found
49
+ end
50
+
51
+ not_found
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ require 'securerandom'
2
+
3
+ module MountableFileServer
4
+ class Identifier
5
+ attr_reader :type, :filename
6
+
7
+ def self.generate_for(filename:, type:)
8
+ extension = File.extname filename
9
+
10
+ new "#{type}-#{SecureRandom.hex}#{extension}"
11
+ end
12
+
13
+ def initialize(identifier)
14
+ @type = /(\w+)-\w+/.match(identifier)[1]
15
+ @filename = /\w+-(.+)$/.match(identifier)[1]
16
+
17
+ raise ArgumentError.new("Unkown type `#{type}`") unless valid_type?
18
+ end
19
+
20
+ def to_str
21
+ "#{type}-#{filename}"
22
+ end
23
+
24
+ def ==(other)
25
+ to_str == other.to_str
26
+ end
27
+
28
+ def public?
29
+ type == 'public'
30
+ end
31
+
32
+ private
33
+ def valid_type?
34
+ ['public', 'private'].include?(type)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ require 'mountable_file_server'
2
+
3
+ module MountableFileServer
4
+ class Engine < Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,52 @@
1
+ module MountableFileServer
2
+ class Storage
3
+ attr_reader :access
4
+
5
+ def initialize(configuration)
6
+ @access = Access.new configuration
7
+ end
8
+
9
+ def store_temporary(path:, type:, filename:)
10
+ file_to_be_stored = File.new path
11
+ identifier = random_identifier filename: filename, type: type
12
+ path = access.temporary_path_for identifier: identifier
13
+
14
+ File.open(path, 'wb') do |file|
15
+ file.write file_to_be_stored.read
16
+ end
17
+
18
+ identifier
19
+ end
20
+
21
+ def store_permanently(path:, type:, filename:)
22
+ identifier = store_temporary path: path, type: type, filename: filename
23
+ move_to_permanent_storage identifier: identifier
24
+ identifier
25
+ end
26
+
27
+ def move_to_permanent_storage(identifier:)
28
+ identifier = Identifier.new identifier
29
+ from = access.temporary_path_for identifier: identifier
30
+ to = access.path_for identifier: identifier
31
+
32
+ FileUtils.move from, to
33
+ end
34
+
35
+ def remove_from_permanent_storage(identifier:)
36
+ identifier = Identifier.new identifier
37
+ path = access.path_for identifier: identifier
38
+
39
+ FileUtils.rm path
40
+ end
41
+
42
+ private
43
+ def random_identifier(filename:, type:)
44
+ loop do
45
+ identifier = Identifier.generate_for filename: filename, type: type
46
+ path = access.temporary_path_for identifier: identifier
47
+
48
+ break identifier unless File.exists?(path)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module MountableFileServer
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mountable_file_server/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mountable_file_server"
8
+ spec.version = MountableFileServer::VERSION
9
+ spec.authors = ["David Strauß"]
10
+ spec.email = ["david@strauss.io"]
11
+ spec.summary = %q{Simple mountable server that handles file uploads}
12
+ spec.description = %q{}
13
+ spec.homepage = "https://github.com/stravid/mountable_file_server"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "minitest"
24
+ spec.add_development_dependency "rails"
25
+ spec.add_development_dependency "capybara"
26
+ spec.add_development_dependency "sqlite3"
27
+ spec.add_development_dependency "poltergeist"
28
+ spec.add_development_dependency "rack-test"
29
+ spec.add_development_dependency "bogus"
30
+
31
+ spec.add_runtime_dependency "sinatra"
32
+ spec.add_runtime_dependency "mini_magick"
33
+ spec.add_runtime_dependency "url_safe_base64"
34
+ end
@@ -0,0 +1,59 @@
1
+ (function() {
2
+ var lastUploadId = 0;
3
+ var finishedUploads = [];
4
+
5
+ var dispatchEvent = function($element, name, payload) {
6
+ var event = document.createEvent('CustomEvent');
7
+
8
+ event.initCustomEvent(name, true, false, payload);
9
+ $element.dispatchEvent(event);
10
+ };
11
+
12
+ var uploadFile = function($element, file, uploadId) {
13
+ var url = $element.getAttribute('data-endpoint');
14
+ var type = $element.getAttribute('data-type');
15
+ var xhr = new XMLHttpRequest();
16
+ var formData = new FormData();
17
+
18
+ formData.append('file', file);
19
+ formData.append('type', type);
20
+
21
+ xhr.open('POST', url, true);
22
+
23
+ xhr.onreadystatechange = function() {
24
+ if (xhr.readyState === 4 && xhr.status === 200) {
25
+ finishedUploads.push(uploadId);
26
+
27
+ dispatchEvent($element, 'upload:success', {
28
+ uploadId: uploadId,
29
+ identifier: xhr.responseText,
30
+ wasLastUpload: lastUploadId == finishedUploads.length
31
+ });
32
+ }
33
+ }
34
+
35
+ xhr.upload.addEventListener('progress', function(progressEvent) {
36
+ if (progressEvent.lengthComputable) {
37
+ dispatchEvent($element, 'upload:progress', {
38
+ uploadId: uploadId,
39
+ progress: progressEvent
40
+ });
41
+ }
42
+ });
43
+
44
+ xhr.send(formData);
45
+
46
+ dispatchEvent($element, 'upload:start', {
47
+ uploadId: uploadId,
48
+ file: file
49
+ });
50
+ };
51
+
52
+ var internalUploadFiles = function($element, files) {
53
+ for (var i = 0; i < files.length; i++) {
54
+ uploadFile($element, files[i], ++lastUploadId);
55
+ }
56
+ };
57
+
58
+ window.uploadFiles = internalUploadFiles;
59
+ })();
metadata ADDED
@@ -0,0 +1,230 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mountable_file_server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - David Strauß
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: capybara
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: poltergeist
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rack-test
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: bogus
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: sinatra
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: mini_magick
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: url_safe_base64
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ description: ''
182
+ email:
183
+ - david@strauss.io
184
+ executables: []
185
+ extensions: []
186
+ extra_rdoc_files: []
187
+ files:
188
+ - ".gitignore"
189
+ - ".travis.yml"
190
+ - Gemfile
191
+ - LICENSE.txt
192
+ - README.md
193
+ - Rakefile
194
+ - lib/mountable_file_server.rb
195
+ - lib/mountable_file_server/access.rb
196
+ - lib/mountable_file_server/access_helper.rb
197
+ - lib/mountable_file_server/ajax_upload_helper.rb
198
+ - lib/mountable_file_server/configuration.rb
199
+ - lib/mountable_file_server/endpoint.rb
200
+ - lib/mountable_file_server/identifier.rb
201
+ - lib/mountable_file_server/rails.rb
202
+ - lib/mountable_file_server/storage.rb
203
+ - lib/mountable_file_server/version.rb
204
+ - mountable_file_server.gemspec
205
+ - vendor/assets/javascripts/mountable_file_server.js
206
+ homepage: https://github.com/stravid/mountable_file_server
207
+ licenses:
208
+ - MIT
209
+ metadata: {}
210
+ post_install_message:
211
+ rdoc_options: []
212
+ require_paths:
213
+ - lib
214
+ required_ruby_version: !ruby/object:Gem::Requirement
215
+ requirements:
216
+ - - ">="
217
+ - !ruby/object:Gem::Version
218
+ version: '0'
219
+ required_rubygems_version: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ requirements: []
225
+ rubyforge_project:
226
+ rubygems_version: 2.4.5
227
+ signing_key:
228
+ specification_version: 4
229
+ summary: Simple mountable server that handles file uploads
230
+ test_files: []