browse-everything 1.0.0 → 1.1.2

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +82 -133
  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 +7 -6
  9. data/app/assets/javascripts/browse_everything/behavior.js +16 -8
  10. data/app/assets/javascripts/treetable.webpack.js +687 -0
  11. data/app/controllers/browse_everything_controller.rb +60 -60
  12. data/app/helpers/browse_everything_helper.rb +4 -0
  13. data/app/views/browse_everything/_files.html.erb +4 -2
  14. data/browse-everything.gemspec +6 -7
  15. data/lib/browse_everything.rb +2 -1
  16. data/lib/browse_everything/auth/google/credentials.rb +5 -5
  17. data/lib/browse_everything/auth/google/request_parameters.rb +38 -38
  18. data/lib/browse_everything/driver/base.rb +14 -14
  19. data/lib/browse_everything/driver/box.rb +62 -55
  20. data/lib/browse_everything/driver/dropbox.rb +34 -33
  21. data/lib/browse_everything/driver/file_system.rb +30 -18
  22. data/lib/browse_everything/driver/google_drive.rb +40 -39
  23. data/lib/browse_everything/driver/s3.rb +61 -45
  24. data/lib/browse_everything/file_entry.rb +1 -1
  25. data/lib/browse_everything/retriever.rb +69 -69
  26. data/lib/browse_everything/version.rb +1 -1
  27. data/spec/features/test_compiling_stylesheets_spec.rb +1 -1
  28. data/spec/lib/browse_everything/driver_spec.rb +43 -3
  29. data/spec/spec_helper.rb +9 -3
  30. data/spec/support/capybara.rb +0 -5
  31. data/spec/test_app_templates/Gemfile.extra +9 -0
  32. data/spec/test_app_templates/lib/generators/test_app_generator.rb +54 -3
  33. metadata +49 -45
  34. data/.travis.yml +0 -30
@@ -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
 
@@ -137,44 +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
163
164
 
164
- # Ensures that the "tmp" directory is used if there is no default download
165
- # directory specified in the configuration
166
- # @return [String]
167
- def default_download_directory
168
- Rails.root.join('tmp')
169
- end
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
170
171
 
171
- # Retrieves the directory path for downloads used when retrieving the
172
- # resource from Dropbox
173
- # @return [String]
174
- def download_directory_path
175
- dir_path = config[:download_directory] || default_download_directory
176
- File.expand_path(dir_path)
177
- end
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
178
179
  end
179
180
  end
180
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?
@@ -89,81 +89,81 @@ module BrowseEverything
89
89
 
90
90
  private
91
91
 
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')
97
-
98
- # This avoids the potential for a KeyError
99
- headers = options.fetch('headers', {}) || {}
100
-
101
- file_size_value = options.fetch('file_size', 0)
102
- file_size = file_size_value.to_i
103
-
104
- output = {
105
- url: ::Addressable::URI.parse(url),
106
- headers: headers,
107
- file_size: file_size
108
- }
109
-
110
- output[:file_size] = get_file_size(output) if output[:file_size] < 1
111
- output
112
- end
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')
113
97
 
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)
126
- end
127
- end
128
- end
98
+ # This avoids the potential for a KeyError
99
+ headers = options.fetch('headers', {}) || {}
129
100
 
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
101
+ file_size_value = options.fetch('file_size', 0)
102
+ file_size = file_size_value.to_i
103
+
104
+ output = {
105
+ url: ::Addressable::URI.parse(url),
106
+ headers: headers,
107
+ file_size: file_size
108
+ }
109
+
110
+ output[:file_size] = get_file_size(output) if output[:file_size] < 1
111
+ output
112
+ end
113
+
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
144
125
  yield(chunk, retrieved, file_size)
145
126
  end
146
- request.run
147
127
  end
128
+ end
148
129
 
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}"
166
- end
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
167
141
  end
142
+ request.on_body do |chunk|
143
+ retrieved += chunk.bytesize
144
+ yield(chunk, retrieved, file_size)
145
+ end
146
+ request.run
147
+ end
148
+
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}"
166
+ end
167
+ end
168
168
  end
169
169
  end