evesync 1.0.2

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +25 -0
  3. data/Rakefile +83 -0
  4. data/bin/evedatad +34 -0
  5. data/bin/evehand +36 -0
  6. data/bin/evemond +42 -0
  7. data/bin/evesync +164 -0
  8. data/bin/evesyncd +44 -0
  9. data/bin/start +16 -0
  10. data/config/example.conf +58 -0
  11. data/lib/evesync/config.rb +63 -0
  12. data/lib/evesync/constants.rb +17 -0
  13. data/lib/evesync/database.rb +107 -0
  14. data/lib/evesync/discover.rb +77 -0
  15. data/lib/evesync/err.rb +25 -0
  16. data/lib/evesync/handler/file.rb +28 -0
  17. data/lib/evesync/handler/package.rb +30 -0
  18. data/lib/evesync/handler.rb +89 -0
  19. data/lib/evesync/ipc/client.rb +37 -0
  20. data/lib/evesync/ipc/data/file.rb +64 -0
  21. data/lib/evesync/ipc/data/hashable.rb +74 -0
  22. data/lib/evesync/ipc/data/ignore.rb +22 -0
  23. data/lib/evesync/ipc/data/package.rb +58 -0
  24. data/lib/evesync/ipc/data/utils.rb +14 -0
  25. data/lib/evesync/ipc/data.rb +50 -0
  26. data/lib/evesync/ipc/ipc.rb +42 -0
  27. data/lib/evesync/ipc/server.rb +56 -0
  28. data/lib/evesync/log.rb +66 -0
  29. data/lib/evesync/ntp.rb +16 -0
  30. data/lib/evesync/os/linux/arch/package_manager.rb +29 -0
  31. data/lib/evesync/os/linux/arch/package_watcher.rb +59 -0
  32. data/lib/evesync/os/linux/arch.rb +2 -0
  33. data/lib/evesync/os/linux/base_package_manager.rb +80 -0
  34. data/lib/evesync/os/linux/deb/dpkg.rb +30 -0
  35. data/lib/evesync/os/linux/deb/package_manager.rb +38 -0
  36. data/lib/evesync/os/linux/deb/package_watcher.rb +32 -0
  37. data/lib/evesync/os/linux/deb.rb +2 -0
  38. data/lib/evesync/os/linux/rhel/package_manager.rb +42 -0
  39. data/lib/evesync/os/linux/rhel/package_watcher.rb +32 -0
  40. data/lib/evesync/os/linux/rhel/rpm.rb +41 -0
  41. data/lib/evesync/os/linux/rhel.rb +3 -0
  42. data/lib/evesync/os/linux.rb +9 -0
  43. data/lib/evesync/os.rb +2 -0
  44. data/lib/evesync/sync.rb +209 -0
  45. data/lib/evesync/trigger/base.rb +56 -0
  46. data/lib/evesync/trigger/file.rb +20 -0
  47. data/lib/evesync/trigger/package.rb +20 -0
  48. data/lib/evesync/trigger.rb +106 -0
  49. data/lib/evesync/utils.rb +51 -0
  50. data/lib/evesync/watcher/file.rb +156 -0
  51. data/lib/evesync/watcher/interface.rb +21 -0
  52. data/lib/evesync/watcher/package.rb +8 -0
  53. data/lib/evesync/watcher.rb +68 -0
  54. data/lib/evesync.rb +3 -0
  55. metadata +198 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9a1973a7b7ed425602bbb593496479cfb6ce24e57d5f490ee2d54c52e76375cb
