browse-everything 1.0.0.rc2 → 1.1.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +93 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +43 -35
  5. data/CONTRIBUTING.md +3 -3
  6. data/Gemfile +3 -6
  7. data/LICENSE.txt +205 -22
  8. data/README.md +17 -15
  9. data/app/assets/javascripts/browse_everything/behavior.js +201 -158
  10. data/app/assets/javascripts/treetable.webpack.js +687 -0
  11. data/app/assets/stylesheets/browse_everything.scss +0 -0
  12. data/app/assets/stylesheets/{_browse_everything_bootstrap3.scss → browse_everything/_browse_everything_bootstrap3.scss} +6 -5
  13. data/app/assets/stylesheets/{_browse_everything_bootstrap4.scss → browse_everything/_browse_everything_bootstrap4.scss} +0 -0
  14. data/app/controllers/browse_everything_controller.rb +60 -60
  15. data/app/helpers/browse_everything_helper.rb +4 -0
  16. data/app/views/browse_everything/_files.html.erb +4 -2
  17. data/browse-everything.gemspec +8 -7
  18. data/lib/browse_everything.rb +2 -1
  19. data/lib/browse_everything/auth/google/credentials.rb +5 -5
  20. data/lib/browse_everything/auth/google/request_parameters.rb +38 -38
  21. data/lib/browse_everything/driver/base.rb +14 -14
  22. data/lib/browse_everything/driver/box.rb +62 -55
  23. data/lib/browse_everything/driver/dropbox.rb +37 -21
  24. data/lib/browse_everything/driver/file_system.rb +30 -18
  25. data/lib/browse_everything/driver/google_drive.rb +40 -39
  26. data/lib/browse_everything/driver/s3.rb +61 -45
  27. data/lib/browse_everything/file_entry.rb +1 -1
  28. data/lib/browse_everything/retriever.rb +72 -67
  29. data/lib/browse_everything/version.rb +1 -1
  30. data/lib/generators/browse_everything/templates/browse_everything_providers.yml.example +1 -0
  31. data/spec/features/test_compiling_stylesheets_spec.rb +1 -1
  32. data/spec/lib/browse_everything/browser_spec.rb +5 -3
  33. data/spec/lib/browse_everything/driver/dropbox_spec.rb +20 -1
  34. data/spec/lib/browse_everything/driver_spec.rb +43 -3
  35. data/spec/spec_helper.rb +9 -2
  36. data/spec/support/capybara.rb +0 -5
  37. data/spec/test_app_templates/Gemfile.extra +9 -0
  38. data/spec/test_app_templates/lib/generators/test_app_generator.rb +56 -5
  39. metadata +72 -39
  40. data/.travis.yml +0 -33
@@ -83,7 +83,8 @@ module BrowseEverything
83
83
  def contents(path = '')
84
84
  path = '/' + path unless path == ''
85
85
  response = client.list_folder(path)
86
- @entries = response.entries.map { |entry| FileEntryFactory.build(metadata: entry, key: key) }
86
+ values = response.entries.map { |entry| FileEntryFactory.build(metadata: entry, key: key) }
87
+ @entries = values.compact
87
88
  @sorter.call(@entries)
88
89
  end
89
90
 
@@ -91,7 +92,7 @@ module BrowseEverything
91
92
  return @downloaded_files[path] if @downloaded_files.key?(path)
92
93
 
93
94
  # This ensures that the name of the file its extension are preserved for user downloads
94
- temp_file_path = File.join(Dir.mktmpdir, File.basename(path))
95
+ temp_file_path = File.join(download_directory_path, File.basename(path))
95
96
  temp_file = File.open(temp_file_path, mode: 'w+', encoding: 'ascii-8bit')
96
97
  client.download(path) do |chunk|
97
98
  temp_file.write chunk
@@ -137,29 +138,44 @@ module BrowseEverything
137
138
 
138
139
  private
139
140
 
