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
CLI commands for Infomon
This module contains command-line interface (CLI) commands for managing Infomon data.
Defined Under Namespace
Modules: Parser, XMLParser Classes: Cache
Constant Summary collapse
- AllowedTypes =
Allowed types for values in Infomon This constant defines the types that can be stored in the Infomon database.
[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 before insertion.
-
._value(val) ⇒ Object
Normalizes the value for storage.
-
.cache ⇒ Cache
Returns the cache object.
-
.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 database connection.
-
.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.
-
.get_updated_at(key) ⇒ Float?
Retrieves the updated_at timestamp for a key.
-
.mutex ⇒ Mutex
Returns the mutex 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.
-
.redo! ⇒ void
Resets and repopulates the Infomon character table.
-
.reset! ⇒ void
Resets the Infomon state by dropping the table and clearing the cache.
-
.set(key, value) ⇒ Symbol
Sets a key-value pair in the cache and database.
-
.setup! ⇒ Sequel::Dataset
Sets up the Infomon table if it does not exist.
-
.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 Infomon table.
-
.table_name ⇒ Symbol
Returns the table name based on the game and XMLData.
-
.upsert(*args) ⇒ void
Inserts or replaces a record in the database.
-
.upsert_batch(*blob) ⇒ void
Inserts or replaces multiple records in the database.
Class Method Details
._key(key) ⇒ String
Normalizes the key for storage
157 158 159 160 161 |
# File 'documented/gemstone/infomon.rb', line 157 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 before insertion
180 181 182 183 |
# File 'documented/gemstone/infomon.rb', line 180 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
166 167 168 169 170 |
# File 'documented/gemstone/infomon.rb', line 166 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 object
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
146 147 148 149 150 151 152 |
# File 'documented/gemstone/infomon.rb', line 146 def self.cache_load sleep(0.01) if XMLData.name.empty? dataset = Infomon.table h = dataset.map(:key).zip(dataset.map(:value)).to_h 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 database connection
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
279 280 281 282 283 |
# File 'documented/gemstone/infomon.rb', line 279 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
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'documented/gemstone/infomon.rb', line 191 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
221 222 223 224 225 226 227 228 229 230 |
# File 'documented/gemstone/infomon.rb', line 221 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 |
.get_updated_at(key) ⇒ Float?
Retrieves the updated_at timestamp for a key
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'documented/gemstone/infomon.rb', line 236 def self.get_updated_at(key) key = self._key(key) begin self.mutex.synchronize do db_result = self.table[key: key] if db_result db_result[:updated_at] else nil end end rescue StandardError respond "--- Lich: error: Infomon.get_updated_at(#{key}): #{$!}" Lich.log "error: Infomon.get_updated_at(#{key}): #{$!}\n\t#{$!.backtrace.join("\n\t")}" nil end end |
.mutex ⇒ Mutex
Returns the mutex 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
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 by dropping the table and clearing the cache
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
267 268 269 270 271 272 273 274 |
# File 'documented/gemstone/infomon.rb', line 267 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 Infomon table if it does not exist
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'documented/gemstone/infomon.rb', line 122 def self.setup! self.mutex_lock # Check if table exists but missing updated_at column if @db.table_exists?(self.table_name) columns = @db.schema(self.table_name).map { |col| col[0] } unless columns.include?(:updated_at) self.mutex_unlock self.reset! return end end @db.create_table?(self.table_name) do text :key, primary_key: true any :value float :updated_at 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 Infomon table
116 117 118 |
# File 'documented/gemstone/infomon.rb', line 116 def self.table @_table ||= self.setup! end |
.table_name ⇒ Symbol
Returns the table name based on the 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 record in the database
257 258 259 260 261 |
# File 'documented/gemstone/infomon.rb', line 257 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 records in the database
288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'documented/gemstone/infomon.rb', line 288 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 |