4
+ data.tar.gz: a593092487578200014b495d75a77c20a9798525d00fe9e9ab94ed26b4eba8fd
5
+ SHA512:
6
+ metadata.gz: b06baf168fac9f60cc20995b586712b1de0cf1f13ab32be858d916686d4a770ea67eba82f9b0967bee7b6cc1d664f788d5c4717489320d224a1f2e724ac1ad4c
7
+ data.tar.gz: 7178b8ffc3f5a120438ffd5a0ea1a61fbb3c1a2b9b4b55a90242fadd5b2abcc61379af372bd1575551076b59583d3472faab46214f4c9b3c5687cacf83cfd424
data/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2018, Valentine Kiselev
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Rakefile ADDED
@@ -0,0 +1,83 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'find'
3
+ require 'mkmf'
4
+ require_relative 'lib/evesync'
5
+ VERSION = Evesync::VERSION
6
+ GEMSPEC = 'evesync.gemspec'.freeze
7
+ GEMFILE = "evesync-#{VERSION}.gem".freeze
8
+
9
+ task default: %i[lint build]
10
+
11
+ task :lint do
12
+ sh 'rubocop -l lib' if find_executable 'rubocop'
13
+ end
14
+
15
+ task :rdoc do
16
+ sh 'rdoc --ri'
17
+ sh 'rdoc'
18
+ end
19
+
20
+ task install: [GEMFILE] do
21
+ sh "gem install --local #{GEMFILE}"
22
+ end
23
+
24
+ task build: GEMFILE
25
+ file GEMFILE do
26
+ sh "gem build #{GEMSPEC}"
27
+ end
28
+
29
+ RSpec::Core::RakeTask.new(:spec)
30
+
31
+ task test: :spec
32
+
33
+ task :clean, [:remove_rpm] do |_t, args|
34
+ args.with_defaults(:remove_rpm => 1)
35
+ rm_rf('tmp')
36
+ rm_rf('doc')
37
+ rm_rf('mkmf.log')
38
+ rm_rf(Dir['*.zip'])
39
+ rm_rf('RPM') if args[:remove_rpm] == 1
40
+ rm_rf("evesync-#{VERSION}")
41
+ rm_rf(GEMFILE)
42
+ end
43
+
44
+ task todos: :todo
45
+ task :todo do
46
+ puts "==== \033[0;31mTODOs\033[0m in code"
47
+ puts `find bin lib dockerfiles -type f -exec grep --color=always TODO \{} \+ ||:`
48
+ puts "==== \033[0;31mFIXMEs\033[0m in code"
49
+ puts `find bin lib dockerfiles -type f -exec grep --color=always FIXME \{} \+ ||:`
50
+ end
51
+
52
+ task :lines do
53
+ sh 'find . -name "*.rb" -exec grep -v -E "^\s*#|^\s*$" \{} \+ | wc -l'
54
+ end
55
+
56
+ ## Docker related targets
57
+
58
+ task :docker do
59
+ sh 'docker-compose build'
60
+ end
61
+
62
+ task :up do
63
+ sh 'docker-compose up -d'
64
+ end
65
+
66
+ task :down do
67
+ sh 'docker-compose stop || docker-compose kill ||:'
68
+ sh 'docker-compose rm --force ||:'
69
+ end
70
+
71
+ task :rpm do
72
+ sh("rpmbuild -bb "\
73
+ "--define 'VERSION #{VERSION}' "\
74
+ "--define 'RELEASE `git rev-list HEAD master --count`' "\
75
+ "--define '_builddir #{Dir.pwd}' "\
76
+ "--define '_rpmdir #{Dir.pwd}/RPM/' "\
77
+ "evesync.spec")
78
+ sh('chown -R 1000:1000 RPM')
79
+ end
80
+
81
+ task :'build-rpm' do
82
+ sh('echo rake rpm | docker-compose run build-centos')
83
+ end
data/bin/evedatad ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+
4
+ require 'evesync/ipc/server'
5
+ require 'evesync/log'
6
+ require 'evesync/database'
7
+
8
+ Evesync::Log.info('Database daemon starting...')
9
+
10
+ database = Evesync::Database.new
11
+
12
+ # Creating an IPC server to accept data
13
+ data_server = Evesync::IPC::Server.new(
14
+ port: :evedatad,
15
+ proxy: database
16
+ ).start
17
+
18
+ Signal.trap('TERM') do
19
+ data_server.stop
20
+ exit(0)
21
+ end
22
+
23
+ Evesync::Log.info('Database daemon started!')
24
+
25
+ begin
26
+ loop do
27
+ sleep 3600 # FIXME: 1 hour, rewrite better
28
+ # zip db if needed, save backup
29
+ end
30
+ rescue SignalException => e
31
+ data_server.stop
32
+ Evesync::Log.warn("Database daemon received signal: #{e.signm}(#{e.signo})")
33
+ exit(0)
34
+ end
data/bin/evehand ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+
4
+ require 'evesync/log'
5
+ require 'evesync/ipc/server'
6
+ require 'evesync/handler'
7
+
8
+ Evesync::Log.info('Handle daemon starting...')
9
+
10
+ all_handler = Evesync::Handler.new
11
+
12
+ handler_server = Evesync::IPC::Server.new(
13
+ port: :evehand,
14
+ proxy: all_handler,
15
+ ip: '*' # accept remote requests
16
+ ).start
17
+
18
+ Evesync::Log.info('Handle daemon started!')
19
+
20
+ # Signal handling
21
+
22
+ Signal.trap('TERM') do
23
+ handler_server.stop
24
+ exit(0)
25
+ end
26
+
27
+ begin
28
+ loop do
29
+ sleep 3600 # FIXME: 1 hour, rewrite better
30
+ # FIXME: know what to do
31
+ end
32
+ rescue SignalException => e
33
+ handler_server.stop
34
+ Evesync::Log.warn("Received signal: #{e.signm}(#{e.signo})")
35
+ exit(0)
36
+ end
data/bin/evemond ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+
4
+ require 'evesync/log'
5
+ require 'evesync/ipc/server'
6
+ require 'evesync/trigger'
7
+ require 'evesync/watcher'
8
+
9
+ Evesync::Log.info 'Monitoring daemon starting...'
10
+
11
+ queue = Queue.new
12
+ trigger = Evesync::Trigger.new(queue)
13
+ watcher = Evesync::Watcher.new(queue)
14
+
15
+ [trigger, watcher].each(&:start)
16
+
17
+ evesync_server = Evesync::IPC::Server.new(
18
+ port: :evemond,
19
+ proxy: trigger
20
+ ).start
21
+
22
+ Evesync::Log.info 'Monitoring daemon started!'
23
+
24
+ # Signal handling
25
+
26
+ Signal.trap('SIGINT') do
27
+ [watcher, trigger].each(&:stop)
28
+ evesync_server.stop
29
+ exit(0)
30
+ end
31
+
32
+ begin
33
+ loop do
34
+ sleep 100
35
+ # FIXME: know what to do
36
+ end
37
+ rescue SignalException => e
38
+ watcher.stop
39
+ evesync_server.stop
40
+ Evesync::Log.warn("Monitoring daemon received signal: #{e.signm}(#{e.signo})")
41
+ exit(0)
42
+ end
data/bin/evesync ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+
4
+ require 'optparse'
5
+ require 'socket'
6
+ require 'find'
7
+ require 'zip'
8
+ require 'evesync'
9
+ require 'evesync/log'
10
+ require 'evesync/utils'
11
+ require 'evesync/database'
12
+ require 'evesync/config'
13
+ require 'evesync/sync'
14
+ require 'evesync/ipc/client'
15
+
16
+ DB_TAG = 'db'
17
+ FILES_TAG = 'files'
18
+
19
+ opts = {}
20
+ $program = File.basename($0)
21
+ #Evesync::Log.level = :warn
22
+ Evesync::Log.simple = true
23
+
24
+ OptionParser.new do |parser|
25
+ parser.banner = "#{$program} [OPTIONS...]
26
+
27
+ "
28
+ parser.summary_width = 20
29
+
30
+ parser.on('-l', '--load FILE', 'Load events from file') do |f|
31
+ unless File.file? f
32
+ puts "File is absent: #{f}"
33
+ exit 1
34
+ end
35
+ opts['load_file'] = f
36
+ end
37
+
38
+ parser.on('-z', '--zip [DEST]', 'Zip current database') do |d|
39
+
40
+ # Pretty default name
41
+ hostname = Socket.gethostname
42
+ hostname = hostname == 'localhost' \
43
+ ? Evesync::Utils::local_ip : hostname
44
+ time_now = Time.now.strftime('%Y.%m.%d-%H:%M:%S')
45
+ opts['zip'] = d || "#{$program}_#{hostname}_#{time_now}.zip"
46
+
47
+ # Validation
48
+ unless File.directory? File.dirname opts['zip']
49
+ puts "Directory doesn't exist: #{File.dirname opts['zip']}"
50
+ exit 1
51
+ end
52
+ end
53
+
54
+ parser.on('-f', '--force', 'Enforce synchronization') do
55
+ opts['sync'] = true
56
+ end
57
+
58
+ parser.on_tail('-v', '--version', "Version of #{$program}") do
59
+ puts Evesync::VERSION
60
+ exit
61
+ end
62
+
63
+ parser.on_tail('-h', '--help', 'Help message') do
64
+ puts parser
65
+ exit
66
+ end
67
+ end.parse!
68
+
69
+ def force_sync
70
+ sync_client = Evesync::IPC::Client.new(
71
+ port: :evesyncd
72
+ )
73
+ sync_client.synchronize
74
+ end
75
+
76
+ def files_in(folder)
77
+ begin
78
+ Find.find(folder).collect { |file| file }
79
+ rescue StandardError
80
+ puts "No files in #{folder}"
81
+ []
82
+ end
83
+ end
84
+
85
+ def zip(out)
86
+ databases = {
87
+ DB_TAG => {
88
+ Evesync::Config[:evedatad]['db_path'] =>
89
+ files_in(Evesync::Config[:evedatad]['db_path'])
90
+ },
91
+ FILES_TAG => {
92
+ Evesync::Config[:evedatad]['db_files_path'] =>
93
+ files_in(Evesync::Config[:evedatad]['db_files_path'])
94
+ }
95
+ }
96
+
97
+ Zip::File.open(out, Zip::File::CREATE) do |zip|
98
+ databases.each do |key, entries|
99
+ entries.each do |base_path, files|
100
+ files.each do |file|
101
+ relative_path = Pathname.new(file)
102
+ .relative_path_from(Pathname.new(base_path))
103
+ zip.add(File.join(key, relative_path.to_s), file)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ def load_file(filename)
111
+
112
+ Dir.mktmpdir($program) do |tmpdir|
113
+ # Unpacking...
114
+ Zip::File.open(filename) do |zip|
115
+ zip.each do |entry|
116
+ entry.extract(File.join(tmpdir, entry.name))
117
+ end
118
+ end
119
+
120
+ db_path = File.join(tmpdir, DB_TAG)
121
+ db_files_path = File.join(tmpdir, FILES_TAG)
122
+
123
+ loaded_db = Evesync::Database.new(
124
+ db_path,
125
+ db_files_path
126
+ )
127
+
128
+ local_db = Evesync::IPC::Client.new(
129
+ port: :evedatad
130
+ )
131
+
132
+ diff = Evesync::Sync::diff_missed(
133
+ v1: local_db.events,
134
+ v2: loaded_db.events
135
+ )
136
+
137
+ new_messages = loaded_db.messages(diff)
138
+
139
+ sync_client = Evesync::IPC::Client.new(
140
+ port: :evesyncd
141
+ )
142
+
143
+ sync_client.apply_events(new_messages)
144
+ end
145
+
146
+ end
147
+
148
+ def evesync(opts)
149
+ if opts['sync']
150
+ puts 'Synchronization enforced'
151
+ force_sync
152
+ end
153
+
154
+ if opts['load_file']
155
+ load_file opts['load_file']
156
+ end
157
+
158
+ if opts['zip']
159
+ zip(opts['zip'])
160
+ puts(opts['zip'])
161
+ end
162
+ end
163
+
164
+ evesync opts
data/bin/evesyncd ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+
4
+ require 'evesync/log'
5
+ require 'evesync/sync'
6
+ require 'evesync/config'
7
+ require 'evesync/ipc/server'
8
+
9
+ # Exiting if `auto_discover = false'
10
+ unless Evesync::Config['auto_discover']
11
+ Evesync::Log.info('Synchronizing not required')
12
+ exit(0)
13
+ end
14
+
15
+ Evesync::Log.info('Synchronizing daemon starting...')
16
+
17
+ sync = Evesync::Sync.new
18
+
19
+ sync_server = Evesync::IPC::Server.new(
20
+ port: :evesyncd,
21
+ proxy: sync
22
+ ).start
23
+
24
+ Signal.trap('TERM') do
25
+ sync_server.stop
26
+ exit(0)
27
+ end
28
+
29
+ Evesync::Log.info('Synchronizing daemon started!')
30
+
31
+ few_seconds = Evesync::Config['discover_timeout'].to_i
32
+
33
+ sync.discover # discovering nodes
34
+ sleep 3 # giving 3 seconds to complete
35
+
36
+ begin
37
+ loop do
38
+ sync.synchronize # updating this node
39
+ sleep few_seconds # ... in some interval
40
+ end
41
+ rescue SignalException
42
+ sync_server.stop
43
+ exit(0)
44
+ end
data/bin/start ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ trap 'echo Exiting; \
3
+ pkill evedatad; \
4
+ pkill evemond; \
5
+ pkill evehand; \
6
+ pkill evesyncd
7
+ exit 1' 2
8
+ evedatad &
9
+ evemond &
10
+ evehand &
11
+ sleep 3s
12
+ evesyncd &
13
+
14
+ while true; do
15
+ sleep 1000
16
+ done
@@ -0,0 +1,58 @@
1
+ # This is a configuration file example
2
+ # It contains all information about daemons that can be changed
3
+ # It's TOML formatted file, so feel free to use this format
4
+ # without any restrictions.
5
+ #
6
+ # For more information about TOML format see:
7
+ # - https://github.com/toml-lang/toml
8
+ # - https://en.wikipedia.org/wiki/TOML
9
+
10
+ # Available log levels:
11
+ # debug info warn error fatal
12
+ loglevel = 'debug'
13
+ # Delay in seconds for discovery package sending
14
+ discover_timeout = 600
15
+ # Automate discovering
16
+ auto_discover = true
17
+
18
+ ntp = 'us.pool.ntp.org'
19
+
20
+ [evemond] # System Monitor daemon
21
+ port = 55443
22
+
23
+ # A list of remote nodes ips to keep connected with
24
+ remotes = [ ]
25
+
26
+ # Files to watch and synchronize
27
+ watch = [
28
+ '/etc/pam.d/login',
29
+ '/etc/pam.d/other',
30
+ ]
31
+
32
+ # Interval for gathering files' changes
33
+ watch_interval = 3
34
+
35
+ # TODO:
36
+ ignore_packages = [
37
+ 'ruby',
38
+ ]
39
+
40
+ # TODO:
41
+ ignore_files = [
42
+ '/etc/hosts',
43
+ ]
44
+
45
+
46
+ [evehand] # System Handler daemon
47
+ port = 55432
48
+
49
+
50
+ [evedatad] # System Data daemon
51
+ port = 54321
52
+
53
+ [evesyncd]
54
+ port = 64653
55
+ broadcast_port = 64564
56
+
57
+
58
+ # Copyright (c) Kiselev Valentin 2019
@@ -0,0 +1,63 @@
1
+ require 'toml-rb'
2
+ require 'evesync/utils'
3
+ require 'evesync/constants'
4
+ require 'evesync/log'
5
+
6
+ module Evesync
7
+ module Config
8
+ class << self
9
+
10
+ DEFAULTS = {
11
+ 'ntp' => '',
12
+ 'evemond' => {
13
+ 'port' => Constants::MOOND_PORT,
14
+ 'remotes' => [],
15
+ 'watch' => [],
16
+ 'watch_interval' => Constants::WATCH_INTERVAL
17
+ },
18
+ 'evedatad' => {
19
+ 'port' => Constants::DATAD_PORT,
20
+ 'db_path' => Constants::DB_PATH,
21
+ 'db_files_path' => Constants::DB_FILES_PATH
22
+ },
23
+ 'evehand' => {
24
+ 'port' => Constants::HAND_PORT
25
+ },
26
+ 'evesyncd' => {
27
+ 'port' => Constants::SYNC_PORT
28
+ },
29
+ 'discover_timeout' => Constants::DISCOVER_TIMEOUT
30
+ }
31
+
32
+
33
+ def [](daemon)
34
+ read_config if needs_reading
35
+
36
+ @@config[daemon.to_s]
37
+ end
38
+
39
+ def reread
40
+ read_config
41
+ end
42
+
43
+ private
44
+
45
+ def read_config
46
+ config = begin
47
+ TomlRB.load_file(Constants::CONFIG_FILE)
48
+ rescue StandardError => e
49
+ Log.error("Config ERROR: Couldn't parse file #{Constants::CONFIG_FILE}")
50
+ Log.error("Config ERROR MESSAGE: #{e}")
51
+ Log.error("Config ERROR: Using default configuration")
52
+ {}
53
+ end
54
+ @@config = DEFAULTS.deep_merge(config)
55
+ Log.info("Config initialized!")
56
+ end
57
+
58
+ def needs_reading
59
+ ! defined? @@config
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,17 @@
1
+ module Evesync
2
+ module Constants
3
+ MOOND_PORT = '55443'.freeze
4
+ DATAD_PORT = '54321'.freeze
5
+ HAND_PORT = '55432'.freeze
6
+ SYNC_PORT = '64653'.freeze
7
+
8
+ CONFIG_FILE = '/etc/evesync.conf'.freeze
9
+ DB_PATH = '/var/lib/evesync/db/'.freeze
10
+ DB_FILES_PATH = '/var/lib/evesync/files/'.freeze
11
+
12
+ DEFAULT_LOGLEVEL = 'debug'.freeze
13
+
14
+ DISCOVER_TIMEOUT = 3600
15
+ WATCH_INTERVAL = 2
16
+ end
17
+ end
@@ -0,0 +1,107 @@
1
+ require 'fileutils'
2
+ require 'json'
3
+ require 'lmdb'
4
+ require 'evesync/config'
5
+ require 'evesync/constants'
6
+ require 'evesync/ipc/data/package'
7
+ require 'evesync/ipc/data/file'
8
+ require 'evesync/ipc/data/ignore'
9
+
10
+ module Evesync
11
+
12
+ ##
13
+ # *Database* class is a proxy for *evedatad* daemon
14
+ # implements at least one method: +save+. Allows
15
+ # Local +evemond+ save messages about changes
16
+ #
17
+ # Messages should be serializable (JSON)
18
+ #
19
+ # TODO:
20
+ # * Think about how it can be widened
21
+
22
+ class Database
23
+ def initialize(db_path=nil, db_files_path=nil)
24
+ path = db_path || Config[:evedatad]['db_path']
25
+
26
+ unless ::File.exist? path
27
+ # FIXME: only root. handle exception
28
+ FileUtils.mkdir_p(path)
29
+ end
30
+ @env = LMDB.new(path)
31
+ @db = @env.database
32
+ @files_path = db_files_path || Config[:evedatad]['db_files_path']
33
+ end
34
+
35
+ # Save message to database, key is timestamp+object
36
+ def save(message)
37
+ Log.debug("Database save: #{message}")
38
+ db_add_entry(message)
39
+
40
+ if message.is_a? Evesync::IPC::Data::File
41
+ Log.debug("Database save file action: #{message.action}")
42
+ unless message.action ==
43
+ IPC::Data::File::Action::DELETE
44
+ save_file(message)
45
+ end
46
+ end
47
+ true
48
+ end
49
+
50
+ # Events simplified: object => [timestamp...]
51
+ def events
52
+ events = {}
53
+ @db.each do |key, _|
54
+ timestamp, object = parse_event(key)
55
+ events[object] ||= []
56
+ events[object].push(timestamp)
57
+ end
58
+ events
59
+ end
60
+
61
+ # Messages for events: object => {timestamp => message}
62
+ def messages(events)
63
+ ev_msgs = {}
64
+ @db.each do |key, message|
65
+ timestamp, object = parse_event(key)
66
+ next unless events.include?(object)
67
+
68
+ ev_msgs[object] ||= {}
69
+ if events[object].include?(timestamp)
70
+ ev_msgs[object][timestamp] = message
71
+ end
72
+ end
73
+ ev_msgs
74
+ end
75
+
76
+ private
77
+
78
+ def db_add_entry(message)
79
+ Log.debug('Database adding entry...')
80
+ key = create_key(message)
81
+ value = create_value(message)
82
+ @db[key] = value
83
+ Log.debug('Database adding entry done!')
84
+ end
85
+
86
+ def create_key(message)
87
+ "#{message.timestamp}_#{message.name}"
88
+ end
89
+
90
+ def create_value(message)
91
+ message.to_hash.to_json
92
+ end
93
+
94
+ def parse_event(key)
95
+ key.split('_', 2)
96
+ end
97
+
98
+ def save_file(file)
99
+ Log.debug('Database saving file...')
100
+ fulldest = File.join(@files_path,
101
+ file.name + ".#{file.timestamp}")
102
+ FileUtils.mkdir_p(File.dirname(fulldest))
103
+ FileUtils.cp(file.name, fulldest)
104
+ Log.debug('Database saving file done!')
105
+ end
106
+ end
107
+ end