browse-everything 0.15.1 → 0.16.0

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 (76) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +61 -9
  3. data/.rubocop_todo.yml +2 -15
  4. data/.travis.yml +19 -19
  5. data/CONTRIBUTING.md +6 -6
  6. data/Gemfile +12 -8
  7. data/README.md +30 -0
  8. data/Rakefile +2 -1
  9. data/app/assets/javascripts/browse_everything/behavior.js.coffee +5 -0
  10. data/app/controllers/browse_everything_controller.rb +75 -23
  11. data/app/helpers/browse_everything_helper.rb +2 -8
  12. data/app/helpers/font_awesome_version_helper.rb +9 -8
  13. data/app/services/browse_everything_session.rb +10 -0
  14. data/app/services/browse_everything_session/provider_session.rb +42 -0
  15. data/app/services/browser_factory.rb +25 -0
  16. data/app/views/browse_everything/_files.html.erb +56 -6
  17. data/browse-everything.gemspec +29 -25
  18. data/config/routes.rb +7 -2
  19. data/lib/browse-everything.rb +2 -0
  20. data/lib/browse_everything.rb +45 -12
  21. data/lib/browse_everything/auth/google/credentials.rb +28 -0
  22. data/lib/browse_everything/auth/google/request_parameters.rb +61 -0
  23. data/lib/browse_everything/browser.rb +11 -4
  24. data/lib/browse_everything/driver/authentication_factory.rb +22 -0
  25. data/lib/browse_everything/driver/base.rb +72 -19
  26. data/lib/browse_everything/driver/box.rb +46 -17
  27. data/lib/browse_everything/driver/dropbox.rb +36 -10
  28. data/lib/browse_everything/driver/file_system.rb +14 -26
  29. data/lib/browse_everything/driver/google_drive.rb +187 -54
  30. data/lib/browse_everything/driver/s3.rb +81 -75
  31. data/lib/browse_everything/engine.rb +3 -2
  32. data/lib/browse_everything/file_entry.rb +3 -1
  33. data/lib/browse_everything/retriever.rb +103 -31
  34. data/lib/browse_everything/version.rb +3 -1
  35. data/lib/generators/browse_everything/assets_generator.rb +3 -2
  36. data/lib/generators/browse_everything/config_generator.rb +11 -9
  37. data/lib/generators/browse_everything/install_generator.rb +3 -2
  38. data/lib/generators/browse_everything/templates/browse_everything_providers.yml.example +12 -11
  39. data/spec/controllers/browse_everything_controller_spec.rb +80 -0
  40. data/spec/features/select_files_spec.rb +13 -13
  41. data/spec/features/test_compiling_stylesheets_spec.rb +2 -0
  42. data/spec/fixtures/vcr_cassettes/google_drive.yml +331 -0
  43. data/spec/fixtures/vcr_cassettes/retriever.yml +93 -0
  44. data/spec/helper/browse_everything_controller_helper_spec.rb +21 -7
  45. data/spec/javascripts/jasmine_spec.rb +2 -0
  46. data/spec/javascripts/support/jasmine_helper.rb +1 -0
  47. data/spec/lib/browse_everything/auth/google/credentials_spec.rb +41 -0
  48. data/spec/{unit → lib/browse_everything}/browse_everything_helper_spec.rb +2 -0
  49. data/spec/lib/browse_everything/browser_spec.rb +109 -0
  50. data/spec/{unit → lib/browse_everything/driver}/base_spec.rb +5 -4
  51. data/spec/{unit → lib/browse_everything/driver}/box_spec.rb +20 -5
  52. data/spec/{unit → lib/browse_everything/driver}/dropbox_spec.rb +15 -18
  53. data/spec/{unit → lib/browse_everything/driver}/file_system_spec.rb +32 -26
  54. data/spec/lib/browse_everything/driver/google_drive_spec.rb +171 -0
  55. data/spec/{unit → lib/browse_everything/driver}/s3_spec.rb +38 -21
  56. data/spec/lib/browse_everything/driver_spec.rb +38 -0
  57. data/spec/{unit → lib/browse_everything}/file_entry_spec.rb +4 -1
  58. data/spec/lib/browse_everything/retriever_spec.rb +200 -0
  59. data/spec/lib/browse_everything_spec.rb +67 -0
  60. data/spec/services/browse_everything_session/provider_session_spec.rb +50 -0
  61. data/spec/services/browser_factory_spec.rb +40 -0
  62. data/spec/spec_helper.rb +39 -18
  63. data/spec/support/app/controllers/file_handler_controller.rb +4 -4
  64. data/spec/support/app/views/file_handler/main.html.erb +1 -1
  65. data/spec/support/capybara.rb +17 -0
  66. data/spec/support/rake.rb +3 -1
  67. data/spec/support/wait_for_ajax.rb +14 -0
  68. data/spec/test_app_templates/Gemfile.extra +1 -0
  69. data/spec/test_app_templates/lib/generators/test_app_generator.rb +10 -4
  70. data/spec/views/browse_everything/{_file.html.erb_spec.rb → _files.html.erb_spec.rb} +24 -18
  71. data/tasks/ci.rake +2 -0
  72. metadata +159 -107
  73. data/app/views/browse_everything/_file.html.erb +0 -52
  74. data/app/views/browse_everything/resolve.html.erb +0 -1
  75. data/spec/unit/browser_spec.rb +0 -76
  76. data/spec/unit/retriever_spec.rb +0 -109
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module BrowseEverything
2
4
  class Browser
