browse-everything 0.15.1 → 0.16.0

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