lifter 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/Gemfile +2 -0
- data/LICENSE +20 -0
- data/lib/lifter/config.rb +54 -0
- data/lib/lifter/connection.rb +170 -0
- data/lib/lifter/file_manager.rb +158 -0
- data/lib/lifter/file_pool.rb +20 -0
- data/lib/lifter/file_upload.rb +74 -0
- data/lib/lifter/payloads/inline_payload.rb +10 -0
- data/lib/lifter/payloads/multipart_payload.rb +85 -0
- data/lib/lifter/server.rb +26 -0
- data/lib/lifter/thread_pool.rb +111 -0
- data/lib/lifter/version.rb +3 -0
- data/lib/lifter/webhook.rb +41 -0
- data/lib/lifter.rb +14 -0
- data/lib/multipart_parser/LICENSE +20 -0
- data/lib/multipart_parser/parser.rb +246 -0
- data/lib/multipart_parser/reader.rb +156 -0
- data/lifter.gemspec +29 -0
- data/test/test.html +14 -0
- data/test/test.rb +52 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8f28fdb4670f7959c6eef5fff3ac85894c016293
|
4
|
+
data.tar.gz: 763a8953d4ba2ed82f969cd28c5cf2150dd0dc81
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b3f02a5c37031f345a3a3320ce4155f67fab09f40dcd98d91a5b12b87a081fbad616416779c9ff47a65ae0a4c1b264dc8d746cfff3a43140243d88676925c330
|
7
|
+
data.tar.gz: ce2838c2521b7aa19926af9e589199fd51c7158e6fb96618cf7f04d62f7309b07824e76ece43afe277f3828db41668e8eed843d1ce3e5091d452ab5516ba0deb
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 Michael Amundson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Lifter
|
2
|
+
class Config
|
3
|
+
Webhook = Struct.new(:method, :url)
|
4
|
+
|
5
|
+
def initialize(&definition)
|
6
|
+
# Defaults
|
7
|
+
@config = {
|
8
|
+
host: '127.0.0.1'
|
9
|
+
}
|
10
|
+
|
11
|
+
instance_eval(&definition)
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(key)
|
15
|
+
key = key.to_sym
|
16
|
+
|
17
|
+
raise ArgumentError.new('unknown key') if !@config.has_key?(key)
|
18
|
+
|
19
|
+
@config[key]
|
20
|
+
end
|
21
|
+
|
22
|
+
def host(host)
|
23
|
+
@config[:host] = host
|
24
|
+
end
|
25
|
+
|
26
|
+
def port(port)
|
27
|
+
@config[:port] = port.to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
def working_dir(path)
|
31
|
+
@config[:working_dir] = path
|
32
|
+
end
|
33
|
+
|
34
|
+
def upload_hash_method(upload_hash_method)
|
35
|
+
@config[:upload_hash_method] = upload_hash_method
|
36
|
+
end
|
37
|
+
|
38
|
+
def max_upload_size(max_upload_size)
|
39
|
+
@config[:max_upload_size] = max_upload_size
|
40
|
+
end
|
41
|
+
|
42
|
+
def upload_prologue_size(upload_prologue_size)
|
43
|
+
@config[:upload_prologue_size] = upload_prologue_size
|
44
|
+
end
|
45
|
+
|
46
|
+
def authorize_webhook(method, url)
|
47
|
+
@config[:authorize_webhook] = Webhook.new(method, url)
|
48
|
+
end
|
49
|
+
|
50
|
+
def completed_webhook(method, url)
|
51
|
+
@config[:completed_webhook] = Webhook.new(method, url)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'http/parser'
|
3
|
+
|
4
|
+
module Lifter
|
5
|
+
class Connection < EventMachine::Connection
|
6
|
+
Request = Struct.new(:headers, :params, :remote_ip)
|
7
|
+
|
8
|
+
PAYLOAD_METHODS = ['put', 'post'].freeze
|
9
|
+
CONTENT_TYPE_KEY = 'content-type'.freeze
|
10
|
+
MULTIPART_CONTENT_TYPE = 'multipart/form-data'.freeze
|
11
|
+
|
12
|
+
attr_reader :request
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
super
|
16
|
+
|
17
|
+
@is_multipart = nil
|
18
|
+
@has_payload = nil
|
19
|
+
|
20
|
+
@multipart_reader = nil
|
21
|
+
@inline_reader = nil
|
22
|
+
|
23
|
+
@server = nil
|
24
|
+
@request = Request.new
|
25
|
+
|
26
|
+
@parser = HTTP::Parser.new
|
27
|
+
|
28
|
+
@parser.on_message_begin = proc do
|
29
|
+
start_request
|
30
|
+
end
|
31
|
+
|
32
|
+
@parser.on_headers_complete = proc do
|
33
|
+
process_headers
|
34
|
+
start_payload if payload?
|
35
|
+
end
|
36
|
+
|
37
|
+
@parser.on_body = proc do |data|
|
38
|
+
receive_payload_data(data) if payload?
|
39
|
+
end
|
40
|
+
|
41
|
+
@parser.on_message_complete = proc do
|
42
|
+
finish_request
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def server=(server)
|
47
|
+
raise ArgumentError.new('incorrect type') if !server.is_a?(Server)
|
48
|
+
|
49
|
+
@server = server
|
50
|
+
end
|
51
|
+
|
52
|
+
def file_manager
|
53
|
+
raise StandardError.new('server not defined') if @server.nil?
|
54
|
+
|
55
|
+
@server.file_manager
|
56
|
+
end
|
57
|
+
|
58
|
+
def receive_data(data)
|
59
|
+
@parser << data
|
60
|
+
end
|
61
|
+
|
62
|
+
def unbind
|
63
|
+
@payload.cancel if !@payload.nil?
|
64
|
+
end
|
65
|
+
|
66
|
+
def http_version
|
67
|
+
@parser.http_version || [1, 0]
|
68
|
+
end
|
69
|
+
|
70
|
+
def remote_ip
|
71
|
+
'127.0.0.1'
|
72
|
+
end
|
73
|
+
|
74
|
+
def respond(code, status)
|
75
|
+
EventMachine.next_tick do
|
76
|
+
response = "HTTP/#{http_version.join('.')} #{code} #{status}"
|
77
|
+
send_data(response)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def close
|
82
|
+
EventMachine.next_tick do
|
83
|
+
close_connection(true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private def receive_payload_data(data)
|
88
|
+
@payload << data
|
89
|
+
end
|
90
|
+
|
91
|
+
private def start_request
|
92
|
+
clear_request
|
93
|
+
clear_multipart
|
94
|
+
clear_payload
|
95
|
+
end
|
96
|
+
|
97
|
+
private def finish_request
|
98
|
+
clear_request
|
99
|
+
clear_multipart
|
100
|
+
clear_payload
|
101
|
+
end
|
102
|
+
|
103
|
+
private def start_payload
|
104
|
+
if multipart?
|
105
|
+
start_multipart_payload
|
106
|
+
else
|
107
|
+
start_inline_payload
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private def clear_request
|
112
|
+
@request = Request.new
|
113
|
+
end
|
114
|
+
|
115
|
+
private def process_headers
|
116
|
+
headers = {}
|
117
|
+
|
118
|
+
@parser.headers.each_pair do |key, value|
|
119
|
+
normalized_key = key.strip.downcase
|
120
|
+
headers[normalized_key] = value
|
121
|
+
end
|
122
|
+
|
123
|
+
@request.headers = headers
|
124
|
+
|
125
|
+
@request.remote_ip = remote_ip
|
126
|
+
end
|
127
|
+
|
128
|
+
private def payload?
|
129
|
+
return @has_payload if !@has_payload.nil?
|
130
|
+
|
131
|
+
@has_payload = PAYLOAD_METHODS.include?(@parser.http_method.to_s.downcase)
|
132
|
+
|
133
|
+
@has_payload
|
134
|
+
end
|
135
|
+
|
136
|
+
private def clear_multipart
|
137
|
+
@is_multipart = nil
|
138
|
+
end
|
139
|
+
|
140
|
+
private def multipart?
|
141
|
+
return @is_multipart if !@is_multipart.nil?
|
142
|
+
|
143
|
+
content_type = @request.headers[CONTENT_TYPE_KEY]
|
144
|
+
|
145
|
+
if content_type.nil? || content_type.empty?
|
146
|
+
@is_multipart = false
|
147
|
+
else
|
148
|
+
@is_multipart = content_type.split(';').first.downcase == MULTIPART_CONTENT_TYPE
|
149
|
+
end
|
150
|
+
|
151
|
+
@is_multipart
|
152
|
+
end
|
153
|
+
|
154
|
+
private def clear_payload
|
155
|
+
@has_payload = nil
|
156
|
+
@payload = nil
|
157
|
+
end
|
158
|
+
|
159
|
+
private def start_multipart_payload
|
160
|
+
content_type = @request.headers[CONTENT_TYPE_KEY]
|
161
|
+
multipart_boundary = MultipartParser::Reader.extract_boundary_value(content_type)
|
162
|
+
|
163
|
+
@payload = Payloads::MultipartPayload.new(self, file_manager, multipart_boundary)
|
164
|
+
end
|
165
|
+
|
166
|
+
private def start_inline_payload
|
167
|
+
@payload = Payloads::InlinePayload.new(self, file_manager)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Lifter
|
4
|
+
class FileManager
|
5
|
+
DEFAULT_HASH_METHOD = :md5
|
6
|
+
|
7
|
+
SCRUB_HEADERS = %w(host content-type content-length accept accept-encoding accept-language
|
8
|
+
connection)
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@working_dir = resolve_working_dir(config.get(:working_dir))
|
12
|
+
|
13
|
+
@authorize_webhook_endpoint = config.get(:authorize_webhook)
|
14
|
+
@completed_webhook_endpoint = config.get(:completed_webhook)
|
15
|
+
|
16
|
+
@work = ThreadPool.new(5)
|
17
|
+
@webhooks = ThreadPool.new(5)
|
18
|
+
@files = FilePool.new
|
19
|
+
|
20
|
+
@upload_hash_method = config.get(:upload_hash_method) || DEFAULT_HASH_METHOD
|
21
|
+
@upload_prologue_size = config.get(:upload_prologue_size)
|
22
|
+
end
|
23
|
+
|
24
|
+
def open_file(connection, file_id, file_param, file_name)
|
25
|
+
@work.push(file_id) do
|
26
|
+
file = File.open("#{@working_dir}/progress/#{file_id}", 'wb')
|
27
|
+
|
28
|
+
file_opts = {
|
29
|
+
hash_method: @upload_hash_method,
|
30
|
+
prologue_size: @upload_prologue_size,
|
31
|
+
original_name: file_name,
|
32
|
+
original_request: connection.request,
|
33
|
+
param: file_param
|
34
|
+
}
|
35
|
+
|
36
|
+
@files.add(file_id, file, file_opts)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_file_data(connection, file_id, data)
|
41
|
+
@work.push(file_id) do
|
42
|
+
file = @files.get(file_id)
|
43
|
+
file.write(data)
|
44
|
+
|
45
|
+
if file.prologue.length >= @upload_prologue_size
|
46
|
+
@webhooks.push(file_id) do
|
47
|
+
webhook = create_authorize_webhook(connection, file)
|
48
|
+
webhook.deliver
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def close_file(connection, file_id)
|
55
|
+
@work.push(file_id) do
|
56
|
+
file = @files.get(file_id)
|
57
|
+
|
58
|
+
file.flush
|
59
|
+
file.close
|
60
|
+
|
61
|
+
file.mv("#{@working_dir}/completed/#{file_id}")
|
62
|
+
|
63
|
+
@files.remove(file_id)
|
64
|
+
|
65
|
+
# In the event the upload was too small for the prologue size to have been met previously,
|
66
|
+
# ensure the authorize webhook is fired off before completed.
|
67
|
+
if file.prologue.length < @upload_prologue_size
|
68
|
+
@webhooks.push(file_id) do
|
69
|
+
webhook = create_authorize_webhook(connection, file)
|
70
|
+
webhook.deliver
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
@webhooks.push(file_id) do
|
75
|
+
webhook = create_completed_webhook(connection, file)
|
76
|
+
webhook.deliver
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def cancel_file(connection, file_id)
|
82
|
+
@webhooks.clear(file_id)
|
83
|
+
|
84
|
+
@work.push(file_id) do
|
85
|
+
file = @files.get(file_id)
|
86
|
+
|
87
|
+
file.close
|
88
|
+
file.rm
|
89
|
+
|
90
|
+
@files.remove(file_id)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private def resolve_working_dir(working_dir)
|
95
|
+
if working_dir.nil?
|
96
|
+
Dir.pwd
|
97
|
+
elsif working_dir[0, 1] == '/'
|
98
|
+
working_dir
|
99
|
+
else
|
100
|
+
"#{Dir.pwd}/#{working_dir}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private def create_authorize_webhook(connection, file)
|
105
|
+
webhook = Webhook.new(@authorize_webhook_endpoint)
|
106
|
+
|
107
|
+
headers = file.original_request.headers.dup
|
108
|
+
params = file.original_request.params.dup
|
109
|
+
|
110
|
+
headers = clean_request_headers(headers)
|
111
|
+
headers['x-upload-ip'] = file.original_request.remote_ip
|
112
|
+
|
113
|
+
params[file.param] = {
|
114
|
+
file_name: file.original_name,
|
115
|
+
file_prologue: file.prologue.length
|
116
|
+
}
|
117
|
+
|
118
|
+
webhook.headers = headers
|
119
|
+
webhook.params = params
|
120
|
+
|
121
|
+
webhook.on_failure do
|
122
|
+
connection.cancel
|
123
|
+
end
|
124
|
+
|
125
|
+
webhook
|
126
|
+
end
|
127
|
+
|
128
|
+
private def create_completed_webhook(connection, file)
|
129
|
+
webhook = Webhook.new(@completed_webhook_endpoint)
|
130
|
+
|
131
|
+
headers = file.original_request.headers.dup
|
132
|
+
params = file.original_request.params.dup
|
133
|
+
|
134
|
+
headers = clean_request_headers(headers)
|
135
|
+
headers['x-upload-ip'] = file.original_request.remote_ip
|
136
|
+
|
137
|
+
params[file.param] = {
|
138
|
+
file_name: file.original_name,
|
139
|
+
file_path: file.full_path,
|
140
|
+
file_hash: file.hash
|
141
|
+
}
|
142
|
+
|
143
|
+
webhook.on_failure do
|
144
|
+
connection.cancel
|
145
|
+
end
|
146
|
+
|
147
|
+
webhook
|
148
|
+
end
|
149
|
+
|
150
|
+
private def clean_request_headers(headers)
|
151
|
+
SCRUB_HEADERS.each do |header_key|
|
152
|
+
headers.delete(header_key)
|
153
|
+
end
|
154
|
+
|
155
|
+
headers
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Lifter
|
2
|
+
class FilePool
|
3
|
+
def initialize
|
4
|
+
@files = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def get(file_id)
|
8
|
+
@files[file_id]
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(file_id, file, opts = {})
|
12
|
+
file_upload = FileUpload.new(file, opts)
|
13
|
+
@files[file_id] = file_upload
|
14
|
+
end
|
15
|
+
|
16
|
+
def remove(file_id)
|
17
|
+
@files.delete(file_id)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'digest/sha2'
|
4
|
+
require 'digest/md5'
|
5
|
+
|
6
|
+
module Lifter
|
7
|
+
class FileUpload
|
8
|
+
DEFAULT_PROLOGUE_SIZE = 10 * 1024
|
9
|
+
|
10
|
+
attr_reader :prologue, :original_request, :original_name, :param
|
11
|
+
|
12
|
+
def initialize(file, opts = {})
|
13
|
+
@file = file
|
14
|
+
|
15
|
+
@hash = setup_hash(opts[:hash_method])
|
16
|
+
|
17
|
+
@prologue_limit = opts[:prologue_size] || DEFAULT_PROLOGUE_SIZE
|
18
|
+
@prologue = ''
|
19
|
+
|
20
|
+
@original_request = opts[:original_request]
|
21
|
+
@original_name = opts[:original_name]
|
22
|
+
@param = opts[:param]
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(data)
|
26
|
+
@file.write(data)
|
27
|
+
|
28
|
+
@hash << data
|
29
|
+
|
30
|
+
if @prologue.length < @prologue_limit
|
31
|
+
@prologue << data[0, @prologue_limit - @prologue.length]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def flush
|
36
|
+
@file.flush
|
37
|
+
end
|
38
|
+
|
39
|
+
def close
|
40
|
+
@file.close
|
41
|
+
end
|
42
|
+
|
43
|
+
def rm
|
44
|
+
FileUtils.rm(full_path)
|
45
|
+
end
|
46
|
+
|
47
|
+
def mv(new_path)
|
48
|
+
FileUtils.mv(full_path, new_path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def full_path
|
52
|
+
File.expand_path(@file.path)
|
53
|
+
end
|
54
|
+
|
55
|
+
def hash
|
56
|
+
@hash.hexdigest
|
57
|
+
end
|
58
|
+
|
59
|
+
private def setup_hash(hash_type)
|
60
|
+
case hash_type
|
61
|
+
when :md5
|
62
|
+
Digest::MD5.new
|
63
|
+
when :sha1
|
64
|
+
Digest::SHA1.new
|
65
|
+
when :sha256
|
66
|
+
Digest::SHA256.new
|
67
|
+
when :sha512
|
68
|
+
Digest::SHA512.new
|
69
|
+
else
|
70
|
+
Digest::MD5.new
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Lifter
|
2
|
+
module Payloads
|
3
|
+
class MultipartPayload
|
4
|
+
CurrentPart = Struct.new(:id, :type, :name)
|
5
|
+
|
6
|
+
def initialize(connection, file_manager, multipart_boundary)
|
7
|
+
@connection = connection
|
8
|
+
@file_manager = file_manager
|
9
|
+
@reader = MultipartParser::Reader.new(multipart_boundary)
|
10
|
+
|
11
|
+
@current_part = nil
|
12
|
+
|
13
|
+
@params = {}
|
14
|
+
|
15
|
+
setup_callbacks
|
16
|
+
end
|
17
|
+
|
18
|
+
def <<(data)
|
19
|
+
@reader.write(data)
|
20
|
+
end
|
21
|
+
|
22
|
+
def cancel
|
23
|
+
return if !current_part?
|
24
|
+
|
25
|
+
if current_part.type == :file
|
26
|
+
@file_manager.cancel_file(@current_part.id)
|
27
|
+
end
|
28
|
+
|
29
|
+
@current_part = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def current_part?
|
33
|
+
!@current_part.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
private def setup_callbacks
|
37
|
+
@reader.on_part do |part|
|
38
|
+
@current_part = CurrentPart.new
|
39
|
+
|
40
|
+
@current_part.id = SecureRandom.hex(10)
|
41
|
+
@current_part.name = part.name
|
42
|
+
|
43
|
+
if part.filename.nil?
|
44
|
+
@current_part.type = :param
|
45
|
+
@params[@current_part.name] = ''
|
46
|
+
else
|
47
|
+
@current_part.type = :file
|
48
|
+
@file_manager.open_file(@connection, @current_part.id, @current_part.name, part.filename)
|
49
|
+
end
|
50
|
+
|
51
|
+
part.on_data do |data|
|
52
|
+
if @current_part.type == :param
|
53
|
+
@params[@current_part.name] << data
|
54
|
+
else
|
55
|
+
@file_manager.write_file_data(@connection, @current_part.id, data)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
part.on_end do
|
60
|
+
@connection.request.params = @params
|
61
|
+
|
62
|
+
if @current_part.type == :file
|
63
|
+
@file_manager.close_file(@connection, @current_part.id)
|
64
|
+
end
|
65
|
+
|
66
|
+
@current_part = nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@reader.on_end do
|
71
|
+
@connection.respond(200, 'OK')
|
72
|
+
@connection.close
|
73
|
+
end
|
74
|
+
|
75
|
+
@reader.on_error do |message|
|
76
|
+
if !@current_part.nil? && @current_part.type == :file
|
77
|
+
@file_manager.cancel_file(@connection, @current_part.id)
|
78
|
+
end
|
79
|
+
|
80
|
+
@current_part = nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module Lifter
|
4
|
+
class Server
|
5
|
+
attr_reader :config, :file_manager
|
6
|
+
|
7
|
+
def initialize(&config)
|
8
|
+
@config = Config.new(&config)
|
9
|
+
@file_manager = FileManager.new(@config)
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
EventMachine.epoll if EventMachine.epoll?
|
14
|
+
EventMachine.kqueue if EventMachine.kqueue?
|
15
|
+
|
16
|
+
EventMachine.run do
|
17
|
+
host = @config.get(:host)
|
18
|
+
port = @config.get(:port)
|
19
|
+
|
20
|
+
EventMachine.start_server(host, port, Connection) do |connection|
|
21
|
+
connection.server = self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|