3
5
  attr_reader :providers
@@ -11,15 +13,20 @@ module BrowseEverything
11
13
  opts = BrowseEverything.config
12
14
  end
13
15
 
14
- @providers = {}
15
- opts.each_pair do |driver, config|
16
+ @providers = ActiveSupport::HashWithIndifferentAccess.new
17
+ opts.each_pair do |driver_key, config|
16
18
  begin
17
- driver_klass = BrowseEverything::Driver.const_get((config[:driver] || driver.to_s).camelize.to_sym)
18
- @providers[driver] = driver_klass.new(config.merge(url_options: url_options))
19
+ driver = driver_key.to_s
20
+ driver_klass = BrowseEverything::Driver.const_get((config[:driver] || driver).camelize.to_sym)
21
+ @providers[driver_key] = driver_klass.new(config.merge(url_options: url_options))
19
22
  rescue NameError
20
23
  Rails.logger.warn "Unknown provider: #{driver}"
21
24
  end
22
25
  end
23
26
  end
27
+
28
+ def first_provider
29
+ @providers.to_hash.each_value.to_a.first
30
+ end
24
31
  end
25
32
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrowseEverything
4
+ module Driver
5
+ # Class for instantiating authentication API Objects
6
+ class AuthenticationFactory
7
+ # Constructor
8
+ # @param klass [Class] the authentication object class
9
+ # @param params [Array, Hash] the parameters for the authentication constructor
10
+ def initialize(klass, *params)
11
+ @klass = klass
12
+ @params = params
13
+ end
14
+
15
+ # Constructs an authentication Object
16
+ # @return [Object]
17
+ def authenticate
18
+ @klass.new(*@params)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,66 +1,119 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module BrowseEverything
2
4
  module Driver
5
+ # Abstract class for provider classes
3
6
  class Base
4
7
  include BrowseEverything::Engine.routes.url_helpers
5
8
 
6
- attr_reader :config, :name
7
- attr_accessor :token
9
+ # Provide accessor and mutator methods for @token and @code
10
+ attr_accessor :token, :code
11
+
12
+ # Integrate sorting lambdas for configuration using initializers
13
+ class << self
14
+ attr_accessor :sorter
15
+
16
+ # Provide a default sorting lambda
17
+ # @return [Proc]
18
+ def default_sorter
19
+ lambda { |files|
20
+ files.sort do |a, b|
21
+ if b.container?
22
+ a.container? ? a.name.downcase <=> b.name.downcase : 1
23
+ else
24
+ a.container? ? -1 : a.name.downcase <=> b.name.downcase
25
+ end
26
+ end
27
+ }
28
+ end
29
+
30
+ # Set the sorter lambda (or proc) for all subclasses
31
+ # (see Class.inherited)
32
+ # @param subclass [Class] the class inheriting from BrowseEverything::Driver::Base
33
+ def inherited(subclass)
34
+ subclass.sorter = sorter
35
+ end
36
+ end
8
37
 
