browse-everything 0.12.0 → 0.13.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.
- checksums.yaml +4 -4
- data/.engine_cart.yml +1 -0
- data/.rubocop.yml +6 -0
- data/.rubocop_todo.yml +8 -32
- data/README.md +2 -1
- data/app/assets/javascripts/browse_everything/behavior.js.coffee +20 -10
- data/app/assets/stylesheets/browse_everything/_browse_everything.scss +15 -16
- data/app/controllers/browse_everything_controller.rb +29 -29
- data/app/views/browse_everything/_files.html.erb +0 -5
- data/app/views/browse_everything/index.html.erb +3 -0
- data/browse-everything.gemspec +1 -1
- data/lib/browse_everything/browser.rb +1 -1
- data/lib/browse_everything/driver/base.rb +8 -8
- data/lib/browse_everything/driver/box.rb +81 -72
- data/lib/browse_everything/driver/dropbox.rb +35 -29
- data/lib/browse_everything/driver/file_system.rb +50 -30
- data/lib/browse_everything/driver/google_drive.rb +15 -15
- data/lib/browse_everything/driver/s3.rb +82 -27
- data/lib/browse_everything/driver/sky_drive.rb +17 -17
- data/lib/browse_everything/version.rb +1 -1
- data/lib/generators/browse_everything/templates/browse_everything_providers.yml.example +5 -4
- data/spec/fixtures/vcr_cassettes/box.yml +498 -0
- data/spec/unit/box_spec.rb +168 -0
- data/spec/unit/s3_spec.rb +62 -5
- metadata +10 -5
@@ -3,39 +3,45 @@ require 'dropbox_sdk'
|
|
3
3
|
module BrowseEverything
|
4
4
|
module Driver
|
5
5
|
class Dropbox < Base
|
6
|
+
CONFIG_KEYS = [:app_key, :app_secret].freeze
|
7
|
+
|
6
8
|
def icon
|
7
9
|
'dropbox'
|
8
10
|
end
|
9
11
|
|
10
12
|
def validate_config
|
11
|
-
|
12
|
-
|
13
|
-
end
|
13
|
+
return if CONFIG_KEYS.all? { |key| config[key].present? }
|
14
|
+
raise BrowseEverything::InitializationError, "Dropbox driver requires #{CONFIG_KEYS.inspect}"
|
14
15
|
end
|
15
16
|
|
17
|
+
# @return [Array<BrowseEverything::FileEntry>]
|
16
18
|
def contents(path = '')
|
17
|
-
path.sub!(/^[\/.]
|
18
|
-
result =
|
19
|
-
|
20
|
-
result << BrowseEverything::FileEntry.new(
|
21
|
-
Pathname(path).join('..'),
|
22
|
-
'', '..', 0, Time.now, true
|
23
|
-
)
|
24
|
-
end
|
25
|
-
result += client.metadata(path)['contents'].collect do |info|
|
26
|
-
path = info['path']
|
27
|
-
BrowseEverything::FileEntry.new(
|
28
|
-
path,
|
29
|
-
[key, path].join(':'),
|
30
|
-
File.basename(path),
|
31
|
-
info['bytes'],
|
32
|
-
Time.parse(info['modified']),
|
33
|
-
info['is_dir']
|
34
|
-
)
|
35
|
-
end
|
19
|
+
path.sub!(%r{ /^[\/.]+/}, '')
|
20
|
+
result = add_directory_entry(path)
|
21
|
+
result += client.metadata(path)['contents'].collect { |info| make_file_entry(info) }
|
36
22
|
result
|
37
23
|
end
|
38
24
|
|
25
|
+
def add_directory_entry(path)
|
26
|
+
return [] if path.empty?
|
27
|
+
[BrowseEverything::FileEntry.new(
|
28
|
+
Pathname(path).join('..'),
|
29
|
+
'', '..', 0, Time.zone.now, true
|
30
|
+
)]
|
31
|
+
end
|
32
|
+
|
33
|
+
def make_file_entry(info)
|
34
|
+
path = info['path']
|
35
|
+
BrowseEverything::FileEntry.new(
|
36
|
+
path,
|
37
|
+
[key, path].join(':'),
|
38
|
+
File.basename(path),
|
39
|
+
info['bytes'],
|
40
|
+
Time.zone.parse(info['modified']),
|
41
|
+
info['is_dir']
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
39
45
|
def link_for(path)
|
40
46
|
[client.media(path)['url'], { expires: 4.hours.from_now, file_name: File.basename(path), file_size: client.metadata(path)['bytes'].to_i }]
|
41
47
|
end
|
@@ -60,14 +66,14 @@ module BrowseEverything
|
|
60
66
|
|
61
67
|
private
|
62
68
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
69
|
+
def auth_flow
|
70
|
+
@csrf ||= {}
|
71
|
+
DropboxOAuth2Flow.new(config[:app_key], config[:app_secret], callback.to_s, @csrf, 'token')
|
72
|
+
end
|
67
73
|
|
68
|
-
|
69
|
-
|
70
|
-
|
74
|
+
def client
|
75
|
+
DropboxClient.new(token)
|
76
|
+
end
|
71
77
|
end
|
72
78
|
end
|
73
79
|
end
|
@@ -6,54 +6,74 @@ module BrowseEverything
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def validate_config
|
9
|
-
|
10
|
-
|
11
|
-
end
|
9
|
+
return if config[:home].present?
|
10
|
+
raise BrowseEverything::InitializationError, 'FileSystem driver requires a :home argument'
|
12
11
|
end
|
13
12
|
|
14
13
|
def contents(path = '')
|
15
14
|
relative_path = path.sub(%r{^[\/.]+}, '')
|
16
15
|
real_path = File.join(config[:home], relative_path)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
result += [details(real_path)]
|
25
|
-
end
|
26
|
-
result.sort do |a, b|
|
27
|
-
if b.container?
|
28
|
-
a.container? ? a.name.downcase <=> b.name.downcase : 1
|
29
|
-
else
|
30
|
-
a.container? ? -1 : a.name.downcase <=> b.name.downcase
|
31
|
-
end
|
32
|
-
end
|
16
|
+
entries = if File.directory?(real_path)
|
17
|
+
make_directory_entry(relative_path, real_path)
|
18
|
+
else
|
19
|
+
[details(real_path)]
|
20
|
+
end
|
21
|
+
|
22
|
+
sort_entries(entries)
|
33
23
|
end
|
34
24
|
|
35
|
-
def
|
25
|
+
def link_for(path)
|
26
|
+
full_path = File.expand_path(path)
|
27
|
+
file_size = file_size(full_path)
|
28
|
+
["file://#{full_path}", { file_name: File.basename(path), file_size: file_size }]
|
29
|
+
end
|
30
|
+
|
31
|
+
def authorized?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def details(path, display = File.basename(path))
|
36
36
|
return nil unless File.exist?(path)
|
37
37
|
info = File::Stat.new(path)
|
38
38
|
BrowseEverything::FileEntry.new(
|
39
|
-
|
39
|
+
make_pathname(path),
|
40
40
|
[key, path].join(':'),
|
41
|
-
display
|
41
|
+
display,
|
42
42
|
info.size,
|
43
43
|
info.mtime,
|
44
44
|
info.directory?
|
45
45
|
)
|
46
46
|
end
|
47
47
|
|
48
|
-
|
49
|
-
full_path = File.expand_path(path)
|
50
|
-
file_size = File.size(full_path).to_i rescue 0
|
51
|
-
["file://#{full_path}", { file_name: File.basename(path), file_size: file_size }]
|
52
|
-
end
|
48
|
+
private
|
53
49
|
|
54
|
-
|
55
|
-
|
56
|
-
|
50
|
+
def make_directory_entry(relative_path, real_path)
|
51
|
+
entries = []
|
52
|
+
if relative_path.present?
|
53
|
+
entries << details(File.expand_path('..', real_path), '..')
|
54
|
+
end
|
55
|
+
entries + Dir[File.join(real_path, '*')].collect { |f| details(f) }
|
56
|
+
end
|
57
|
+
|
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
|
+
def make_pathname(path)
|
69
|
+
Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(config[:home]))
|
70
|
+
end
|
71
|
+
|
72
|
+
def file_size(path)
|
73
|
+
File.size(path).to_i
|
74
|
+
rescue
|
75
|
+
0
|
76
|
+
end
|
57
77
|
end
|
58
78
|
end
|
59
79
|
end
|
@@ -85,22 +85,22 @@ module BrowseEverything
|
|
85
85
|
|
86
86
|
private
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
88
|
+
def authorization
|
89
|
+
return @auth_client unless @auth_client.nil?
|
90
|
+
return nil unless token.present?
|
91
|
+
auth_client.update_token!(token)
|
92
|
+
self.token = auth_client.fetch_access_token! if auth_client.expired?
|
93
|
+
auth_client
|
94
|
+
end
|
95
95
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
96
|
+
def auth_client
|
97
|
+
@auth_client ||= Signet::OAuth2::Client.new token_credential_uri: 'https://www.googleapis.com/oauth2/v3/token',
|
98
|
+
authorization_uri: 'https://accounts.google.com/o/oauth2/auth',
|
99
|
+
scope: 'https://www.googleapis.com/auth/drive',
|
100
|
+
client_id: config[:client_id],
|
101
|
+
client_secret: config[:client_secret],
|
102
|
+
redirect_uri: callback
|
103
|
+
end
|
104
104
|
end
|
105
105
|
end
|
106
106
|
end
|
@@ -3,10 +3,17 @@ require 'aws-sdk'
|
|
3
3
|
module BrowseEverything
|
4
4
|
module Driver
|
5
5
|
class S3 < Base
|
6
|
-
DEFAULTS = {
|
7
|
-
|
6
|
+
DEFAULTS = { response_type: :signed_url }.freeze
|
7
|
+
RESPONSE_TYPES = [:signed_url, :public_url, :s3_uri].freeze
|
8
|
+
CONFIG_KEYS = [:bucket].freeze
|
9
|
+
|
10
|
+
attr_reader :entries
|
8
11
|
|
9
12
|
def initialize(config, *args)
|
13
|
+
if config.key?(:signed_url) && config.delete(:signed_url) == false
|
14
|
+
warn '[DEPRECATION] Amazon S3 driver: `:signed_url` is deprecated. Please use `:response_type` instead.'
|
15
|
+
config[:response_type] = :public_url
|
16
|
+
end
|
10
17
|
config = DEFAULTS.merge(config)
|
11
18
|
super
|
12
19
|
end
|
@@ -16,27 +23,48 @@ module BrowseEverything
|
|
16
23
|
end
|
17
24
|
|
18
25
|
def validate_config
|
26
|
+
if config.values_at(:app_key, :app_secret).compact.length == 1
|
27
|
+
raise BrowseEverything::InitializationError, 'Amazon S3 driver: If either :app_key or :app_secret is provided, both must be.'
|
28
|
+
end
|
29
|
+
unless RESPONSE_TYPES.include?(config[:response_type].to_sym)
|
30
|
+
raise BrowseEverything::InitializationError, "Amazon S3 driver: Valid response types: #{RESPONSE_TYPES.join(',')}"
|
31
|
+
end
|
19
32
|
return if CONFIG_KEYS.all? { |key| config[key].present? }
|
20
33
|
raise BrowseEverything::InitializationError, "Amazon S3 driver requires #{CONFIG_KEYS.join(',')}"
|
21
34
|
end
|
22
35
|
|
36
|
+
# @return [Array<BrowseEverything::FileEntry>]
|
37
|
+
# Appends / to the path before querying S3
|
23
38
|
def contents(path = '')
|
24
39
|
path = File.join(path, '') unless path.empty?
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
40
|
+
init_entries(path)
|
41
|
+
generate_listing(path)
|
42
|
+
sort_entries
|
43
|
+
end
|
44
|
+
|
45
|
+
def generate_listing(path)
|
46
|
+
listing = client.list_objects(bucket: config[:bucket], delimiter: '/', prefix: full_path(path))
|
47
|
+
add_directories(listing)
|
48
|
+
add_files(listing, path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_directories(listing)
|
33
52
|
listing.common_prefixes.each do |prefix|
|
34
|
-
|
53
|
+
entries << entry_for(from_base(prefix.prefix), 0, Time.current, true)
|
35
54
|
end
|
36
|
-
|
37
|
-
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_files(listing, path)
|
58
|
+
listing.contents.each do |entry|
|
59
|
+
key = from_base(entry.key)
|
60
|
+
unless strip(key) == strip(path)
|
61
|
+
entries << entry_for(key, entry.size, entry.last_modified, false)
|
62
|
+
end
|
38
63
|
end
|
39
|
-
|
64
|
+
end
|
65
|
+
|
66
|
+
def sort_entries
|
67
|
+
entries.sort do |a, b|
|
40
68
|
if b.container?
|
41
69
|
a.container? ? a.name.downcase <=> b.name.downcase : 1
|
42
70
|
else
|
@@ -45,28 +73,34 @@ module BrowseEverything
|
|
45
73
|
end
|
46
74
|
end
|
47
75
|
|
76
|
+
def init_entries(path)
|
77
|
+
@entries = if path.empty?
|
78
|
+
[]
|
79
|
+
else
|
80
|
+
[BrowseEverything::FileEntry.new(Pathname(path).join('..').to_s, '', '..',
|
81
|
+
0, Time.current, true)]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
48
85
|
def entry_for(name, size, date, dir)
|
49
86
|
BrowseEverything::FileEntry.new(name, [key, name].join(':'), File.basename(name), size, date, dir)
|
50
87
|
end
|
51
88
|
|
52
89
|
def details(path)
|
53
|
-
entry = client.head_object(path)
|
90
|
+
entry = client.head_object(full_path(path))
|
54
91
|
BrowseEverything::FileEntry.new(
|
55
|
-
entry.key,
|
56
|
-
|
57
|
-
|
58
|
-
entry.size,
|
59
|
-
entry.last_modified,
|
60
|
-
false
|
92
|
+
entry.key, [key, entry.key].join(':'),
|
93
|
+
File.basename(entry.key), entry.size,
|
94
|
+
entry.last_modified, false
|
61
95
|
)
|
62
96
|
end
|
63
97
|
|
64
98
|
def link_for(path)
|
65
|
-
obj = bucket.object(path)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
99
|
+
obj = bucket.object(full_path(path))
|
100
|
+
case config[:response_type].to_sym
|
101
|
+
when :signed_url then obj.presigned_url(:get, expires_in: 14400)
|
102
|
+
when :public_url then obj.public_url
|
103
|
+
when :s3_uri then "s3://#{obj.bucket_name}/#{obj.key}"
|
70
104
|
end
|
71
105
|
end
|
72
106
|
|
@@ -79,8 +113,29 @@ module BrowseEverything
|
|
79
113
|
end
|
80
114
|
|
81
115
|
def client
|
82
|
-
@client ||= Aws::S3::Client.new(
|
116
|
+
@client ||= Aws::S3::Client.new(aws_config)
|
83
117
|
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def strip(path)
|
122
|
+
path.sub %r{^/?(.+?)/?$}, '\1'
|
123
|
+
end
|
124
|
+
|
125
|
+
def from_base(key)
|
126
|
+
Pathname.new(key).relative_path_from(Pathname.new(config[:base].to_s)).to_s
|
127
|
+
end
|
128
|
+
|
129
|
+
def full_path(path)
|
130
|
+
config[:base].present? ? File.join(config[:base], path) : path
|
131
|
+
end
|
132
|
+
|
133
|
+
def aws_config
|
134
|
+
result = {}
|
135
|
+
result[:credentials] = Aws::Credentials.new(config[:app_key], config[:app_secret]) if config[:app_key].present?
|
136
|
+
result[:region] = config[:region] if config.key?(:region)
|
137
|
+
result
|
138
|
+
end
|
84
139
|
end
|
85
140
|
end
|
86
141
|
end
|
@@ -100,26 +100,26 @@ module BrowseEverything
|
|
100
100
|
|
101
101
|
private
|
102
102
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
103
|
+
def oauth_client
|
104
|
+
Skydrive::Oauth::Client.new(config[:client_id], config[:client_secret], callback.to_s, 'wl.skydrive')
|
105
|
+
# TODO: error checking here
|
106
|
+
end
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
108
|
+
def rehydrate_token
|
109
|
+
return @rehydrate_token if @rehydrate_token
|
110
|
+
token_str = @token[:token]
|
111
|
+
token_expires = @token[:expires_at]
|
112
|
+
Rails.logger.warn "\n\n Rehydrating: #{@token} #{token_str} #{token_expires}"
|
113
|
+
@rehydrate_token = oauth_client.get_access_token_from_hash(token_str, expires_at: token_expires)
|
114
|
+
end
|
115
115
|
|
116
|
-
|
117
|
-
|
118
|
-
|
116
|
+
def safe_id(id)
|
117
|
+
id.tr('.', '-')
|
118
|
+
end
|
119
119
|
|
120
|
-
|
121
|
-
|
122
|
-
|
120
|
+
def real_id(id)
|
121
|
+
id.tr('-', '.')
|
122
|
+
end
|
123
123
|
end
|
124
124
|
end
|
125
125
|
end
|
@@ -12,11 +12,12 @@
|
|
12
12
|
# :client_id: YOUR_GOOGLE_API_CLIENT_ID
|
13
13
|
# :client_secret: YOUR_GOOGLE_API_CLIENT_SECRET
|
14
14
|
# s3:
|
15
|
-
# :app_key: YOUR_AWS_S3_KEY
|
16
|
-
# :app_secret: YOUR_AWS_S3_SECRET
|
17
15
|
# :bucket: YOUR_AWS_S3_BUCKET
|
18
|
-
# :
|
19
|
-
# :
|
16
|
+
# :response_type: :signed_url # set to :public_url for public urls or :s3_uri for an s3://BUCKET/KEY uri
|
17
|
+
# :app_key: YOUR_AWS_S3_KEY # :app_key, :app_secret, and :region can be specified
|
18
|
+
# :app_secret: YOUR_AWS_S3_SECRET # explicitly here, or left out to use system-configured
|
19
|
+
# :region: YOUR_AWS_S3_REGION # defaults.
|
20
|
+
# See https://aws.amazon.com/blogs/security/a-new-and-standardized-way-to-manage-credentials-in-the-aws-sdks/
|
20
21
|
# sky_drive:
|
21
22
|
# :client_id: YOUR_MS_LIVE_CONNECT_CLIENT_ID
|
22
23
|
# :client_secret: YOUR_MS_LIVE_CONNECT_CLIENT_SECRET
|