async-ruby-zip 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +31 -0
- data/README.md +30 -0
- data/async-ruby-zip.gemspec +77 -0
- data/ext/LICENSE +20 -0
- data/ext/archive.c +106 -0
- data/ext/archive.h +8 -0
- data/ext/archive_data.c +61 -0
- data/ext/archive_data.h +25 -0
- data/ext/archive_data_fwd.h +6 -0
- data/ext/async_zip.c +18 -0
- data/ext/async_zip.h +8 -0
- data/ext/callback.c +121 -0
- data/ext/callback.h +9 -0
- data/ext/carchive.c +219 -0
- data/ext/carchive.h +10 -0
- data/ext/carray.c +80 -0
- data/ext/carray.h +28 -0
- data/ext/cerror.c +74 -0
- data/ext/cerror.h +35 -0
- data/ext/cfilesystem.c +107 -0
- data/ext/cfilesystem.h +11 -0
- data/ext/extconf.rb +10 -0
- data/ext/task.c +62 -0
- data/ext/task.h +13 -0
- data/ext/writer.c +96 -0
- data/ext/writer.h +7 -0
- data/lib/async_zip.rb +5 -0
- data/lib/async_zip/version.rb +3 -0
- metadata +140 -0
data/Gemfile.lock
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.3)
|
5
|
+
git (1.2.5)
|
6
|
+
jeweler (1.8.4)
|
7
|
+
bundler (~> 1.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
rdoc
|
11
|
+
json (1.7.3)
|
12
|
+
rake (0.9.2.2)
|
13
|
+
rdoc (3.12)
|
14
|
+
json (~> 1.4)
|
15
|
+
rspec (2.10.0)
|
16
|
+
rspec-core (~> 2.10.0)
|
17
|
+
rspec-expectations (~> 2.10.0)
|
18
|
+
rspec-mocks (~> 2.10.0)
|
19
|
+
rspec-core (2.10.1)
|
20
|
+
rspec-expectations (2.10.0)
|
21
|
+
diff-lcs (~> 1.1.3)
|
22
|
+
rspec-mocks (2.10.1)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
bundler
|
29
|
+
jeweler (~> 1.8.3)
|
30
|
+
rdoc (~> 3.12)
|
31
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
async-ruby-zip
|
2
|
+
==============
|
3
|
+
|
4
|
+
Non-blocking zip reading and writing for Ruby
|
5
|
+
|
6
|
+
|
7
|
+
## Requirements.
|
8
|
+
|
9
|
+
* OS X or Linux
|
10
|
+
* MRI 1.9.2
|
11
|
+
* libzip >=0.10.1
|
12
|
+
|
13
|
+
## Example.
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'rubygems'
|
17
|
+
require 'async_zip'
|
18
|
+
include AsyncZip
|
19
|
+
|
20
|
+
|
21
|
+
# Non-blocking zip-file creation:
|
22
|
+
AsyncZip.create(files, './output.zip') do |task|
|
23
|
+
puts task.inspect
|
24
|
+
end
|
25
|
+
|
26
|
+
# Non-blocking zip-file extraction:
|
27
|
+
AsyncZip.extract('./output.zip', './extracted') do |task|
|
28
|
+
puts task.inspect
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "async-ruby-zip"
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Grigoriy Chudnov"]
|
12
|
+
s.date = "2012-08-04"
|
13
|
+
s.description = "Non-blocking zip reading and writing for Ruby."
|
14
|
+
s.email = "g.chudnov@gmail.com"
|
15
|
+
s.extensions = ["ext/extconf.rb"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"Gemfile.lock",
|
21
|
+
"README.md",
|
22
|
+
"async-ruby-zip-1.0.0.gem",
|
23
|
+
"async-ruby-zip.gemspec",
|
24
|
+
"ext/LICENSE",
|
25
|
+
"ext/archive.c",
|
26
|
+
"ext/archive.h",
|
27
|
+
"ext/archive_data.c",
|
28
|
+
"ext/archive_data.h",
|
29
|
+
"ext/archive_data_fwd.h",
|
30
|
+
"ext/async_zip.c",
|
31
|
+
"ext/async_zip.h",
|
32
|
+
"ext/callback.c",
|
33
|
+
"ext/callback.h",
|
34
|
+
"ext/carchive.c",
|
35
|
+
"ext/carchive.h",
|
36
|
+
"ext/carray.c",
|
37
|
+
"ext/carray.h",
|
38
|
+
"ext/cerror.c",
|
39
|
+
"ext/cerror.h",
|
40
|
+
"ext/cfilesystem.c",
|
41
|
+
"ext/cfilesystem.h",
|
42
|
+
"ext/extconf.rb",
|
43
|
+
"ext/task.c",
|
44
|
+
"ext/task.h",
|
45
|
+
"ext/writer.c",
|
46
|
+
"ext/writer.h",
|
47
|
+
"lib/async_zip.rb",
|
48
|
+
"lib/async_zip/version.rb"
|
49
|
+
]
|
50
|
+
s.homepage = "https://github.com/gchudnov/async-ruby-zip"
|
51
|
+
s.licenses = ["MIT"]
|
52
|
+
s.require_paths = ["lib"]
|
53
|
+
s.rubygems_version = "1.8.24"
|
54
|
+
s.summary = "async-ruby-zip is a ruby extension that zip files asynchronously."
|
55
|
+
|
56
|
+
if s.respond_to? :specification_version then
|
57
|
+
s.specification_version = 3
|
58
|
+
|
59
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
60
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
61
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
63
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
66
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
67
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
68
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
69
|
+
end
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
72
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
73
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
74
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
data/ext/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Grigoriy Chudnov
|
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.
|
data/ext/archive.c
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
#include "archive.h"
|
2
|
+
#include "carchive.h"
|
3
|
+
#include "cerror.h"
|
4
|
+
#include "carray.h"
|
5
|
+
#include "archive_data.h"
|
6
|
+
#include "callback.h"
|
7
|
+
#include "writer.h"
|
8
|
+
|
9
|
+
|
10
|
+
static VALUE az_create(VALUE self, VALUE zip_path, VALUE files);
|
11
|
+
static VALUE az_extract(VALUE self, VALUE zip_path, VALUE dest_path);
|
12
|
+
|
13
|
+
|
14
|
+
static void* az_archive_thread_func(void* data)
|
15
|
+
{
|
16
|
+
archive_data_t* adata = (archive_data_t*)data;
|
17
|
+
if(!adata)
|
18
|
+
return NULL;
|
19
|
+
|
20
|
+
cerror_t err = { 0 };
|
21
|
+
|
22
|
+
if(adata->zip_path && !adata->dst_path) // create
|
23
|
+
{
|
24
|
+
err = carchive_create(adata->zip_path, adata->files_arr);
|
25
|
+
}
|
26
|
+
else if(adata->zip_path && adata->dst_path) // extract
|
27
|
+
{
|
28
|
+
err = carchive_extract(adata->zip_path, adata->dst_path, &adata->files_arr);
|
29
|
+
}
|
30
|
+
|
31
|
+
// error
|
32
|
+
if(cerror_is_error(&err))
|
33
|
+
{
|
34
|
+
adata->err_str = strdup(err.message);
|
35
|
+
}
|
36
|
+
cerror_free_message(&err);
|
37
|
+
|
38
|
+
az_add_to_event_qeueue(adata);
|
39
|
+
|
40
|
+
return NULL;
|
41
|
+
}
|
42
|
+
|
43
|
+
|
44
|
+
/* Add files to archive */
|
45
|
+
static VALUE az_create(VALUE self, VALUE files, VALUE zip_path)
|
46
|
+
{
|
47
|
+
rb_need_block();
|
48
|
+
VALUE proc = rb_block_proc();
|
49
|
+
|
50
|
+
int len = RARRAY_LEN(files);
|
51
|
+
if(len > 0)
|
52
|
+
{
|
53
|
+
carray_str_t* parr = carray_str_create(len);
|
54
|
+
|
55
|
+
int i;
|
56
|
+
for(i = 0; i != len; ++i)
|
57
|
+
{
|
58
|
+
VALUE current = rb_ary_entry(files, i);
|
59
|
+
if(rb_respond_to(current, rb_intern("to_s")))
|
60
|
+
{
|
61
|
+
VALUE name = rb_funcall(current, rb_intern("to_s"), 0);
|
62
|
+
|
63
|
+
carray_str_set(parr, i, StringValuePtr(name));
|
64
|
+
}
|
65
|
+
else
|
66
|
+
{
|
67
|
+
carray_str_set(parr, i, NULL);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
archive_data_t* adata = az_make_archive_data(StringValuePtr(zip_path), NULL, parr);
|
72
|
+
adata->proc = proc;
|
73
|
+
|
74
|
+
rb_gc_register_address(&adata->proc);
|
75
|
+
|
76
|
+
//
|
77
|
+
az_enqueue_task(az_archive_thread_func, adata);
|
78
|
+
}
|
79
|
+
|
80
|
+
return self;
|
81
|
+
}
|
82
|
+
|
83
|
+
/* Extract files from archive */
|
84
|
+
static VALUE az_extract(VALUE self, VALUE zip_path, VALUE dest_path)
|
85
|
+
{
|
86
|
+
rb_need_block();
|
87
|
+
VALUE proc = rb_block_proc();
|
88
|
+
|
89
|
+
archive_data_t* adata = az_make_archive_data(StringValuePtr(zip_path), StringValuePtr(dest_path), NULL);
|
90
|
+
adata->proc = proc;
|
91
|
+
|
92
|
+
rb_gc_register_address(&adata->proc);
|
93
|
+
|
94
|
+
//
|
95
|
+
az_enqueue_task(az_archive_thread_func, adata);
|
96
|
+
|
97
|
+
return self;
|
98
|
+
}
|
99
|
+
|
100
|
+
|
101
|
+
/* Initialize zip pipeline */
|
102
|
+
void init_async_zip_archive(void)
|
103
|
+
{
|
104
|
+
rb_define_singleton_method(mAsyncZip, "create", az_create, 2);
|
105
|
+
rb_define_singleton_method(mAsyncZip, "extract", az_extract, 2);
|
106
|
+
}
|
data/ext/archive.h
ADDED
data/ext/archive_data.c
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#include "archive_data.h"
|
2
|
+
|
3
|
+
// Create a new archive-data structure
|
4
|
+
archive_data_t* az_make_archive_data(const char* zip_path, const char* dst_path, carray_str_t* files_arr)
|
5
|
+
{
|
6
|
+
archive_data_t* adata = (archive_data_t*)malloc(sizeof(archive_data_t));
|
7
|
+
if(adata)
|
8
|
+
{
|
9
|
+
memset(adata, 0, sizeof(archive_data_t));
|
10
|
+
|
11
|
+
if(zip_path)
|
12
|
+
{
|
13
|
+
adata->zip_path = strdup(zip_path);
|
14
|
+
}
|
15
|
+
|
16
|
+
if(dst_path)
|
17
|
+
{
|
18
|
+
adata->dst_path = strdup(dst_path);
|
19
|
+
}
|
20
|
+
|
21
|
+
if(files_arr)
|
22
|
+
{
|
23
|
+
adata->files_arr = files_arr;
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
return adata;
|
28
|
+
}
|
29
|
+
|
30
|
+
// Free archive-data structure
|
31
|
+
void az_free_archive_data(archive_data_t* adata)
|
32
|
+
{
|
33
|
+
if(!adata)
|
34
|
+
return;
|
35
|
+
|
36
|
+
if(adata->zip_path)
|
37
|
+
{
|
38
|
+
free(adata->zip_path);
|
39
|
+
adata->zip_path = NULL;
|
40
|
+
}
|
41
|
+
|
42
|
+
if(adata->dst_path)
|
43
|
+
{
|
44
|
+
free(adata->dst_path);
|
45
|
+
adata->dst_path = NULL;
|
46
|
+
}
|
47
|
+
|
48
|
+
if(adata->err_str)
|
49
|
+
{
|
50
|
+
free(adata->err_str);
|
51
|
+
adata->err_str = NULL;
|
52
|
+
}
|
53
|
+
|
54
|
+
if(adata->files_arr)
|
55
|
+
{
|
56
|
+
carray_str_destroy(adata->files_arr);
|
57
|
+
adata->files_arr = NULL;
|
58
|
+
}
|
59
|
+
|
60
|
+
free(adata);
|
61
|
+
}
|
data/ext/archive_data.h
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#ifndef ASYNC_ZIP_ARCHIVE_DATA_H
|
2
|
+
#define ASYNC_ZIP_ARCHIVE_DATA_H
|
3
|
+
|
4
|
+
#include "ruby.h"
|
5
|
+
#include "carray.h"
|
6
|
+
|
7
|
+
typedef struct _archive_data_t
|
8
|
+
{
|
9
|
+
char* zip_path; // source zip-file
|
10
|
+
char* dst_path; // destination folder to extract zip-file { used only for extraction }
|
11
|
+
|
12
|
+
carray_str_t* files_arr; // array of files to archive or files that were extracted
|
13
|
+
|
14
|
+
VALUE proc;
|
15
|
+
char* err_str;
|
16
|
+
|
17
|
+
struct _archive_data_t* next;
|
18
|
+
} archive_data_t;
|
19
|
+
|
20
|
+
|
21
|
+
archive_data_t* az_make_archive_data(const char* zip_path, const char* dst_path, carray_str_t* files_arr);
|
22
|
+
void az_free_archive_data(archive_data_t* adata);
|
23
|
+
|
24
|
+
#endif
|
25
|
+
|
data/ext/async_zip.c
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#include "async_zip.h"
|
2
|
+
#include "archive.h"
|
3
|
+
#include "task.h"
|
4
|
+
#include "callback.h"
|
5
|
+
|
6
|
+
VALUE mAsyncZip, eZipError;
|
7
|
+
|
8
|
+
/* Initialize extension */
|
9
|
+
void Init_async_zip_ext()
|
10
|
+
{
|
11
|
+
mAsyncZip = rb_define_module("AsyncZip");
|
12
|
+
eZipError = rb_define_class_under(mAsyncZip, "Error", rb_eStandardError);
|
13
|
+
|
14
|
+
//
|
15
|
+
init_async_zip_archive();
|
16
|
+
init_async_zip_task();
|
17
|
+
init_async_zip_event_thread();
|
18
|
+
}
|
data/ext/async_zip.h
ADDED
data/ext/callback.c
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
#include "callback.h"
|
2
|
+
#include "async_zip.h"
|
3
|
+
#include "task.h"
|
4
|
+
#include "archive_data.h"
|
5
|
+
#include <pthread.h>
|
6
|
+
|
7
|
+
|
8
|
+
typedef struct _adata_wait_t
|
9
|
+
{
|
10
|
+
archive_data_t* adata;
|
11
|
+
int abort;
|
12
|
+
} adata_wait_t;
|
13
|
+
|
14
|
+
|
15
|
+
/* Queue of callbacks; each one invoked on a ruby thread */
|
16
|
+
pthread_mutex_t az_proc_mutex = PTHREAD_MUTEX_INITIALIZER;
|
17
|
+
pthread_cond_t az_proc_cond = PTHREAD_COND_INITIALIZER;
|
18
|
+
archive_data_t* az_proc_queue = NULL;
|
19
|
+
|
20
|
+
/* Push new callback to front of the queue */
|
21
|
+
static void az_proc_queue_push(archive_data_t* adata)
|
22
|
+
{
|
23
|
+
adata->next = az_proc_queue;
|
24
|
+
az_proc_queue = adata;
|
25
|
+
}
|
26
|
+
|
27
|
+
/* Pop next callback from the queue; Returns NULL, when the queue is empty */
|
28
|
+
static archive_data_t* az_proc_queue_pop(void)
|
29
|
+
{
|
30
|
+
archive_data_t* adata = az_proc_queue;
|
31
|
+
if(adata)
|
32
|
+
{
|
33
|
+
az_proc_queue = adata->next;
|
34
|
+
}
|
35
|
+
|
36
|
+
return adata;
|
37
|
+
}
|
38
|
+
|
39
|
+
/* Callback executed by Ruby Thread */
|
40
|
+
static VALUE az_handle_proc(void *d)
|
41
|
+
{
|
42
|
+
archive_data_t* adata = (archive_data_t*)d;
|
43
|
+
|
44
|
+
// Invoke callback with task argument
|
45
|
+
VALUE proc = (VALUE)adata->proc;
|
46
|
+
|
47
|
+
int is_create = ((adata->zip_path && !adata->dst_path) ? 1 : 0);
|
48
|
+
|
49
|
+
VALUE task = rb_class_new_instance(0, NULL, cTask);
|
50
|
+
az_task_init(task, (is_create ? NULL : adata->zip_path), (is_create ? adata->zip_path : adata->dst_path), adata->err_str, adata->files_arr);
|
51
|
+
rb_funcall2(proc, rb_intern("call"), 1, &task);
|
52
|
+
rb_gc_unregister_address(&adata->proc);
|
53
|
+
|
54
|
+
az_free_archive_data(adata);
|
55
|
+
|
56
|
+
return Qnil;
|
57
|
+
}
|
58
|
+
|
59
|
+
|
60
|
+
/* Wait until we have some callbacks to process */
|
61
|
+
static VALUE az_wait_for_adata(void* w)
|
62
|
+
{
|
63
|
+
adata_wait_t* waiter = (adata_wait_t*)w;
|
64
|
+
|
65
|
+
pthread_mutex_lock(&az_proc_mutex);
|
66
|
+
while (!waiter->abort && (waiter->adata = az_proc_queue_pop()) == NULL)
|
67
|
+
{
|
68
|
+
pthread_cond_wait(&az_proc_cond, &az_proc_mutex);
|
69
|
+
}
|
70
|
+
pthread_mutex_unlock(&az_proc_mutex);
|
71
|
+
|
72
|
+
return Qnil;
|
73
|
+
}
|
74
|
+
|
75
|
+
/* Stop waiting for callbacks */
|
76
|
+
static void az_stop_waiting_for_adata(void* w)
|
77
|
+
{
|
78
|
+
adata_wait_t* waiter = (adata_wait_t*)w;
|
79
|
+
|
80
|
+
pthread_mutex_lock(&az_proc_mutex);
|
81
|
+
waiter->abort = 1;
|
82
|
+
pthread_mutex_unlock(&az_proc_mutex);
|
83
|
+
pthread_cond_signal(&az_proc_cond);
|
84
|
+
}
|
85
|
+
|
86
|
+
|
87
|
+
/* ruby event thread, waiting for processed archives to invoke a callback */
|
88
|
+
static VALUE az_event_thread(void *unused)
|
89
|
+
{
|
90
|
+
adata_wait_t waiter = { .adata = NULL, .abort = 0 };
|
91
|
+
while (!waiter.abort)
|
92
|
+
{
|
93
|
+
rb_thread_blocking_region(az_wait_for_adata, &waiter, az_stop_waiting_for_adata, &waiter);
|
94
|
+
if (waiter.adata)
|
95
|
+
{
|
96
|
+
rb_thread_create(az_handle_proc, waiter.adata);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
return Qnil;
|
101
|
+
}
|
102
|
+
|
103
|
+
/* Initialize Ruby Event Thread for invokation of user-provider callbacks */
|
104
|
+
void az_create_event_thread(void)
|
105
|
+
{
|
106
|
+
rb_thread_create(az_event_thread, NULL);
|
107
|
+
}
|
108
|
+
|
109
|
+
/* Add the archive data to the event queue */
|
110
|
+
void az_add_to_event_qeueue(archive_data_t* adata)
|
111
|
+
{
|
112
|
+
pthread_mutex_lock(&az_proc_mutex);
|
113
|
+
az_proc_queue_push(adata);
|
114
|
+
pthread_mutex_unlock(&az_proc_mutex);
|
115
|
+
pthread_cond_signal(&az_proc_cond);
|
116
|
+
}
|
117
|
+
|
118
|
+
void init_async_zip_event_thread(void)
|
119
|
+
{
|
120
|
+
az_create_event_thread();
|
121
|
+
}
|