9
- def initialize(config, _session_info = {})
10
- @config = config
38
+ # Constructor
39
+ # @param config_values [Hash] configuration for the driver
40
+ def initialize(config_values)
41
+ @config = config_values
42
+ @sorter = self.class.sorter || self.class.default_sorter
11
43
  validate_config
12
44
  end
13
45
 
46
+ # Ensure that the configuration Hash has indifferent access
47
+ # @return [ActiveSupport::HashWithIndifferentAccess]
48
+ def config
49
+ @config = ActiveSupport::HashWithIndifferentAccess.new(@config) if @config.is_a? Hash
50
+ @config
51
+ end
52
+
53
+ # Abstract method
54
+ def validate_config; end
55
+
56
+ # Generate the key for the driver
57
+ # @return [String]
14
58
  def key
15
59
  self.class.name.split(/::/).last.underscore
16
60
  end
17
61
 
62
+ # Generate the icon markup for the driver
63
+ # @return [String]
18
64
  def icon
19
65
  'unchecked'
20
66
  end
21
67
 
68
+ # Generate the name for the driver
69
+ # @return [String]
22
70
  def name
23
71
  @name ||= (@config[:name] || self.class.name.split(/::/).last.titleize)
24
72
  end
25
73
 
26
- def validate_config
27
- end
28
-
29
- def contents(_path)
74
+ # Abstract method
75
+ def contents(*_args)
30
76
  []
31
77
  end
32
78
 
33
- def details(_path)
34
- nil
35
- end
36
-
79
+ # Generate the link for a resource at a given path
80
+ # @param path [String] the path to the resource
81
+ # @return [Array<String, Hash>]
37
82
  def link_for(path)
38
83
  [path, { file_name: File.basename(path) }]
39
84
  end
40
85
 
86
+ # Abstract method
41
87
  def authorized?
42
88
  false
43
89
  end
44
90
 
45
- # @return [Array{URI,Object}] 2 elements: the URI, and session data to store under "#{provider_name}_data"
91
+ # Abstract method
46
92
  def auth_link
47
93
  []
48
94
  end
49
95
 
50
- def connect(_params, _data)
96
+ # Abstract method
97
+ def connect(*_args)
51
98
  nil
52
99
  end
53
100
 
54
101
  private
55
102
 
56
- def callback
57
- connector_response_url(callback_options)
58
- end
59
-
103
+ # Generate the options for the Rails URL generation for API callbacks
60
104
  # remove the script_name parameter from the url_options since that is causing issues
61
105
  # with the route not containing the engine path in rails 4.2.0
106
+ # @return [Hash]
62
107
  def callback_options
63
- config[:url_options].reject { |k, _v| k == :script_name }
108
+ options = config.to_hash
109
+ options.deep_symbolize_keys!
110
+ options[:url_options].reject { |k, _v| k == :script_name }
111
+ end
112
+
113
+ # Generate the URL for the API callback
114
+ # @return [String]
115
+ def callback
116
+ connector_response_url(callback_options)
64
117
  end
65
118
  end
66
119
  end
@@ -1,18 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-box'
4
+ require_relative 'authentication_factory'
5
+
1
6
  module BrowseEverything
2
7
  module Driver
3
8
  # Driver for accessing the Box API (https://www.box.com/home)
4
9
  class Box < Base
5
- require 'ruby-box'
6
-
7
10
  ITEM_LIMIT = 99999
8
11
 
12
+ class << self
13
+ attr_accessor :authentication_klass
14
+
15
+ def default_authentication_klass
16
+ RubyBox::Session
17
+ end
18
+ end
19
+
20
+ # Constructor
21
+ # @param config_values [Hash] configuration for the driver
22
+ def initialize(config_values)
23
+ self.class.authentication_klass ||= self.class.default_authentication_klass
24
+ super(config_values)
25
+ end
26
+
9
27
  def icon
