Module: Lich::Gemstone::Infomon
- Defined in:
- documented/gemstone/infomon.rb,
documented/gemstone/infomon/cli.rb,
documented/gemstone/infomon/cache.rb,
documented/gemstone/infomon/parser.rb,
documented/gemstone/infomon/xmlparser.rb
Overview
This module handles information monitoring for the game.
Defined Under Namespace
Modules: Parser, XMLParser Classes: Cache
Constant Summary collapse
- AllowedTypes =
Allowed types for values in Infomon
[Integer, String, NilClass, FalseClass, TrueClass]
Class Method Summary collapse
-
._key(key) ⇒ String
Normalizes the key for storage.
-
._validate!(key, value) ⇒ Object
Validates the key and value types.
-
._value(val) ⇒ Object
Normalizes the value for storage.
-
.cache ⇒ Cache
Returns the cache instance used by Infomon.
-
.cache_load ⇒ void
Loads the cache from the database.
-
.context! ⇒ Object
Checks if the context is valid before accessing Infomon.
-
.db ⇒ Sequel::Database
Returns the Sequel database instance.
-
.db_refresh_needed? ⇒ Boolean
Checks if a refresh of the Infomon database is needed.
-
.delete!(key) ⇒ void
Deletes a key from the cache and database.
-
.file ⇒ String
Returns the file path for the Infomon database.
-
.get(key) ⇒ Object
Retrieves a value from the cache or database.
-
.get_bool(key) ⇒ Boolean
Retrieves a boolean value from the cache or database.
-
.mutex ⇒ Mutex
Returns the mutex used for thread safety.
-
.mutex_lock ⇒ Object
Locks the mutex to ensure thread safety.
-
.mutex_unlock ⇒ Object
Unlocks the mutex to allow other threads access.
-
.queue ⇒ Queue
Returns the SQL queue for database operations.
-
.redo! ⇒ void
Resets and repopulates the Infomon character table.
-
.reset! ⇒ void
Resets the Infomon state, clearing the cache and database table.
-
.set(key, value) ⇒ Symbol
Sets a key-value pair in the cache and database.
-
.setup! ⇒ Sequel::Dataset
Sets up the database table for Infomon.
-
.show(full = false) ⇒ void
Displays stored Infomon information for the current character.
-
.sync ⇒ void
CLI commands for Infomon Synchronizes Infomon data with the current character’s settings.
-
.table ⇒ Sequel::Dataset
Returns the database table for Infomon.
-
.table_name ⇒ Symbol
Generates the table name based on game and XMLData.
-
.upsert(*args) ⇒ void
Inserts or replaces a key-value pair in the database.
-
.upsert_batch(*blob) ⇒ void
Inserts or replaces multiple key-value pairs in the database.
Instance Method Summary collapse
-
#Background(SQLQueueProcessor) ⇒ void
Background thread for processing SQL statements from the queue.
Class Method Details
._key(key) ⇒ String
Normalizes the key for storage
145 146 147 148 149 |
# File 'documented/gemstone/infomon.rb', line 145 def self._key(key) key = key.to_s.downcase key.tr!(' ', '_').gsub!('_-_', '_').tr!('-', '_') if /\s|-/.match?(key) return key end |
._validate!(key, value) ⇒ Object
Validates the key and value types
167 168 169 170 |
# File 'documented/gemstone/infomon.rb', line 167 def self._validate!(key, value) return self._value(value) if AllowedTypes.include?(value.class) raise "infomon:insert(%s) was called with %s\nmust be %s\nvalue=%s" % [key, value.class, AllowedTypes.map(&:name).join("|"), value] end |
._value(val) ⇒ Object
Normalizes the value for storage
154 155 156 157 158 |
# File 'documented/gemstone/infomon.rb', line 154 def self._value(val) return true if val.to_s == "true" return false if val.to_s == "false" return val end |
.cache ⇒ Cache
Returns the cache instance used by Infomon
39 40 41 |
# File 'documented/gemstone/infomon.rb', line 39 def self.cache @cache end |
.cache_load ⇒ void
This method returns an undefined value.
Loads the cache from the database
134 135 136 137 138 139 140 |
# File 'documented/gemstone/infomon.rb', line 134 def self.cache_load sleep(0.01) if XMLData.name.empty? dataset = Infomon.table h = Hash[dataset.map(:key).zip(dataset.map(:value))] self.cache.merge!(h) @cache_loaded = true end |
.context! ⇒ Object
Checks if the context is valid before accessing Infomon
91 92 93 94 95 |
# File 'documented/gemstone/infomon.rb', line 91 def self.context! return unless XMLData.name.empty? or XMLData.name.nil? puts Exception.new.backtrace fail "cannot access Infomon before XMLData.name is loaded" end |
.db ⇒ Sequel::Database
Returns the Sequel database instance
51 52 53 |
# File 'documented/gemstone/infomon.rb', line 51 def self.db @db end |
.db_refresh_needed? ⇒ Boolean
Checks if a refresh of the Infomon database is needed.
This method determines if the database needs to be refreshed based on the last sync date and version of the Infomon data structure.
105 106 107 108 109 110 111 112 |
# File 'documented/gemstone/infomon/cli.rb', line 105 def self.db_refresh_needed? # Change date below to the last date of infomon.db structure change to allow for a forced reset of data. # Change Lich version below to also force a refresh of DB as well due to new API/methods used by infomon (introduction of CHE and account subscription status for example). return true if Infomon.get("infomon.last_sync_date").nil? return true if Infomon.get("infomon.last_sync_date") < Time.new(2025, 6, 26, 20, 0, 0).to_i return true if Gem::Version.new("5.12.2") > Gem::Version.new(Infomon.get("infomon.last_sync_version")) return false end |
.delete!(key) ⇒ void
This method returns an undefined value.
Deletes a key from the cache and database
244 245 246 247 248 |
# File 'documented/gemstone/infomon.rb', line 244 def self.delete!(key) key = self._key(key) self.cache.delete(key) self.queue << "DELETE FROM %s WHERE key = (%s);" % [self.db.literal(self.table_name), self.db.literal(key)] end |
.file ⇒ String
Returns the file path for the Infomon database
45 46 47 |
# File 'documented/gemstone/infomon.rb', line 45 def self.file @file end |
.get(key) ⇒ Object
Retrieves a value from the cache or database
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'documented/gemstone/infomon.rb', line 178 def self.get(key) self.cache_load if !@cache_loaded key = self._key(key) val = self.cache.get(key) { sleep 0.01 until self.queue.empty? begin self.mutex.synchronize do begin db_result = self.table[key: key] if db_result db_result[:value] else nil end rescue => exception pp(exception) nil end end rescue StandardError respond "--- Lich: error: Infomon.get(#{key}): #{$!}" Lich.log "error: Infomon.get(#{key}): #{$!}\n\t#{$!.backtrace.join("\n\t")}" end } return self._value(val) end |
.get_bool(key) ⇒ Boolean
Retrieves a boolean value from the cache or database
208 209 210 211 212 213 214 215 216 217 |
# File 'documented/gemstone/infomon.rb', line 208 def self.get_bool(key) value = Infomon.get(key) if value.is_a?(TrueClass) || value.is_a?(FalseClass) return value elsif value == 1 return true else return false end end |
.mutex ⇒ Mutex
Returns the mutex used for thread safety
57 58 59 |
# File 'documented/gemstone/infomon.rb', line 57 def self.mutex @sql_mutex end |
.mutex_lock ⇒ Object
Locks the mutex to ensure thread safety
63 64 65 66 67 68 69 70 |
# File 'documented/gemstone/infomon.rb', line 63 def self.mutex_lock begin self.mutex.lock unless self.mutex.owned? rescue StandardError respond "--- Lich: error: Infomon.mutex_lock: #{$!}" Lich.log "error: Infomon.mutex_lock: #{$!}\n\t#{$!.backtrace.join("\n\t")}" end end |
.mutex_unlock ⇒ Object
Unlocks the mutex to allow other threads access
74 75 76 77 78 79 80 81 |
# File 'documented/gemstone/infomon.rb', line 74 def self.mutex_unlock begin self.mutex.unlock if self.mutex.owned? rescue StandardError respond "--- Lich: error: Infomon.mutex_unlock: #{$!}" Lich.log "error: Infomon.mutex_unlock: #{$!}\n\t#{$!.backtrace.join("\n\t")}" end end |
.queue ⇒ Queue
Returns the SQL queue for database operations
85 86 87 |
# File 'documented/gemstone/infomon.rb', line 85 def self.queue @sql_queue end |
.redo! ⇒ void
This method returns an undefined value.
Resets and repopulates the Infomon character table.
This method deletes the existing character table, recreates it, and then syncs the data again. This is a destructive operation.
66 67 68 69 70 71 72 |
# File 'documented/gemstone/infomon/cli.rb', line 66 def self.redo! # Destructive - deletes char table, recreates it, then repopulates it respond 'Infomon complete reset reqeusted.' Infomon.reset! Infomon.sync respond 'Infomon reset is now complete.' end |
.reset! ⇒ void
This method returns an undefined value.
Resets the Infomon state, clearing the cache and database table
106 107 108 109 110 111 112 |
# File 'documented/gemstone/infomon.rb', line 106 def self.reset! self.mutex_lock Infomon.db.drop_table?(self.table_name) self.cache.clear @cache_loaded = false Infomon.setup! end |
.set(key, value) ⇒ Symbol
Sets a key-value pair in the cache and database
232 233 234 235 236 237 238 239 |
# File 'documented/gemstone/infomon.rb', line 232 def self.set(key, value) key = self._key(key) value = self._validate!(key, value) return :noop if self.cache.get(key) == value self.cache.put(key, value) self.queue << "INSERT OR REPLACE INTO %s (`key`, `value`) VALUES (%s, %s) on conflict(`key`) do update set value = excluded.value;" % [self.db.literal(self.table_name), self.db.literal(key), self.db.literal(value)] end |
.setup! ⇒ Sequel::Dataset
Sets up the database table for Infomon
122 123 124 125 126 127 128 129 130 |
# File 'documented/gemstone/infomon.rb', line 122 def self.setup! self.mutex_lock @db.create_table?(self.table_name) do text :key, primary_key: true any :value end self.mutex_unlock @_table = @db[self.table_name] end |
.show(full = false) ⇒ void
This method returns an undefined value.
Displays stored Infomon information for the current character.
This method can display either a full or filtered view of the stored data.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'documented/gemstone/infomon/cli.rb', line 81 def self.show(full = false) response = [] # display all stored db values respond "Displaying stored information for #{XMLData.name}" Infomon.table.map([:key, :value]).each { |k, v| response << "#{k} : #{v.inspect}\n" } unless full response.each { |_line| response.reject! do |line| line.match?(/\s:\s0$/) end } end respond response end |
.sync ⇒ void
This method returns an undefined value.
CLI commands for Infomon Synchronizes Infomon data with the current character’s settings.
This method checks for active spells that may interfere with the sync process, and retrieves various character information to update Infomon.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'documented/gemstone/infomon/cli.rb', line 20 def self.sync # since none of this information is 3rd party displayed, silence is golden. shroud_detected = false respond 'Infomon sync requested.' if Effects::Spells.active?(1212) respond 'ATTENTION: SHROUD DETECTED - disabling Shroud of Deception to sync character\'s infomon setting' while Effects::Spells.active?(1212) dothistimeout('STOP 1212', 3, /^With a moment's concentration, you terminate the Shroud of Deception spell\.$|^Stop what\?$/) sleep(0.5) end shroud_detected = true end request = { 'info' => /<a exist=.+#{XMLData.name}/, 'skill' => /<a exist=.+#{XMLData.name}/, 'spell' => %r{<output class="mono"/>}, 'experience' => %r{<output class="mono"/>}, 'society' => %r{<pushBold/>}, 'citizenship' => /^You don't seem|^You currently have .+ in/, 'armor list all' => /<a exist=.+#{XMLData.name}/, 'cman list all' => /<a exist=.+#{XMLData.name}/, 'feat list all' => /<a exist=.+#{XMLData.name}/, 'shield list all' => /<a exist=.+#{XMLData.name}/, 'weapon list all' => /<a exist=.+#{XMLData.name}/, 'ascension list all' => /<a exist=.+#{XMLData.name}/, 'resource' => /^Health: \d+\/(?:<pushBold\/>)?\d+(?:<popBold\/>)?\s+Mana: \d+\/(?:<pushBold\/>)?\d+(?:<popBold\/>)?\s+Stamina: \d+\/(?:<pushBold\/>)?\d+(?:<popBold\/>)?\s+Spirit: \d+\/(?:<pushBold\/>)?\d+/, 'warcry' => /^You have learned the following War Cries:|^You must be an active member of the Warrior Guild to use this skill/, 'profile full' => %r{<output class="mono"/>} } request.each do |command, start_capture| respond "Retrieving character #{command}." if $infomon_debug Lich::Util.issue_command(command.to_s, start_capture, /<prompt/, include_end: true, timeout: 5, silent: false, usexml: true, quiet: true) respond "Did #{command}." if $infomon_debug end respond 'Requested Infomon sync complete.' respond 'ATTENTION: TEND TO YOUR SHROUD!' if shroud_detected Infomon.set('infomon.last_sync_date', Time.now.to_i) Infomon.set('infomon.last_sync_version', LICH_VERSION) end |
.table ⇒ Sequel::Dataset
Returns the database table for Infomon
116 117 118 |
# File 'documented/gemstone/infomon.rb', line 116 def self.table @_table ||= self.setup! end |
.table_name ⇒ Symbol
Generates the table name based on game and XMLData
99 100 101 102 |
# File 'documented/gemstone/infomon.rb', line 99 def self.table_name self.context! ("%s_%s" % [XMLData.game, XMLData.name]).to_sym end |
.upsert(*args) ⇒ void
This method returns an undefined value.
Inserts or replaces a key-value pair in the database
222 223 224 225 226 |
# File 'documented/gemstone/infomon.rb', line 222 def self.upsert(*args) self.table .insert_conflict(:replace) .insert(*args) end |
.upsert_batch(*blob) ⇒ void
This method returns an undefined value.
Inserts or replaces multiple key-value pairs in the database
253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'documented/gemstone/infomon.rb', line 253 def self.upsert_batch(*blob) updated = (blob.first.map { |k, v| [self._key(k), self._validate!(k, v)] } - self.cache.to_a) return :noop if updated.empty? pairs = updated.map { |key, value| (value.is_a?(Integer) or value.is_a?(String)) or fail "upsert_batch only works with Integer or String types" # add the value to the cache self.cache.put(key, value) %[(%s, %s)] % [self.db.literal(key), self.db.literal(value)] }.join(", ") # queue sql statement to run async self.queue << "INSERT OR REPLACE INTO %s (`key`, `value`) VALUES %s on conflict(`key`) do update set value = excluded.value;" % [self.db.literal(self.table_name), pairs] end |
Instance Method Details
#Background(SQLQueueProcessor) ⇒ void
This method returns an undefined value.
Background thread for processing SQL statements from the queue. This thread continuously processes queued SQL statements asynchronously.
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'documented/gemstone/infomon.rb', line 271 Thread.new do loop do sql_statement = Infomon.queue.pop begin Infomon.mutex.synchronize do begin Infomon.db.run(sql_statement) rescue StandardError => e pp(e) end end rescue StandardError respond "--- Lich: error: Infomon ThreadQueue: #{$!}" Lich.log "error: Infomon ThreadQueue: #{$!}\n\t#{$!.backtrace.join("\n\t")}" end end end |