geminabox-s3-store 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7be7730bb6bad6cc92e7aecc4e3924a24ee9bf85
4
+ data.tar.gz: 03ff92c2f42ec849105cdc1705193d8888319063
5
+ SHA512:
6
+ metadata.gz: fbd268c668ac3e43579e86da0f400280ad8be94872413e2e7c5baee802fd4829315cc08439a3f8713bb6d14bf9252087fecc34c7d7746b632106a311527ef363
7
+ data.tar.gz: 456f362481a2fe489d0d0c5cec4fa979b07c0ae32f6d26b1292c979e079eea940721de867584c896c0ee73a47dd3b1068f0cabebe0da9f808394de8d43162cde
@@ -0,0 +1 @@
1
+ require "geminabox/store/s3"
@@ -0,0 +1,186 @@
1
+ module Geminabox
2
+ module Store
3
+ class S3
4
+ attr_reader :logger
5
+
6
+ SPLICEABLE_GZIPPED_FILES = %w[
7
+ specs.4.8.gz
8
+ latest_specs.4.8.gz
9
+ prerelease_specs.4.8.gz
10
+ ]
11
+ SPLICEABLE_TEXT_FILES = %w[
12
+ yaml
13
+ Marshal.4.8
14
+ specs.4.8
15
+ latest_specs.4.8
16
+ prerelease_specs.4.8
17
+ ]
18
+
19
+ def initialize(bucket: nil, lock_manager: lock_manager, file_store: Geminabox::GemStore, logger: Logger.new(STDOUT))
20
+ @bucket = bucket
21
+ @file_store = file_store
22
+ @lock_manager = lock_manager
23
+ @logger = logger
24
+ end
25
+
26
+ def create(gem, overwrite = false)
27
+ @file_store.create gem, overwrite
28
+
29
+ object_name = gem_object_name("/gems/" + gem.name)
30
+
31
+ logger.info "Gem: local -> S3 #{object_name}"
32
+
33
+ @bucket
34
+ .objects[object_name]
35
+ .write gem.gem_data
36
+ update_metadata
37
+ end
38
+
39
+ # Note: deleting doesn't make much sense in this case anyway, as
40
+ # other instances will continue serving cached copies of this
41
+ # gem (there's no way to notify them that gem has been deleted)
42
+ #
43
+ # Do consider using Geminabox.allow_delete = false
44
+ #
45
+ def delete(path_info)
46
+ @file_store.delete path_info
47
+
48
+ @bucket.objects[gem_object_name(path_info)].delete
49
+ end
50
+
51
+ def update_local_file(path_info)
52
+ gem_file = @file_store.local_path path_info
53
+
54
+ unless File.exists? gem_file
55
+ gem_object = @bucket.objects[gem_object_name(path_info)]
56
+ if gem_object.exists?
57
+ # Note: this will load the entire contents of the gem into
58
+ # memory We might switch to using streaming IO or
59
+ # temporary files if this proves to be critical in our
60
+ # environment
61
+ io = StringIO.new gem_object.read
62
+ incoming_gem = Geminabox::IncomingGem.new io
63
+ @file_store.create incoming_gem
64
+
65
+ update_metadata
66
+ end
67
+ end
68
+
69
+ @file_store.update_local_file path_info
70
+ end
71
+
72
+ def update_local_metadata_file(path_info)
73
+ file_name = File.basename path_info
74
+ pull_file file_name do |local, remote|
75
+ if file_name =~ /\.gz$/
76
+ merge_gzipped local, remote
77
+ else
78
+ merge_text local, remote
79
+ end
80
+ end
81
+ end
82
+
83
+ def reindex &block
84
+ FileUtils.mkpath @file_store.local_path "gems"
85
+ @bucket.objects.with_prefix("gems/").each do |object|
86
+ path_info = "/" + object.key
87
+ local_file_path = @file_store.local_path path_info
88
+
89
+ file_does_not_exist = !File.exists?(local_file_path)
90
+
91
+ # File.size raises an exception if file does not exist
92
+ file_size = file_does_not_exist ? 0 : File.size(local_file_path)
93
+ file_has_different_size = object.content_length != file_size
94
+
95
+ if file_does_not_exist || file_has_different_size
96
+ logger.info "Gem: S3 -> local #{local_file_path}"
97
+ File.write local_file_path, object.read
98
+ end
99
+ end
100
+
101
+ @file_store.reindex(&block)
102
+ end
103
+
104
+ private
105
+
106
+ def update_metadata
107
+ @lock_manager.lock ".metadata" do
108
+ push_files SPLICEABLE_GZIPPED_FILES do |local_contents, remote_contents|
109
+ merge_gzipped local_contents, remote_contents
110
+ end
111
+
112
+ push_files SPLICEABLE_TEXT_FILES do |local_contents, remote_contents|
113
+ merge_text local_contents, remote_contents
114
+ end
115
+ end
116
+ end
117
+
118
+ def push_files file_list, &block
119
+ file_list.each do |file_name|
120
+ push_file file_name, &block
121
+ end
122
+ end
123
+
124
+ def merge_file_with_remote file_name
125
+ local_index_file = @file_store.local_path file_name
126
+ if File.exists? local_index_file
127
+ old_contents = File.read(local_index_file, open_args: ["rb"])
128
+ else
129
+ old_contents = ''
130
+ end
131
+
132
+ object = s3_object(file_name)
133
+ unless object.exists?
134
+ old_contents
135
+ else
136
+ yield old_contents, object.read
137
+ end
138
+ end
139
+
140
+ def s3_object file_name
141
+ object_name = metadata_object_name('/' + file_name)
142
+ @bucket.objects[object_name]
143
+ end
144
+
145
+ def push_file file_name, &block
146
+ logger.info "Push: local -> S3 #{file_name}"
147
+
148
+ new_contents = merge_file_with_remote file_name, &block
149
+ s3_object(file_name).write new_contents
150
+ end
151
+
152
+ def pull_file file_name, &block
153
+ logger.info "Pull: S3 -> local #{file_name}"
154
+
155
+ new_contents = merge_file_with_remote file_name, &block
156
+ file_path = @file_store.local_path file_name
157
+ File.write file_path, new_contents
158
+ end
159
+
160
+ def gem_object_name(path_info)
161
+ # Remove loading slash from the path
162
+ path_info[1..-1]
163
+ end
164
+
165
+ def metadata_object_name(path_info)
166
+ path_info.gsub %r{/}, 'metadata/'
167
+ end
168
+
169
+ def merge_gzipped(a, b)
170
+ package(unpackage(a) | unpackage(b))
171
+ end
172
+
173
+ def merge_text(a, b)
174
+ a.to_s + b.to_s
175
+ end
176
+
177
+ def unpackage(content)
178
+ Marshal.load(Gem.gunzip(content))
179
+ end
180
+
181
+ def package(content)
182
+ Gem.gzip(Marshal.dump(content))
183
+ end
184
+ end
185
+ end
186
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geminabox-s3-store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Konstantin Burnaev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: geminabox
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: lock-smith
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A simple hello world gem
42
+ email: kbourn@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/geminabox-s3-store.rb
48
+ - lib/geminabox/store/s3.rb
49
+ homepage: https://github.com/bkon/geminabox-s3-store
50
+ licenses:
51
+ - MIT
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 2.6.13
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: AWS S3 store for geminabox
73
+ test_files: []