browse-everything 1.0.0 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
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