140
- def session
141
- AuthenticationFactory.new(
142
- self.class.authentication_klass,
143
- config[:client_id],
144
- config[:client_secret]
145
- )
146
- end
141
+ def session
142
+ AuthenticationFactory.new(
143
+ self.class.authentication_klass,
144
+ config[:client_id],
145
+ config[:client_secret]
146
+ )
147
+ end
147
148
 
148
- def authenticate
149
- session.authenticate
150
- end
149
+ def authenticate
150
+ session.authenticate
151
+ end
151
152
 
152
- def authenticator
153
- @authenticator ||= authenticate
154
- end
153
+ def authenticator
154
+ @authenticator ||= authenticate
155
+ end
155
156
 
156
- def client
157
- DropboxApi::Client.new(token)
158
- end
157
+ def client
158
+ DropboxApi::Client.new(token)
159
+ end
159
160
 
160
- def redirect_uri(url_options)
161
- connector_response_url(**url_options)
162
- end
161
+ def redirect_uri(url_options)
162
+ connector_response_url(**url_options)
163
+ end
164
+
165
+ # Ensures that the "tmp" directory is used if there is no default download
166
+ # directory specified in the configuration
167
+ # @return [String]
168
+ def default_download_directory
169
+ Rails.root.join('tmp')
170
+ end
171
+
172
+ # Retrieves the directory path for downloads used when retrieving the
173
+ # resource from Dropbox
174
+ # @return [String]
175
+ def download_directory_path
176
+ dir_path = config[:download_directory] || default_download_directory
177
+ File.expand_path(dir_path)
178
+ end
163
179
  end
164
180
  end
165
181
  end
@@ -11,13 +11,17 @@ module BrowseEverything
11
11
  raise BrowseEverything::InitializationError, 'FileSystem driver requires a :home argument' if config[:home].blank?
12
12
  end
13
13
 
14
+ # Retrieve the contents of a directory
15
+ # @param path [String] the path to a file system resource
16
+ # @return [Array<BrowseEverything::FileEntry>]
14
17
  def contents(path = '')
15
18
  real_path = File.join(config[:home], path)
16
- @entries = if File.directory?(real_path)
17
- make_directory_entry real_path
18
- else
19
- [details(real_path)]
20
- end
19
+ values = if File.directory?(real_path)
20
+ make_directory_entry real_path
21
+ else
22
+ [details(real_path)]
23
+ end
24
+ @entries = values.compact
21
25
 
22
26
  @sorter.call(@entries)
23
27
  end
@@ -32,6 +36,10 @@ module BrowseEverything
32
36
  true
33
37
  end
34
38
 
39
+ # Construct a FileEntry objects for a file-system resource
40
+ # @param path [String] path to the file
41
+ # @param display [String] display label for the resource
42
+ # @return [BrowseEverything::FileEntry]
35
43
  def details(path, display = File.basename(path))
36
44
  return nil unless File.exist? path
37
45
  info = File::Stat.new(path)
@@ -47,21 +55,25 @@ module BrowseEverything
47
55
 
48
56
  private
49
57
 
50
- def make_directory_entry(real_path)
51
- entries = []
52
- entries + Dir[File.join(real_path, '*')].collect { |f| details(f) }
53
- end
58
+ # Construct an array of FileEntry objects for the contents of a
59
+ # directory
60
+ # @param real_path [String] path to the file system directory
61
+ # @return [Array<BrowseEverything::FileEntry>]
62
+ def make_directory_entry(real_path)
63
+ entries = []
64
+ entries + Dir[File.join(real_path, '*')].collect { |f| details(f) }
65
+ end
54
66
 
55
- def make_pathname(path)
56
- Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(config[:home]))
57
- end
67
+ def make_pathname(path)
68
+ Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(config[:home]))
69
+ end
58
70
 