10
28
  'cloud'
11
29
  end
12
30
 
13
31
  def validate_config
14
32
  return if config[:client_id] && config[:client_secret]
15
- raise BrowseEverything::InitializationError, 'Box driver requires both :client_id and :client_secret argument'
33
+ raise InitializationError, 'Box driver requires both :client_id and :client_secret argument'
16
34
  end
17
35
 
18
36
  # @param [String] id of the file or folder in Box
@@ -20,16 +38,16 @@ module BrowseEverything
20
38
  def contents(id = '')
21
39
  if id.empty?
22
40
  folder = box_client.root_folder
23
- results = []
41
+ @entries = []
24
42
  else
25
43
  folder = box_client.folder_by_id(id)
26
- results = [parent_directory(folder)]
44
+ @entries = [parent_directory(folder)]
27
45
  end
28
46
 
29
- folder.items(ITEM_LIMIT, 0, %w(name size created_at)).collect do |f|
30
- results << directory_entry(f)
47
+ folder.items(ITEM_LIMIT, 0, %w[name size created_at]).collect do |f|
48
+ @entries << directory_entry(f)
31
49
  end
32
- results
50
+ @sorter.call(@entries)
33
51
  end
34
52
 
35
53
  # @param [String] id of the file in Box
@@ -68,17 +86,28 @@ module BrowseEverything
68
86
 
69
87
  def box_client
70
88
  if token_expired?
71
- session = box_session(box_token)
89
+ session = box_session
72
90
  register_access_token(session.refresh_token(box_refresh_token))
73
91
  end
74
- RubyBox::Client.new(box_session(box_token, box_refresh_token))
92
+ RubyBox::Client.new(box_session)
93
+ end
94
+
95
+ def session
96
+ AuthenticationFactory.new(
97
+ self.class.authentication_klass,
98
+ client_id: config[:client_id],
99
+ client_secret: config[:client_secret],
100
+ access_token: box_token,
101
+ refresh_token: box_refresh_token
102
+ )
103
+ end
104
+
105
+ def authenticate
106
+ session.authenticate
75
107
  end
76
108
 
77
- def box_session(token = nil, refresh_token = nil)
78
- RubyBox::Session.new(client_id: config[:client_id],
79
- client_secret: config[:client_secret],
80
- access_token: token,
81
- refresh_token: refresh_token)
109
+ def box_session
110
+ authenticate
82
111
  end
83
112
 
84
113
  # If there is an active session, {@token} will be set by {BrowseEverythingController} using data stored in the
@@ -114,8 +143,8 @@ module BrowseEverything
114
143
  BrowseEverything::FileEntry.new(Pathname(folder.name).join('..'), '', '..', 0, Time.current, true)
115
144
  end
116
145
 
117
- def directory_entry(f)
118
- BrowseEverything::FileEntry.new(f.id, "#{key}:#{f.id}", f.name, f.size, f.created_at, f.type == 'folder')
146
+ def directory_entry(file)
147
+ BrowseEverything::FileEntry.new(file.id, "#{key}:#{file.id}", file.name, file.size, file.created_at, file.type == 'folder')
119
148
  end
120
149
  end
121
150
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dropbox_api'
4
+ require_relative 'authentication_factory'
2
5
 
3
6
  module BrowseEverything
4
7
  module Driver
@@ -49,23 +52,34 @@ module BrowseEverything
49
52
  end
50
53
  end
51
54
 
55
+ class << self
56
+ attr_accessor :authentication_klass
57
+
58
+ def default_authentication_klass
59
+ DropboxApi::Authenticator
60
+ end
61
+ end
62
+
63
+ # Constructor
64
+ # @param config_values [Hash] configuration for the driver
65
+ def initialize(config_values)
66
+ self.class.authentication_klass ||= self.class.default_authentication_klass
67
+ super(config_values)
68
+ end
69
+
52
70
  def icon
53
71
  'dropbox'
54
72
  end
55
73
 
56
74
  def validate_config
