@ -87,7 +87,7 @@ dbot.version : Module to track version and changelog information and update the
save_state="y"
save_state="y"
date_written="2017-08-12 08:45:15"
date_written="2017-08-12 08:45:15"
requires="4.98"
requires="4.98"
version="2.0015 "
version="2.0016 "
>
>
<description trim= "y" >
<description trim= "y" >
< ![CDATA[
< ![CDATA[
@ -149,7 +149,7 @@ Usage
dinv reset [list | confirm] <module n a m e s | a l l >
dinv reset [list | confirm] <module n a m e s | a l l >
dinv forget <query >
dinv forget <query >
dinv notify [none | light | standard | all]
dinv notify [none | light | standard | all]
dinv cache [reset | size] [recent | frequent | all] < # entries>
dinv cache [reset | size] [recent | frequent | custom | all] < # entries>
dinv tags <names | a l l > [on | off]
dinv tags <names | a l l > [on | off]
dinv reload
dinv reload
@ -189,19 +189,13 @@ Release Notes
4) The plugin does not automatically open containers that are closed. As a result, you won't be able
4) The plugin does not automatically open containers that are closed. As a result, you won't be able
to get/put items in a closed container. Keep your containers open! :)
to get/put items in a closed container. Keep your containers open! :)
5) If you add a keyword to an item, drop it, and then pick it back up, the new keyword will still
5) If the plugin tags are enabled, they will echo an end tag at the conclusion of an operation. However,
be available on the item if it is still in your cache. However, this is not the case for common
consumable items. For example, we treat all duff beer potions as being identical so that you don't
need to individually identify each potion. As a result, the cached versions of those common items
won't maintain custom keywords.
6) If the plugin tags are enabled, they will echo an end tag at the conclusion of an operation. However,
if the user goes into a state that doesn't allow echoing (e.g., AFK) then the plugin cannot report the
if the user goes into a state that doesn't allow echoing (e.g., AFK) then the plugin cannot report the
end tag. In this scenario, the plugin will notify the user about the end tag via a warning notification
end tag. In this scenario, the plugin will notify the user about the end tag via a warning notification
instead of an echo. Triggers cannot catch notifications though so any code relying on end tags should
instead of an echo. Triggers cannot catch notifications though so any code relying on end tags should
either detect when you go AFK or cleanly time out after a reasonable amount of time.
either detect when you go AFK or cleanly time out after a reasonable amount of time.
7 ) If you add the portal wish after you have built your inventory table, you will need to either rebuild
6) If you add the portal wish after you have built your inventory table, you will need to either rebuild
the table (dinv build confirm) or forget/re-identify your portals (dinv forget type portal) and
the table (dinv build confirm) or forget/re-identify your portals (dinv forget type portal) and
(dinv refresh all).
(dinv refresh all).
@ -413,7 +407,7 @@ Feature Wishlist
<alias
<alias
script="inv.cli.cache.fn"
script="inv.cli.cache.fn"
match="^[ ]*dinv[ ]+cache[ ]+(reset|display|size)[ ]+(recent|frequent|all)[ ]*([0-9]+)?.*$"
match="^[ ]*dinv[ ]+cache[ ]+(reset|display|size)[ ]+(recent|frequent|custom| all)[ ]*([0-9]+)?.*$"
enabled="y"
enabled="y"
regexp="y"
regexp="y"
send_to="12"
send_to="12"
@ -2196,7 +2190,7 @@ inv.cli.keyword = {}
function inv.cli.keyword.fn(name, line, wildcards)
function inv.cli.keyword.fn(name, line, wildcards)
local operation = wildcards[1] or ""
local operation = wildcards[1] or ""
local keyword = wildcards[2] or ""
local keyword = wildcards[2] or ""
local query = wildcards[3] or ""
local query = Trim( wildcards[3] or "")
inv.items.keyword(keyword, operation, query, false, line)
inv.items.keyword(keyword, operation, query, false, line)
end -- inv.cli.keyword.fn
end -- inv.cli.keyword.fn
@ -3528,8 +3522,8 @@ reset back to default values:
need to rebuild your inventory table if you reset this.
need to rebuild your inventory table if you reset this.
@Citems@W: This is your inventory table. You'll need to rebuild it if you
@Citems@W: This is your inventory table. You'll need to rebuild it if you
reset this.
reset this.
@Ccache@W: This clears both your "recent item cache" and "frequently used
@Ccache@W: This clears your "recent item cache", "frequently used item cache",
item cache".
and "customization item cache".
@Csnapshot@W: This table stores all custom equipment set snapshots that you have
@Csnapshot@W: This table stores all custom equipment set snapshots that you have
created.
created.
@Cpriority@W: This wipes out all custom stat priorities and implements the
@Cpriority@W: This wipes out all custom stat priorities and implements the
@ -3716,6 +3710,9 @@ function inv.cli.cache.fn(name, line, wildcards)
if (cacheType == "frequent") or (cacheType == "all") then
if (cacheType == "frequent") or (cacheType == "all") then
retval = inv.cache.resetCache(inv.cache.frequent.name)
retval = inv.cache.resetCache(inv.cache.frequent.name)
end -- if
end -- if
if (cacheType == "custom") or (cacheType == "all") then
retval = inv.cache.resetCache(inv.cache.custom.name)
end -- if
elseif (cacheCommand == "display") then
elseif (cacheCommand == "display") then
if (cacheType == "recent") or (cacheType == "all") then
if (cacheType == "recent") or (cacheType == "all") then
@ -3724,6 +3721,9 @@ function inv.cli.cache.fn(name, line, wildcards)
if (cacheType == "frequent") or (cacheType == "all") then
if (cacheType == "frequent") or (cacheType == "all") then
retval = inv.cache.dump(inv.cache.frequent.table)
retval = inv.cache.dump(inv.cache.frequent.table)
end -- if
end -- if
if (cacheType == "custom") or (cacheType == "all") then
retval = inv.cache.dump(inv.cache.custom.table)
end -- if
elseif (cacheCommand == "size") then
elseif (cacheCommand == "size") then
if (cacheType == "recent") or (cacheType == "all") then
if (cacheType == "recent") or (cacheType == "all") then
@ -3742,6 +3742,14 @@ function inv.cli.cache.fn(name, line, wildcards)
retval = inv.cache.setSize(inv.cache.frequent.table, cacheSize)
retval = inv.cache.setSize(inv.cache.frequent.table, cacheSize)
end -- if
end -- if
end -- if
end -- if
if (cacheType == "custom") or (cacheType == "all") then
if (cacheSize < 0 ) t h e n
dbot.print("@WCustom item cache: " .. dbot.table.getNumEntries(inv.cache.custom.table.entries) ..
" / " .. (inv.cache.getSize(inv.cache.custom.table) or 0) .. " entries are in use@w")
else
retval = inv.cache.setSize(inv.cache.custom.table, cacheSize)
end -- if
end -- if
else
else
dbot.warn("inv.cli.cache.fn: Invalid cache command \"" .. cacheCommand .. "\" detected")
dbot.warn("inv.cli.cache.fn: Invalid cache command \"" .. cacheCommand .. "\" detected")
@ -3759,7 +3767,7 @@ end -- inv.cli.cache.fn
function inv.cli.cache.usage()
function inv.cli.cache.usage()
dbot.print("@W " .. pluginNameCmd ..
dbot.print("@W " .. pluginNameCmd ..
" cache @G[reset | size] [recent | frequent | all] @Y< # entries>@w")
" cache @G[reset | size] [recent | frequent | custom | all] @Y< # entries>@w")
end -- inv.cli.cache.usage
end -- inv.cli.cache.usage
@ -3768,13 +3776,13 @@ function inv.cli.cache.examples()
inv.cli.cache.usage()
inv.cli.cache.usage()
dbot.print(
dbot.print(
[[@W
[[@W
This plugin implements two types of item caches. The first type is the "@Crecent item@W"
This plugin implements three types of item caches. The first type is the "@Crecent item@W"
cache. If an identified item leaves your inventory (e.g., you dropped it or you put
cache. If an identified item leaves your inventory (e.g., you dropped it or you put
it into your vault) then information about that item is moved to the recent cache.
it into your vault) then information about that item is moved to the recent cache.
If you add that item back to your inventory at some point in the future then you won't
If you add that item back to your inventory at some point in the future then you won't
need to re-identify the item. The plugin will pull the necessary info directly from the
need to re-identify the item. The plugin will pull the necessary info directly from the
recent cache. This is very convenient and speeds up accessing your vault or using a bag
recent cache. This is very convenient and speeds up accessing your vault or using a bag
filled with keys. By default, the recent cache keeps entries for the 5 00 most-recently
filled with keys. By default, the recent cache keeps entries for the 10 00 most-recently
used items that left your inventory but you can adjust the cache size as shown below.
used items that left your inventory but you can adjust the cache size as shown below.
The second type of cache is the "@Cfrequently used@W" item cache. The recent cache stores
The second type of cache is the "@Cfrequently used@W" item cache. The recent cache stores
@ -3788,11 +3796,19 @@ and avoid re-identification. By default, the plugin will use the frequent cache
all potions, pills, and consumable items. The frequent cache stores information on up
all potions, pills, and consumable items. The frequent cache stores information on up
to 100 different items at a time.
to 100 different items at a time.
The third type of cache is the "@Ccustomization cache@W". Most details about an item
can be regenerated by re-identifying the item. However, customizations such as adding
a keyword or adding an organization query to an item could be lost if an item is removed
from your inventory and that item is no longer in your recent cache when you add it back
to your inventory. The custom cache is a long-lived repository for item customizations
that makes it possible to recover details such as custom keywords or organization queries.
This is especially handy if you die and all of your items are no longer in your inventory.
Examples:
Examples:
1) Reset just the recent cache
1) Reset just the recent cache
"@Gdinv cache reset recent@W"
"@Gdinv cache reset recent@W"
2) Reset both the recent and frequent caches
2) Reset the recent, frequent, and custom caches
"@Gdinv cache reset all@W"
"@Gdinv cache reset all@W"
3) Set the number of entries in the frequent cache to 200
3) Set the number of entries in the frequent cache to 200
@ -4684,7 +4700,7 @@ function inv.items.init.atInstall()
-- Trigger on an eqdata, invdata, or keyring data tag
-- Trigger on an eqdata, invdata, or keyring data tag
check (AddTriggerEx(inv.items.trigger.itemDataStartName,
check (AddTriggerEx(inv.items.trigger.itemDataStartName,
"^{(eqdata|invdata|keyring)[ ]?([0-9]+)?}$",
"^{(eqdata|invdata|keyring)[ ]?([0-9]+)?}$|^(Item) ([0-9]+) not found.$ ",
"inv.items.trigger.itemDataStart(\"%1\",\"%2\")",
"inv.items.trigger.itemDataStart(\"%1\",\"%2\")",
drlTriggerFlagsBaseline + trigger_flag.OmitFromOutput,
drlTriggerFlagsBaseline + trigger_flag.OmitFromOutput,
custom_colour.Custom11, 0, "", "", sendto.script, 0))
custom_colour.Custom11, 0, "", "", sendto.script, 0))
@ -5504,6 +5520,25 @@ function inv.items.identifyCR(maxNumItems, refreshLocations)
end -- if
end -- if
end -- if
end -- if
-- Check if the custom cache has additional details for this item. For example, we might have
-- custom keywords or an organize query for the item.
local cachedEntry = inv.cache.get(inv.cache.custom.table, objId)
if (cachedEntry ~= nil) then
-- Merge any cached keywords into the item's keywords field
if (cachedEntry.keywords ~= nil) and (cachedEntry.keywords ~= "") then
local oldKeywords = inv.items.getStatField(objId, invStatFieldKeywords) or ""
local mergedKeywords = dbot.mergeFields(cachedEntry.keywords, oldKeywords) or cachedEntry.keywords
inv.items.setStatField(objId, invStatFieldKeywords, mergedKeywords)
dbot.debug("Merged cached keywords = \"" .. mergedKeywords .. "\"")
end -- if
-- Use any cached organize queries that exist
if (cachedEntry.organize ~= nil) and (cachedEntry.organize ~= "") then
inv.items.setStatField(objId, invQueryKeyOrganize, cachedEntry.organize)
dbot.debug("Cached organize queries = \"" .. cachedEntry.organize .. "\"")
end -- if
end -- if
end -- for objId,_ in pairs
end -- for objId,_ in pairs
-- We are done (at least for now)
-- We are done (at least for now)
@ -7157,6 +7192,16 @@ function inv.items.keywordCR()
local endTag = inv.items.keywordPkg.endTag
local endTag = inv.items.keywordPkg.endTag
-- The custom cache will store any relevant customizable pieces from an object. We don't
-- bother caching the "clean" keyword because it is updated so frequently and we can easily
-- get back to a known good state even if it isn't cached. This will reduce disk overhead.
local doCacheItem
if (inv.items.keywordPkg.keyword == invItemsRefreshClean) then
doCacheItem = false
else
doCacheItem = true
end -- if
idArray, retval = inv.items.searchCR(inv.items.keywordPkg.queryString)
idArray, retval = inv.items.searchCR(inv.items.keywordPkg.queryString)
if (retval ~= DRL_RET_SUCCESS) then
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.items.keywordCR: failed to search inventory table: " .. dbot.retval.getString(retval))
dbot.warn("inv.items.keywordCR: failed to search inventory table: " .. dbot.retval.getString(retval))
@ -7170,6 +7215,7 @@ function inv.items.keywordCR()
-- Update the keyword for each item that matched the query string
-- Update the keyword for each item that matched the query string
for i,objId in ipairs(idArray) do
for i,objId in ipairs(idArray) do
local keywordField = inv.items.getStatField(objId, invStatFieldKeywords) or ""
local keywordField = inv.items.getStatField(objId, invStatFieldKeywords) or ""
local customEntry
if (inv.items.keywordPkg.keywordOperation == invKeywordOpAdd) then
if (inv.items.keywordPkg.keywordOperation == invKeywordOpAdd) then
dbot.debug("Adding keyword \"" .. inv.items.keywordPkg.keyword .. "\" to object " .. objId)
dbot.debug("Adding keyword \"" .. inv.items.keywordPkg.keyword .. "\" to object " .. objId)
@ -7211,6 +7257,14 @@ function inv.items.keywordCR()
inv.items.keywordPkg = nil
inv.items.keywordPkg = nil
return inv.tags.stop(invTagsKeyword, endTag, DRL_RET_INTERNAL_ERROR)
return inv.tags.stop(invTagsKeyword, endTag, DRL_RET_INTERNAL_ERROR)
end -- if
end -- if
if (doCacheItem) then
retval = inv.cache.add(inv.cache.custom.table, objId)
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.items.keywordCR: Failed to add keywords to custom cache for object " .. objId ..
dbot.retval.getString(retval))
end -- if
end -- if
end -- for
end -- for
end -- if
end -- if
@ -7219,6 +7273,13 @@ function inv.items.keywordCR()
numUpdatedKeywords .. " out of " .. numQueryItems .. " items matching query")
numUpdatedKeywords .. " out of " .. numQueryItems .. " items matching query")
end -- if
end -- if
-- Save the inventory table and the custom cache to disk if we just updated keywords for one or more
-- items and the keywords are cacheable
if doCacheItem and (numUpdatedKeywords > 0) then
inv.items.save()
inv.cache.saveCustom()
end -- if
inv.items.keywordPkg = nil
inv.items.keywordPkg = nil
return inv.tags.stop(invTagsKeyword, endTag, retval)
return inv.tags.stop(invTagsKeyword, endTag, retval)
@ -8202,7 +8263,15 @@ function inv.items.organize.addCR()
inv.items.setStatField(objId, invQueryKeyOrganize, organizeField)
inv.items.setStatField(objId, invQueryKeyOrganize, organizeField)
inv.items.save()
inv.items.save()
dbot.note("Added organization query \"@C" .. inv.items.organize.addPkg.query .. "@W\" to container \"" ..
-- Add the new organization query to the custom cache
retval = inv.cache.add(inv.cache.custom.table, objId)
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.items.organize.addCR: Failed to add organize queries to custom cache for object " ..
objId .. dbot.retval.getString(retval))
end -- if
inv.cache.saveCustom()
dbot.info("Added organization query \"@C" .. inv.items.organize.addPkg.query .. "@W\" to container \"" ..
(inv.items.getField(objId, invFieldColorName) or "Unidentified"))
(inv.items.getField(objId, invFieldColorName) or "Unidentified"))
-- Clean up, print an end tag (if necessary), and return
-- Clean up, print an end tag (if necessary), and return
@ -8246,7 +8315,8 @@ function inv.items.organize.clearCR()
-- Find the unique container specified by the user via a relative name (e.g., "2.bag")
-- Find the unique container specified by the user via a relative name (e.g., "2.bag")
idArray, retval = inv.items.searchCR("type container rname " .. inv.items.organize.clearPkg.container)
idArray, retval = inv.items.searchCR("type container rname " .. inv.items.organize.clearPkg.container)
if (retval ~= DRL_RET_SUCCESS) then
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.items.organize.clearCR: failed to search inventory table: " .. dbot.retval.getString(retval))
dbot.warn("inv.items.organize.clearCR: failed to search inventory table: " ..
dbot.retval.getString(retval))
elseif (#idArray ~= 1) then
elseif (#idArray ~= 1) then
-- There should only be a single match to the container's relative name (e.g., "2.bag")
-- There should only be a single match to the container's relative name (e.g., "2.bag")
dbot.warn("Container relative name \"" .. inv.items.organize.clearPkg.container ..
dbot.warn("Container relative name \"" .. inv.items.organize.clearPkg.container ..
@ -8268,7 +8338,15 @@ function inv.items.organize.clearCR()
inv.items.setStatField(objId, invQueryKeyOrganize, "")
inv.items.setStatField(objId, invQueryKeyOrganize, "")
inv.items.save()
inv.items.save()
dbot.note("Cleared all organization queries from container \"" ..
-- Update the custom cache because organization queries are stored there long term
retval = inv.cache.add(inv.cache.custom.table, objId)
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.items.organize.clearCR: Failed to add organize queries to custom cache for object " ..
objId .. dbot.retval.getString(retval))
end -- if
inv.cache.saveCustom()
dbot.info("Cleared all organization queries from container \"" ..
(inv.items.getField(objId, invFieldColorName) or "Unidentified") .. DRL_ANSI_WHITE .. "@W\"")
(inv.items.getField(objId, invFieldColorName) or "Unidentified") .. DRL_ANSI_WHITE .. "@W\"")
-- Clean up, print an end tag (if necessary) and return
-- Clean up, print an end tag (if necessary) and return
@ -9211,6 +9289,7 @@ function inv.items.trigger.itemDataStart(dataType, containerId)
-- We are scanning worn items, main inventory items, keyring items, or a container
-- We are scanning worn items, main inventory items, keyring items, or a container
if (dataType == "eqdata") then
if (dataType == "eqdata") then
inv.items.discoverPkg.loc = invItemLocWorn
inv.items.discoverPkg.loc = invItemLocWorn
elseif (dataType == "invdata") then
elseif (dataType == "invdata") then
containerIdNum = tonumber(containerId)
containerIdNum = tonumber(containerId)
if (containerIdNum == nil) then
if (containerIdNum == nil) then
@ -9218,12 +9297,14 @@ function inv.items.trigger.itemDataStart(dataType, containerId)
else
else
inv.items.discoverPkg.loc = containerId
inv.items.discoverPkg.loc = containerId
end -- if
end -- if
elseif (dataType == "keyring") then
elseif (dataType == "keyring") then
inv.items.discoverPkg.loc = invItemLocKeyring
inv.items.discoverPkg.loc = invItemLocKeyring
else
else
dbot.error("inv.items.trigger.itemDataStart: invalid dataType parameter ")
dbot.debug("inv.items.trigger.itemDataStart: Could not find target item ")
inv.items.trigger.itemDataEnd() -- clean up state
inv.items.trigger.itemDataEnd() -- clean up state
return DRL_RET_INTERNAL_ERROR
return DRL_RET_MISSING_ENTRY
end -- if
end -- if
-- Watch for the eqdata, invdata, or keyring end tag so that we can stop scanning
-- Watch for the eqdata, invdata, or keyring end tag so that we can stop scanning
@ -9315,6 +9396,7 @@ function inv.items.trigger.itemDataStats(objId, flags, itemName, level, typeFiel
inv.items.setField(objId, invFieldColorName, itemName)
inv.items.setField(objId, invFieldColorName, itemName)
end -- if
end -- if
end -- if
end -- if
else -- we got here from an eqdata or invdata request
else -- we got here from an eqdata or invdata request
-- Remember that we saw this item during a discovery/refresh. This lets us prune items that
-- Remember that we saw this item during a discovery/refresh. This lets us prune items that
-- are listed in the inventory table but are no longer in our inventory. This situation could
-- are listed in the inventory table but are no longer in our inventory. This situation could
@ -9998,6 +10080,16 @@ invItemEffectsShield = "shield"
-- pill, food, etc. matches a key in the cache. In that case, we use the cached details
-- pill, food, etc. matches a key in the cache. In that case, we use the cached details
-- without performing the identification.
-- without performing the identification.
--
--
-- The "custom" cache contains user-specified customizations to items. For example, an
-- item may have been assigned a custom keyword or a container may have been assigned one
-- or more organize queries. The recent cache can hold some items including any customizations
-- for each item. However, there can be a lot of turnover in the recent cache -- especially
-- during catastrophic events such as dying where your entire inventory may overflow the cache.
-- Entries in the "custom" cache are much longer-lived than entries in the recent cache and
-- this greatly reduces the odds that someone will lose a customization that they actually
-- want. Also, the custom cache can support more entries than the recent cache because only
-- custom fields need to be saved.
--
-- Functions:
-- Functions:
-- inv.cache.init.atActive()
-- inv.cache.init.atActive()
-- inv.cache.fini(doSaveState)
-- inv.cache.fini(doSaveState)
@ -10005,7 +10097,15 @@ invItemEffectsShield = "shield"
-- inv.cache.config(cacheName, numEntries)
-- inv.cache.config(cacheName, numEntries)
-- inv.cache.save()
-- inv.cache.save()
-- inv.cache.load()
-- inv.cache.load()
--
-- inv.cache.saveRecent()
-- inv.cache.saveFrequent()
-- inv.cache.saveCustom()
--
-- inv.cache.reset() -- reset all caches
-- inv.cache.reset() -- reset all caches
-- inv.cache.resetRecent()
-- inv.cache.resetFrequent()
-- inv.cache.resetCustom()
--
--
-- inv.cache.resetCache(cacheName) -- reset a single specific cache
-- inv.cache.resetCache(cacheName) -- reset a single specific cache
-- inv.cache.add
-- inv.cache.add
@ -10022,27 +10122,34 @@ invItemEffectsShield = "shield"
-- Data:
-- Data:
-- inv.cache.recent.table
-- inv.cache.recent.table
-- inv.cache.frequent.table
-- inv.cache.frequent.table
-- inv.cache.custom.table
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
inv.cache = {}
inv.cache = {}
inv.cache.init = {}
inv.cache.init = {}
inv.cache.recent = {}
inv.cache.recent = {}
inv.cache.frequent = {}
inv.cache.frequent = {}
inv.cache.custom = {}
inv.cache.recent.table = nil
inv.cache.recent.table = nil
inv.cache.frequent.table = nil
inv.cache.frequent.table = nil
inv.cache.custom.table = nil
inv.cache.recent.name = "recent"
inv.cache.recent.name = "recent"
inv.cache.frequent.name = "frequent"
inv.cache.frequent.name = "frequent"
inv.cache.custom.name = "custom"
inv.cache.recent.stateName = "inv-cache-recent.state"
inv.cache.recent.stateName = "inv-cache-recent.state"
inv.cache.frequent.stateName = "inv-cache-frequent.state"
inv.cache.frequent.stateName = "inv-cache-frequent.state"
inv.cache.custom.stateName = "inv-cache-custom.state"
inv.cache.recent.defaultNumEntries = 1000
inv.cache.recent.defaultNumEntries = 1000
inv.cache.frequent.defaultNumEntries = 100
inv.cache.frequent.defaultNumEntries = 100
inv.cache.custom.defaultNumEntries = 1500
inv.cache.recent.prunePercent = 0.2
inv.cache.recent.prunePercent = 0.20
inv.cache.frequent.prunePercent = 0.2
inv.cache.frequent.prunePercent = 0.10
inv.cache.custom.prunePercent = 0.05
function inv.cache.init.atActive()
function inv.cache.init.atActive()
@ -10074,7 +10181,11 @@ end -- inv.cache.fini
function inv.cache.config(cacheName, numEntries)
function inv.cache.config(cacheName, numEntries)
if (cacheName ~= inv.cache.recent.name) and (cacheName ~= inv.cache.frequent.name) then
local retval = DRL_RET_SUCCESS
if (cacheName ~= inv.cache.recent.name) and
(cacheName ~= inv.cache.frequent.name) and
(cacheName ~= inv.cache.custom.name) then
dbot.warn("inv.cache.config: Invalid cache name \"" .. (cacheName or "nil") .. "\"")
dbot.warn("inv.cache.config: Invalid cache name \"" .. (cacheName or "nil") .. "\"")
return DRL_RET_INVALID_PARAM
return DRL_RET_INVALID_PARAM
end -- if
end -- if
@ -10092,64 +10203,117 @@ function inv.cache.config(cacheName, numEntries)
if (cacheName == inv.cache.recent.name) then
if (cacheName == inv.cache.recent.name) then
inv.cache.recent.table = cache
inv.cache.recent.table = cache
retval = inv.cache.saveRecent()
elseif (cacheName == inv.cache.frequent.name) then
elseif (cacheName == inv.cache.frequent.name) then
inv.cache.frequent.table = cache
inv.cache.frequent.table = cache
retval = inv.cache.saveFrequent()
elseif (cacheName == inv.cache.custom.name) then
inv.cache.custom.table = cache
retval = inv.cache.saveCustom()
else
else
dbot.error("inv.cache.config: Invalid cache name detected: \"" .. (cacheName or "nil") .. "\"")
dbot.error("inv.cache.config: Invalid cache name detected: \"" .. (cacheName or "nil") .. "\"")
return DRL_RET_INTERNAL_ERROR
return DRL_RET_INTERNAL_ERROR
end -- if
end -- if
inv.cache.save()
return retval
return DRL_RET_SUCCESS
end -- inv.cache.config
end -- inv.cache.config
function inv.cache.save()
function inv.cache.save()
local recentRetval = DRL_RET_SUCCESS
local recentRetval = inv.cache.saveRecent()
local frequentRetval = DRL_RET_SUCCESS
local frequentRetval = inv.cache.saveFrequent()
local customRetval = inv.cache.saveCustom()
if (recentRetval ~= DRL_RET_SUCCESS) then
return recentRetval
elseif (frequentRetval ~= DRL_RET_SUCCESS) then
return frequentRetval
else
return customRetval
end -- if
end -- inv.cache.save
function inv.cache.saveRecent()
local retval = DRL_RET_SUCCESS
if (inv.cache.recent.table ~= nil) then
if (inv.cache.recent.table ~= nil) then
recentRetval = dbot.storage.saveTable(dbot.backup.getCurrentDir() .. inv.cache.recent.stateName,
retval = dbot.storage.saveTable(dbot.backup.getCurrentDir() .. inv.cache.recent.stateName,
"inv.cache.recent.table", inv.cache.recent.table)
"inv.cache.recent.table", inv.cache.recent.table)
if (recentRetval ~= DRL_RET_SUCCESS) then
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.cache.save: Failed to save cache.recent table: " .. dbot.retval.getString(recentRetval))
dbot.warn("inv.cache.saveRecent: Failed to save cache.recent table: " ..
dbot.retval.getString(retval))
end -- if
end -- if
end -- if
end -- if
return retval
end -- inv.cache.saveRecent
function inv.cache.saveFrequent()
local retval = DRL_RET_SUCCESS
if (inv.cache.frequent.table ~= nil) then
if (inv.cache.frequent.table ~= nil) then
frequentRetval = dbot.storage.saveTable(dbot.backup.getCurrentDir() .. inv.cache.frequent.stateName,
retval = dbot.storage.saveTable(dbot.backup.getCurrentDir() .. inv.cache.frequent.stateName,
"inv.cache.frequent.table", inv.cache.frequent.table)
"inv.cache.frequent.table", inv.cache.frequent.table)
if (frequentRetval ~= DRL_RET_SUCCESS) then
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.cache.save: Failed to save cache.frequent table: " ..
dbot.warn("inv.cache.saveFrequent : Failed to save cache.frequent table: " ..
dbot.retval.getString(frequentRetval))
dbot.retval.getString(retval))
end -- if
end -- if
end -- if
end -- if
if (recentRetval ~= DRL_RET_SUCCESS) then
return retval
return recentRetval
end -- inv.cache.saveFrequent
else
return frequentRetval
function inv.cache.saveCustom()
local retval = DRL_RET_SUCCESS
if (inv.cache.custom.table ~= nil) then
retval = dbot.storage.saveTable(dbot.backup.getCurrentDir() .. inv.cache.custom.stateName,
"inv.cache.custom.table", inv.cache.custom.table)
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.cache.saveCustom: Failed to save cache.custom table: " ..
dbot.retval.getString(retval))
end -- if
end -- if
end -- if
end -- inv.cache.save
return retval
end -- inv.cache.saveCustom
function inv.cache.load()
function inv.cache.load()
local retval = DRL_RET_SUCCESS
local recentRetval = dbot.storage.loadTable(dbot.backup.getCurrentDir() .. inv.cache.recent.stateName,
local recentRetval = dbot.storage.loadTable(dbot.backup.getCurrentDir() .. inv.cache.recent.stateName,
inv.cache.reset)
inv.cache.resetRecent )
if (recentRetval ~= DRL_RET_SUCCESS) then
if (recentRetval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.cache.load: Failed to load cache table from file \"@R" ..
dbot.warn("inv.cache.load: Failed to load cache table from file \"@R" ..
dbot.backup.getCurrentDir() .. inv.cache.recent.stateName .. "@W\": " ..
dbot.backup.getCurrentDir() .. inv.cache.recent.stateName .. "@W\": " ..
dbot.retval.getString(recentRetval))
dbot.retval.getString(recentRetval))
retval = recentRetval
end -- if
end -- if
local frequentRetval = dbot.storage.loadTable(dbot.backup.getCurrentDir() .. inv.cache.frequent.stateName,
local frequentRetval = dbot.storage.loadTable(dbot.backup.getCurrentDir() .. inv.cache.frequent.stateName,
inv.cache.reset)
inv.cache.resetFrequent )
if (frequentRetval ~= DRL_RET_SUCCESS) then
if (frequentRetval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.cache.load: Failed to load cache table from file \"@R" ..
dbot.warn("inv.cache.load: Failed to load cache table from file \"@R" ..
dbot.backup.getCurrentDir() .. inv.cache.frequent.stateName .. "@W\": " ..
dbot.backup.getCurrentDir() .. inv.cache.frequent.stateName .. "@W\": " ..
dbot.retval.getString(frequentRetval))
dbot.retval.getString(frequentRetval))
retval = frequentRetval
end -- if
local customRetval = dbot.storage.loadTable(dbot.backup.getCurrentDir() .. inv.cache.custom.stateName,
inv.cache.resetCustom)
if (customRetval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.cache.load: Failed to load cache table from file \"@R" ..
dbot.backup.getCurrentDir() .. inv.cache.custom.stateName .. "@W\": " ..
dbot.retval.getString(customRetval))
retval = customRetval
end -- if
end -- if
if (inv.version.table ~= nil) and (inv.version.table.tableFormat ~= nil) and
if (inv.version.table ~= nil) and (inv.version.table.tableFormat ~= nil) and
@ -10168,45 +10332,71 @@ function inv.cache.load()
end -- if
end -- if
else
else
dbot.error("inv.cache.load: Missing inv.version components")
dbot.error("inv.cache.load: Missing inv.version components")
return DRL_RET_INTERNAL_ERROR
retval = DRL_RET_INTERNAL_ERROR
end -- if
end -- if
return retval
end -- inv.cache.load
function inv.cache.reset()
local recentRetval = inv.cache.resetRecent()
local frequentRetval = inv.cache.resetFrequent()
local customRetval = inv.cache.resetCustom()
if (recentRetval ~= DRL_RET_SUCCESS) then
if (recentRetval ~= DRL_RET_SUCCESS) then
return recentRetval
return recentRetval
else
elseif (frequentRetval ~= DRL_RET_SUCCESS) then
return frequentRetval
return frequentRetval
else
return customRetval
end -- if
end -- if
end -- inv.cache.load
end -- inv.cache.reset
function inv.cache.reset()
function inv.cache.resetRecent()
local recentRetval = DRL_RET_SUCCESS
local retval = DRL_RET_SUCCESS
local frequentRetval = DRL_RET_SUCCESS
if (inv.cache.recent ~= nil) then
if (inv.cache.recent ~= nil) then
recentRe tval = inv.cache.resetCache(inv.cache.recent.name)
retval = inv.cache.resetCache(inv.cache.recent.name)
if (recentRe tval ~= DRL_RET_SUCCESS) then
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.cache.reset: recent cache reset failed: " .. dbot.retval.getString(recentRetval))
dbot.warn("inv.cache.resetRecent : recent cache reset failed: " .. dbot.retval.getString(recentRetval))
end -- if
end -- if
end -- if
end -- if
return retval
end -- inv.cache.resetRecent
function inv.cache.resetFrequent()
local retval = DRL_RET_SUCCESS
if (inv.cache.frequent ~= nil) then
if (inv.cache.frequent ~= nil) then
frequentRetval = inv.cache.resetCache(inv.cache.frequent.name)
retval = inv.cache.resetCache(inv.cache.frequent.name)
if (frequentRetval ~= DRL_RET_SUCCESS) then
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.cache.reset: frequent cache reset failed: " .. dbot.retval.getString(frequentRetval))
dbot.warn("inv.cache.resetFrequent: frequent cache reset failed: " ..
dbot.retval.getString(frequentRetval))
end -- if
end -- if
end -- if
end -- if
-- If the recent cache reset failed, return that error code. Otherwise, return the frequent
return retval
-- cache reset's return value.
end -- inv.cache.resetFrequent
if (recentRetval ~= DRL_RET_SUCCESS) then
return recentRetval
else
function inv.cache.resetCustom()
return frequentRetval
local retval = DRL_RET_SUCCESS
if (inv.cache.custom ~= nil) then
retval = inv.cache.resetCache(inv.cache.custom.name)
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.cache.resetCustom: custom cache reset failed: " .. dbot.retval.getString(customRetval))
end -- if
end -- if
end -- if
end -- inv.cache.reset
return retval
end -- inv.cache.resetCustom
function inv.cache.resetCache(cacheName)
function inv.cache.resetCache(cacheName)
@ -10221,6 +10411,8 @@ function inv.cache.resetCache(cacheName)
numEntries = inv.cache.recent.defaultNumEntries
numEntries = inv.cache.recent.defaultNumEntries
elseif (cacheName == inv.cache.frequent.name) then
elseif (cacheName == inv.cache.frequent.name) then
numEntries = inv.cache.frequent.defaultNumEntries
numEntries = inv.cache.frequent.defaultNumEntries
elseif (cacheName == inv.cache.custom.name) then
numEntries = inv.cache.custom.defaultNumEntries
end -- if
end -- if
local retval = inv.cache.config(cacheName, numEntries)
local retval = inv.cache.config(cacheName, numEntries)
@ -10245,21 +10437,26 @@ function inv.cache.add(cache, objId)
return DRL_RET_MISSING_ENTRY
return DRL_RET_MISSING_ENTRY
end -- if
end -- if
local cacheEntry = { timeCached = dbot.getTime(), entry = dbot.table.getCopy(entry) }
-- Cache the object if we've done some type of identification on it
-- Cache the object if we've done some type of identification on it
local idLevel = inv.items.getField(objId, invFieldIdentifyLevel)
local idLevel = inv.items.getField(objId, invFieldIdentifyLevel)
if (idLevel ~= nil) and (idLevel ~= invIdLevelNone) then
if (idLevel ~= nil) then
if (cache.name == inv.cache.recent.name) then
if (cache.name == inv.cache.recent.name) then
cache.entries[objId] = cacheEntry
cache.entries[objId] = { timeCached = dbot.getTime(), entry = dbot.table.getCopy(entry) }
elseif (cache.name == inv.cache.frequent.name) then
elseif (cache.name == inv.cache.frequent.name) then
local name = inv.items.getStatField(objId, invStatFieldName)
local name = inv.items.getStatField(objId, invStatFieldName)
if (name ~= nil) and (name ~= "") then
if (name ~= nil) and (name ~= "") then
cache.entries[name] = cacheEntry
cache.entries[name] = { timeCached = dbot.getTime(), entry = dbot.table.getCopy(entry) }
end -- if
end -- if
elseif (cache.name == inv.cache.custom.name) then
local newEntry = {}
newEntry.keywords = inv.items.getStatField(objId, invStatFieldKeywords) or ""
newEntry.organize = inv.items.getStatField(objId, invQueryKeyOrganize) or ""
cache.entries[objId] = { timeCached = dbot.getTime(), entry = newEntry }
else
dbot.warn("inv.cache.add: Unknown cache name \"" .. (cache.name or "nil") .. "\"")
end -- if
end -- if
dbot.note("FIXME: Added \"" .. (inv.items.getField(objId, "colorName") or "Unidentified") .. "\" " ..
dbot.debug(" Added \"" .. (inv.items.getField(objId, "colorName") or "Unidentified") .. "@W \" " ..
"to the \"" .. cache.name .. "\" cache")
"to the \"" .. cache.name .. "\" cache")
end -- if
end -- if
@ -10273,9 +10470,11 @@ function inv.cache.add(cache, objId)
end -- if
end -- if
-- Note: To cut down on overhead, we currently do not call inv.cache.save() here. Instead,
-- Note: To cut down on overhead, we currently do not call inv.cache.save() here. Instead,
-- the cache is saved in inv.cache.fini when we disconnect or reload the plugin. There
-- the lossy caches (recent and frequent) are saved in inv.cache.fini. There is a chance
-- is a chance we may miss some cache updates this way if mush exits uncleanly, but the
-- we may miss some cache updates this way if mush exits uncleanly, but the downside is
-- downside is low because we we'll just re-identify anything we need if that happens.
-- low because we we'll just re-identify anything we need if that happens. On the other
-- hand, we must save the custom cache after a batch add so that we don't lose what the
-- user entered.
return retval
return retval
end -- inv.cache.add
end -- inv.cache.add
@ -10286,9 +10485,9 @@ function inv.cache.remove(cache, key)
assert(key ~= nil, "Received nil key!!!")
assert(key ~= nil, "Received nil key!!!")
local cacheKey = key
local cacheKey = key
-- The recent cache uses a numeric object ID as a key and we do a little extra parameter
-- The recent and custom caches use a numeric object ID as a key and we do a little extra parameter
-- checking in this case because I'm paranoid...
-- checking in this case because I'm paranoid...
if (cache.name == inv.cache.recent.name) then
if (cache.name == inv.cache.recent.name) or (cache.name == inv.cache.custom.name) then
cacheKey = tonumber(key)
cacheKey = tonumber(key)
if (cacheKey == nil) then
if (cacheKey == nil) then
dbot.warn("inv.cache.remove: failed to remove item for non-numeric objId key " .. key)
dbot.warn("inv.cache.remove: failed to remove item for non-numeric objId key " .. key)
@ -10325,23 +10524,56 @@ function inv.cache.prune(cache)
numEntriesToPrune = math.floor(numEntriesInCache * inv.cache.recent.prunePercent) + 1 or 0
numEntriesToPrune = math.floor(numEntriesInCache * inv.cache.recent.prunePercent) + 1 or 0
elseif (cache.name == inv.cache.frequent.name) then
elseif (cache.name == inv.cache.frequent.name) then
numEntriesToPrune = math.floor(numEntriesInCache * inv.cache.frequent.prunePercent) + 1 or 0
numEntriesToPrune = math.floor(numEntriesInCache * inv.cache.frequent.prunePercent) + 1 or 0
elseif (cache.name == inv.cache.custom.name) then
numEntriesToPrune = math.floor(numEntriesInCache * inv.cache.custom.prunePercent) + 1 or 0
end -- if
end -- if
dbot.note ("The " .. cache.name .. " cache is full, removing the " ..
dbot.debug ("The " .. cache.name .. " cache is full, removing the " ..
numEntriesToPrune .. " least recently used items")
numEntriesToPrune .. " least recently used items")
-- Sort the cache entries by the date they were last used . We create a temporary array of the
-- Sort the cache entries. We create a temporary array of the entries so that we can sort them
-- entries so that we can sort them (you can't sort a table.)
-- (you can't sort a table.)
local entryArray = {}
local entryArray = {}
-- The custom cache only prunes items that aren't currently in inventory. If an item is not
-- in inventory, we first get rid of items that are no longer even in the recent cache before
-- we remove recently cached items. If all else fails, we sort things by cache time.
if (cache.name == inv.cache.custom.name) then
for k,v in pairs(cache.entries) do
if (inv.items.getEntry(k) == nil) then
table.insert(entryArray, { key=k, timeCached=v.timeCached })
end -- if
end -- for
table.sort(entryArray,
function (e1, e2)
local recent1 = inv.cache.recent.table.entries[e1]
local recent2 = inv.cache.recent.table.entries[e2]
if (recent1 == nil) and (recent2 ~= nil) then
return true
elseif (recent1 ~= nil) and (recent2 == nil) then
return false
else
return e1.timeCached < e2.timeCached
end -- if
end) -- function
-- The recent and frequent caches sort by the last access time
else
for k,v in pairs(cache.entries) do
for k,v in pairs(cache.entries) do
table.insert(entryArray, { key=k, timeCached=v.timeCached })
table.insert(entryArray, { key=k, timeCached=v.timeCached })
end -- for
end -- for
table.sort(entryArray, function (entry1, entry2) return entry1.timeCached < entry2.timeCached e n d )
table.sort(entryArray, function (entry1, entry2) return entry1.timeCached < entry2.timeCached e n d )
end -- if
-- Remove the "numEntriesToPrune" first entries in the array
-- Remove the "numEntriesToPrune" first entries in the array
for i = 1, numEntriesToPrune do
for i = 1, numEntriesToPrune do
if (entryArray[i] == nil) then
break
end -- if
local key = entryArray[i].key
local key = entryArray[i].key
retval = inv.cache.remove(cache, entryArray[i].key)
retval = inv.cache.remove(cache, key)
if (retval ~= DRL_RET_SUCCESS) then
if (retval ~= DRL_RET_SUCCESS) then
dbot.warn("inv.cache.prune: Failed to remove cache item " .. key)
dbot.warn("inv.cache.prune: Failed to remove cache item " .. key)
break
break
@ -10360,9 +10592,9 @@ function inv.cache.get(cache, key)
local cacheKey = key
local cacheKey = key
-- The recent cache uses a numeric object ID as a key and we do a little extra parameter
-- The recent and custom caches use a numeric object ID as a key and we do a little extra parameter
-- checking in this case because I'm paranoid...
-- checking in this case because I'm paranoid...
if (cache.name == inv.cache.recent.name) then
if (cache.name == inv.cache.recent.name) or (cache.name == inv.cache.custom.name) then
cacheKey = tonumber(key)
cacheKey = tonumber(key)
if (cacheKey == nil) then
if (cacheKey == nil) then
dbot.warn("inv.cache.get: failed to get item for non-numeric objId key " .. key)
dbot.warn("inv.cache.get: failed to get item for non-numeric objId key " .. key)
@ -10391,14 +10623,30 @@ end -- inv.cache.getSize
function inv.cache.setSize(cache, numEntries)
function inv.cache.setSize(cache, numEntries)
local retval = DRL_RET_SUCCESS
assert(cache ~= nil, "Cache is nil!!!")
assert(cache ~= nil, "Cache is nil!!!")
assert(tonumber(numEntries) ~= nil, "numEntries parameter is not numeric!")
assert(tonumber(numEntries) ~= nil, "numEntries parameter is not numeric!")
cache.maxEntries = numEntries
cache.maxEntries = numEntries
return inv.cache.save()
if (cache.name == inv.cache.recent.name) then
retval = inv.cache.saveRecent()
end -- inv.cache.getSize
elseif (cache.name == inv.cache.frequent.name) then
retval = inv.cache.saveFrequent()
elseif (cache.name == inv.cache.custom.name) then
retval = inv.cache.saveCustom()
else
dbot.warn("inv.cache.setSize: Invalid cache name detected: \"" .. (cache.name or "nil") .. "\"")
retval = DRL_RET_INTERNAL_ERROR
end -- if
return retval
end -- inv.cache.setSize
function inv.cache.dump(cache)
function inv.cache.dump(cache)