59
- def file_size(path)
60
- File.size(path).to_i
61
- rescue StandardError => error
62
- Rails.logger.error "Failed to find the file size for #{path}: #{error}"
63
- 0
64
- end
71
+ def file_size(path)
72
+ File.size(path).to_i
73
+ rescue StandardError => error
74
+ Rails.logger.error "Failed to find the file size for #{path}: #{error}"
75
+ 0
76
+ end
65
77
  end
66
78
  end
67
79
  end
@@ -79,9 +79,10 @@ module BrowseEverything
79
79
  raise error
80
80
  end
81
81
 
82
- @entries += file_list.files.map do |gdrive_file|
82
+ values = file_list.files.map do |gdrive_file|
83
83
  details(gdrive_file, path)
84
84
  end
85
+ @entries += values.compact
85
86
 
86
87
  request_params.page_token = file_list.next_page_token
87
88
  end
@@ -190,50 +191,50 @@ module BrowseEverything
190
191
 
191
192
  private
192
193
 
193
- def client_secrets
194
- {
195
- Google::Auth::ClientId::WEB_APP => {
196
- Google::Auth::ClientId::CLIENT_ID => config[:client_id],
197
- Google::Auth::ClientId::CLIENT_SECRET => config[:client_secret]
198
- }
194
+ def client_secrets
195
+ {
196
+ Google::Auth::ClientId::WEB_APP => {
197
+ Google::Auth::ClientId::CLIENT_ID => config[:client_id],
198
+ Google::Auth::ClientId::CLIENT_SECRET => config[:client_secret]
199
199
  }
200
- end
200
+ }
201
+ end
201
202
 
202
- # This is required for using the googleauth Gem
203
- # @see http://www.rubydoc.info/gems/googleauth/Google/Auth/Stores/FileTokenStore FileTokenStore for googleauth
204
- # @return [Tempfile] temporary file within which to cache credentials
205
- def file_token_store_path
206
- Tempfile.new('gdrive.yaml')
207
- end
203
+ # This is required for using the googleauth Gem
204
+ # @see http://www.rubydoc.info/gems/googleauth/Google/Auth/Stores/FileTokenStore FileTokenStore for googleauth
205
+ # @return [Tempfile] temporary file within which to cache credentials
206
+ def file_token_store_path
207
+ Tempfile.new('gdrive.yaml')
208
+ end
208
209
 
209
- def scope
210
- Google::Apis::DriveV3::AUTH_DRIVE
211
- end
210
+ def scope
211
+ Google::Apis::DriveV3::AUTH_DRIVE
212
+ end
212
213
 
213
- # Provides the user ID for caching access tokens
214
- # (This is a hack which attempts to anonymize the access tokens)
215
- # @return [String] the ID for the user
216
- def user_id
217
- 'current_user'
218
- end
214
+ # Provides the user ID for caching access tokens
215
+ # (This is a hack which attempts to anonymize the access tokens)
216
+ # @return [String] the ID for the user
217
+ def user_id
218
+ 'current_user'
219
+ end
219
220
 
220
- # Please see https://developers.google.com/drive/v3/web/manage-downloads
221
- # @param id [String] the ID for the Google Drive File
222
- # @return [String] the URL for the file download
223
- def download_url(id)
224
- "https://www.googleapis.com/drive/v3/files/#{id}?alt=media"
225
- end
221
+ # Please see https://developers.google.com/drive/v3/web/manage-downloads
222
+ # @param id [String] the ID for the Google Drive File
223
+ # @return [String] the URL for the file download
224
+ def download_url(id)
225
+ "https://www.googleapis.com/drive/v3/files/#{id}?alt=media"
226
+ end
226
227
 
227
- # Restore the credentials for the Google API
228
- # @param access_token [String] the access token redeemed using an authorization code
229
- # @return Credentials credentials restored from a cached access token
230
- def restore_credentials(access_token)
231
- client = Auth::Google::Credentials.new
232
- client.client_id = client_id.id
233
- client.client_secret = client_id.secret
234
- client.update_token!('access_token' => access_token)
235
- @credentials = client
236
- end
228
+ # Restore the credentials for the Google API
229
+ # @param access_token [String] the access token redeemed using an authorization code
230
+ # @return Credentials credentials restored from a cached access token
231
+ def restore_credentials(access_token)
232
+ client = Auth::Google::Credentials.new
233
+ client.client_id = client_id.id
234
+ client.client_secret = client_id.secret
235
+ client.update_token!('access_token' => access_token)
236
+ @credentials = client
237
+ end
237
238
  end
238
239
  end
239
240
  end
@@ -79,63 +79,79 @@ module BrowseEverything
79
79
 
80
80
  private
81
81
 
82
- def strip(path)
83
- path.sub %r{^/?(.+?)/?$}, '\1'
84
- end
82
+ def strip(path)
83
+ path.sub %r{^/?(.+?)/?$}, '\1'
84
+ end
85
85
 
86
- def from_base(key)
87
- Pathname.new(key).relative_path_from(Pathname.new(config[:base].to_s)).to_s
88
- end
86
+ def from_base(key)
87
+ Pathname.new(key).relative_path_from(Pathname.new(config[:base].to_s)).to_s
88
+ end
89
89
 
90
- def full_path(path)
91
- config[:base].present? ? File.join(config[:base], path) : path
92
- end
90
+ def full_path(path)
91
+ config[:base].present? ? File.join(config[:base], path) : path
92
+ end
93
93
 
94
- def aws_config
95
- result = {}
96
- result[:credentials] = Aws::Credentials.new(config[:app_key], config[:app_secret]) if config[:app_key].present?
97
- result[:region] = config[:region] if config.key?(:region)
98
- result
99
- end
94
+ def aws_config
95
+ result = {}
96
+ result[:credentials] = Aws::Credentials.new(config[:app_key], config[:app_secret]) if config[:app_key].present?
97
+ result[:region] = config[:region] if config.key?(:region)
98
+ result
99
+ end
100
100
 
101
- def session
102
- AuthenticationFactory.new(
103
- self.class.authentication_klass,
104
- aws_config
105
- )
106
- end
101
+ def session
102
+ AuthenticationFactory.new(
103
+ self.class.authentication_klass,
104
+ aws_config
105
+ )
106
+ end
107
107
 
108
- def authenticate
109
- session.authenticate
110
- end
108
+ def authenticate
109
+ session.authenticate
110
+ end
111
111
 
112
- def client
113
- @client ||= authenticate
114
- end
112
+ def client
113
+ @client ||= authenticate
114
+ end
115
115
 
116
- def entry_for(name, size, date, dir)
117
- BrowseEverything::FileEntry.new(name, [key, name].join(':'), File.basename(name), size, date, dir)
118
- end
116
+ # Construct a BrowseEverything::FileEntry object
117
+ # @param name [String]
118
+ # @param size [String]
119
+ # @param date [DateTime]
120
+ # @param dir [String]
121
+ # @return [BrowseEverything::FileEntry]
122
+ def entry_for(name, size, date, dir)
123
+ BrowseEverything::FileEntry.new(name, [key, name].join(':'), File.basename(name), size, date, dir)
124
+ end
119
125
 
120
- def add_directories(listing)
121
- listing.common_prefixes.each do |prefix|
122
- @entries << entry_for(from_base(prefix.prefix), 0, Time.current, true)
123
- end
126
+ # Populate the entries with FileEntry objects from an S3 listing
127
+ # @param listing [Seahorse::Client::Response]
128
+ def add_directories(listing)
129
+ listing.common_prefixes.each do |prefix|
130
+ new_entry = entry_for(from_base(prefix.prefix), 0, Time.current, true)
131
+ @entries << new_entry unless new_entry.nil?
124
132
  end
133
+ end
125
134
 
126
- def add_files(listing, path)
127
- listing.contents.each do |entry|
128
- key = from_base(entry.key)
129
- @entries << entry_for(key, entry.size, entry.last_modified, false) unless strip(key) == strip(path)
130
- end
135
+ # Given a listing and a S3 listing and path, populate the entries
136
+ # @param listing [Seahorse::Client::Response]
137
+ # @param path [String]
138
+ def add_files(listing, path)
139
+ listing.contents.each do |entry|
140
+ key = from_base(entry.key)
141
+ new_entry = entry_for(key, entry.size, entry.last_modified, false)
142
+ @entries << new_entry unless strip(key) == strip(path) || new_entry.nil?
131
143
  end
144
+ end
132
145
 
133
- def generate_listing(path)
134
- client
135
- listing = client.list_objects(bucket: config[:bucket], delimiter: '/', prefix: full_path(path))
136
- add_directories(listing)
137
- add_files(listing, path)
138
- end
146
+ # For a given path to a S3 resource, retrieve the listing object and
147
+ # construct the file entries
148
+ # @param path [String]
149
+ def generate_listing(path)
150
+ client
151
+ listing = client.list_objects(bucket: config[:bucket], delimiter: '/', prefix: full_path(path))
152
+ add_directories(listing)
153
+ add_files(listing, path)
154
+ end
139
155
  end
140
156
  end
141
157
  end
@@ -15,7 +15,7 @@ module BrowseEverything
15
15
  end
16
16
 
17
17
  def relative_parent_path?
18
- name =~ /^\.\.?$/ ? true : false
18
+ name.match?(/^\.\.?$/)
19
19
  end
20
20
 
21
21
  def container?
@@ -31,8 +31,13 @@ module BrowseEverything
31
31
  attr_accessor :chunk_size
32
32
 
33
33
  class << self
34
- def can_retrieve?(uri)
35
- Typhoeus.get(uri, headers: { Range: 'bytes=0-0' }).success?
34
+ # Determines whether or not a remote resource can be retrieved
35
+ # @param uri [String] URI for the remote resource (usually a URL)
36
+ # @param headers [Hash] any custom headers required to transit the request
37
+ def can_retrieve?(uri, headers = {})
38
+ request_headers = headers.merge(Range: 'bytes=0-0')
39
+ response = Typhoeus.get(uri, headers: request_headers)
40
+ response.success?
36
41
  end
37
42
  end
38
43
 
@@ -84,81 +89,81 @@ module BrowseEverything
84
89
 
85
90
  private
86
91
 
87
- # Extract and parse options used to download a file or resource from an HTTP API
88
- # @param options [Hash]
89
- # @return [Hash]
90
- def extract_download_options(options)
91
- url = options.fetch('url')
92
+ # Extract and parse options used to download a file or resource from an HTTP API
93
+ # @param options [Hash]
94
+ # @return [Hash]
95
+ def extract_download_options(options)
96
+ url = options.fetch('url')
92
97
 
93
- # This avoids the potential for a KeyError
94
- headers = options.fetch('headers', {}) || {}
98
+ # This avoids the potential for a KeyError
99
+ headers = options.fetch('headers', {}) || {}
95
100
 
96
- file_size_value = options.fetch('file_size', 0)
97
- file_size = file_size_value.to_i
101
+ file_size_value = options.fetch('file_size', 0)
102
+ file_size = file_size_value.to_i
98
103
 
99
- output = {
100
- url: ::Addressable::URI.parse(url),
101
- headers: headers,
102
- file_size: file_size
103
- }
104
+ output = {
105
+ url: ::Addressable::URI.parse(url),
106
+ headers: headers,
107
+ file_size: file_size
108
+ }
104
109
 
105
- output[:file_size] = get_file_size(output) if output[:file_size] < 1
106
- output
107
- end
110
+ output[:file_size] = get_file_size(output) if output[:file_size] < 1
111
+ output
112
+ end
108
113
 
109
- # Retrieve the file from the file system
110
- # @param options [Hash]
111
- def retrieve_file(options)
112
- file_uri = options.fetch(:url)
113
- file_size = options.fetch(:file_size)
114
-
115
- retrieved = 0
116
- File.open(file_uri.path, 'rb') do |f|
117
- until f.eof?
118
- chunk = f.read(chunk_size)
119
- retrieved += chunk.length
120
- yield(chunk, retrieved, file_size)
121
- end
114
+ # Retrieve the file from the file system
115
+ # @param options [Hash]
116
+ def retrieve_file(options)
117
+ file_uri = options.fetch(:url)
118
+ file_size = options.fetch(:file_size)
119
+
120
+ retrieved = 0
121
+ File.open(file_uri.path, 'rb') do |f|
122
+ until f.eof?
123
+ chunk = f.read(chunk_size)
124
+ retrieved += chunk.length
125
+ yield(chunk, retrieved, file_size)
122
126
  end
123
127
  end
128
+ end
124
129
 
125
- # Retrieve a resource over the HTTP
126
- # @param options [Hash]
127
- def retrieve_http(options)
128
- file_size = options.fetch(:file_size)
129
- headers = options.fetch(:headers)
130
- url = options.fetch(:url)
131
- retrieved = 0
132
-
133
- request = Typhoeus::Request.new(url.to_s, method: :get, headers: headers)
134
- request.on_headers do |response|
135
- raise DownloadError.new("#{self.class}: Failed to download #{url}: Status Code: #{response.code}", response) unless response.code == 200
136
- end
137
- request.on_body do |chunk|
138
- retrieved += chunk.bytesize
139
- yield(chunk, retrieved, file_size)
140
- end
141
- request.run
130
+ # Retrieve a resource over the HTTP
131
+ # @param options [Hash]
132
+ def retrieve_http(options)
133
+ file_size = options.fetch(:file_size)
134
+ headers = options.fetch(:headers)
135
+ url = options.fetch(:url)
136
+ retrieved = 0
137
+
138
+ request = Typhoeus::Request.new(url.to_s, method: :get, headers: headers)
139
+ request.on_headers do |response|
140
+ raise DownloadError.new("#{self.class}: Failed to download #{url}: Status Code: #{response.code}", response) unless response.code == 200
141
+ end
142
+ request.on_body do |chunk|
143
+ retrieved += chunk.bytesize
144
+ yield(chunk, retrieved, file_size)
142
145
  end
146
+ request.run
147
+ end
143
148
 
144
- # Retrieve the file size
145
- # @param options [Hash]
146
- # @return [Integer] the size of the requested file
147
- def get_file_size(options)
148
- url = options.fetch(:url)
149
- headers = options.fetch(:headers)
150
- file_size = options.fetch(:file_size)
151
-
152
- case url.scheme
153
- when 'file'
154
- File.size(url.path)
155
- when /https?/
156
- response = Typhoeus.head(url.to_s, headers: headers)
157
- length_value = response.headers['Content-Length'] || file_size
158
- length_value.to_i
159
- else
160
- raise URI::BadURIError, "Unknown URI scheme: #{url.scheme}"
161
- end
149
+ # Retrieve the file size
150
+ # @param options [Hash]
151
+ # @return [Integer] the size of the requested file
152
+ def get_file_size(options)
153
+ url = options.fetch(:url)
154
+ headers = options.fetch(:headers)
155
+ file_size = options.fetch(:file_size)
156
+
157
+ case url.scheme
158
+ when 'file'
159
+ File.size(url.path)
160
+ when /https?/
161
+ response = Typhoeus.head(url.to_s, headers: headers)
162
+ length_value = response.headers['Content-Length'] || file_size
163
+ length_value.to_i
164
+ else
165
+ raise URI::BadURIError, "Unknown URI scheme: #{url.scheme}"
162
166
  end
167
+ end
163
168
  end
164
169
  end