57
- raise BrowseEverything::InitializationError, 'Dropbox driver requires a :client_id argument' unless config[:client_id]
58
- raise BrowseEverything::InitializationError, 'Dropbox driver requires a :client_secret argument' unless config[:client_secret]
75
+ raise InitializationError, 'Dropbox driver requires a :client_id argument' unless config[:client_id]
76
+ raise InitializationError, 'Dropbox driver requires a :client_secret argument' unless config[:client_secret]
59
77
  end
60
78
 
61
79
  def contents(path = '')
62
- result = client.list_folder(path)
63
- result.entries.map { |entry| FileEntryFactory.build(metadata: entry, key: key) }
64
- end
65
-
66
- def details(path)
67
- metadata = client.get_metadata(path)
68
- FileEntryFactory.build(metadata: metadata, key: key)
80
+ response = client.list_folder(path)
81
+ @entries = response.entries.map { |entry| FileEntryFactory.build(metadata: entry, key: key) }
82
+ @sorter.call(@entries)
69
83
  end
70
84
 
71
85
  def download(path)
@@ -102,8 +116,20 @@ module BrowseEverything
102
116
 
103
117
  private
104
118
 
119
+ def session
120
+ AuthenticationFactory.new(
121
+ self.class.authentication_klass,
122
+ config[:client_id],
123
+ config[:client_secret]
124
+ )
125
+ end
126
+
127
+ def authenticate
128
+ session.authenticate
129
+ end
130
+
105
131
  def authenticator
106
- @authenticator ||= DropboxApi::Authenticator.new(config[:client_id], config[:client_secret])
132
+ @authenticator ||= authenticate
107
133
  end
108
134
 
109
135
  def client
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module BrowseEverything
2
4
  module Driver
3
5
  class FileSystem < Base
@@ -6,20 +8,18 @@ module BrowseEverything
6
8
  end
7
9
 
8
10
  def validate_config
9
- return if config[:home].present?
10
- raise BrowseEverything::InitializationError, 'FileSystem driver requires a :home argument'
11
+ raise BrowseEverything::InitializationError, 'FileSystem driver requires a :home argument' if config[:home].blank?
11
12
  end
12
13
 
13
14
  def contents(path = '')
14
- relative_path = path.sub(%r{^[\/.]+}, '')
15
- real_path = File.join(config[:home], relative_path)
16
- entries = if File.directory?(real_path)
17
- make_directory_entry(relative_path, real_path)
18
- else
19
- [details(real_path)]
20
- end
15
+ 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
21
21
 
22
- sort_entries(entries)
22
+ @sorter.call(@entries)
23
23
  end
24
24
 
25
25
  def link_for(path)
@@ -33,7 +33,7 @@ module BrowseEverything
33
33
  end
34
34
 
35
35
  def details(path, display = File.basename(path))
36
- return nil unless File.exist?(path)
36
+ return nil unless File.exist? path
37
37
  info = File::Stat.new(path)
38
38
  BrowseEverything::FileEntry.new(
39
39
  make_pathname(path),
@@ -47,31 +47,19 @@ module BrowseEverything
47
47
 
48
48
  private
49
49
 
50
- def make_directory_entry(relative_path, real_path)
50
+ def make_directory_entry(real_path)
51
51
  entries = []
52
- if relative_path.present?
53
- entries << details(File.expand_path('..', real_path), '..')
54
- end
55
52
  entries + Dir[File.join(real_path, '*')].collect { |f| details(f) }
56
53
  end
57
54
 
58
- def sort_entries(entries)
59
- entries.sort do |a, b|
60
- if b.container?
61
- a.container? ? a.name.downcase <=> b.name.downcase : 1
62
- else
63
- a.container? ? -1 : a.name.downcase <=> b.name.downcase
64
- end
65
- end
66
- end
67
-
68
55
  def make_pathname(path)
69
56
  Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(config[:home]))
70
57
  end
71
58
 
72
59
  def file_size(path)
73
60
  File.size(path).to_i
74
- rescue
61
+ rescue StandardError => error
62
+ Rails.logger.error "Failed to find the file size for #{path}: #{error}"
75
63
  0
76
64
  end
77
65
  end