-- The Archive is in the default location, ACHV_DB
_G.Archivist=Archivist
localloader=CreateFrame("frame")
loader:RegisterEvent("ADDON_LOADED")
loader:SetScript("OnEvent",function(self,_,addon)
ifaddon==addonNamethen
iftype(ACHV_DB)~="table"then
ACHV_DB={}
end
Archivist:Initialize(ACHV_DB)
self:UnregisterEvent("ADDON_LOADED")
end
end)
end
end
functionArchivist:Assert(valid,pattern,...)
ifnotvalidthen
ifpatternthen
error(pattern:format(...),2)
else
error("Archivist encountered an unknown error.",2)
end
end
end
functionArchivist:Warn(valid,pattern,...)-- Like assert, but doesn't interrupt execution
ifnotvalidandself.debugthen
ifpatternthen
print(pattern:format(...),2)
else
print("Archivist encountered an unknown warning.")
end
returntrue
end
end
functionArchivist:IsInitialized()
returnself.initialized
end
-- Give Archivist its archive to play with. Called automatically, unless Archivist has been embedded.
functionArchivist:Initialize(sv)
do-- arg validation
self:Assert(notself:IsInitialized(),"Archivist has already been initialized.")
self:Assert(type(sv)=="table","Attempt to initialize Archivist SavedVariables with a %q instead of a table.",type(sv))
end
self.sv=sv
self.initialized=true
forid,prototypeinpairs(self.prototypes)do
self.sv[id]=self.sv[id]or{}
ifprototype.Initthen
prototype:Init()
end
end
end
-- Shut Archivist down
functionArchivist:DeInitialize()
ifself:IsInitialized()then
self.initialized=false
self:CloseAllStores()
self.sv=nil
end
end
-- register a store type with Archivist
-- prototype fields:
-- id - unique identifier. Preferably also a descriptive name, like "simple" or "snapshot".
-- version - positive integer. Used for version control, in case any data migrations are needed. Registration will fail if the prototype is outdated.
-- Init - function (optional). If provided, executes exactly once per session, before any other methods are called.
-- Create - function (required). Create a brand new active store object.
-- Update - function (optional). Massage archived data into a format that Open can accept. Useful for data migrations.
-- Open - function (requried). Create from the provided data an active store object. Prototype may assume ownership of the provided data however it wishes.
-- Commit - function (required). Return an image of the data that should be archived.
-- Close - function (required). Release ownership of active store object. Optionally, return image of data to write into archive.
-- Delete - function (optional). If provided, called when a store is deleted. Useful for cleaning up sub stores.
-- Please note that Create, Open, Update (if provided), Commit, and Close may be called at any time if Archivist deems it necessary.
-- Thus, these methods should ideally be as close to purely functional as is practical, to minimize friction.
functionArchivist:RegisterStoreType(prototype)
do-- prototype validation
self:Assert(type(prototype)=="table","Invalid argument #1 to RegisterStoreType: Expected table, got %q instead.",type(prototype))
-- prototype is now guaranteed to be indexable
self:Assert(type(prototype.id)=="string","Invalid prototype field 'id': Expected string, got %q instead.",type(prototype.id))
self:Assert(type(prototype.version)=="number","Invalid prototype field 'version': Expected number, got %q instead.",type(prototype.version))
"Prototype %q version expected to be a positive integer, but got %d instead.",prototype.id,prototype.version)then
return
end
localoldPrototype=self.prototypes[prototype.id]
self:Assert(notoldPrototypeorprototype.version>=oldPrototype.version,"Store type %q already exists with a higher version",oldPrototypeandoldPrototype.version)
-- prototype is now guaranteed to be either new or an Update to existing prototype
self:Assert(prototype.Init==nilortype(prototype.Init)=="function","Invalid prototype field 'Init': Expected function, got %q instead.",type(prototype.Init))
self:Assert(type(prototype.Create)=="function","Invalid prototype field 'Create': Expected function, got %q instead.",type(prototype.Create))
self:Assert(type(prototype.Open)=="function","Invalid prototype field 'Open': Expected function, got %q instead.",type(prototype.Open))
self:Assert(prototype.Update==nilortype(prototype.Update)=="function","Invalid prototype field 'Update': Expected function, got %q instead.",type(prototype.Update))
self:Assert(type(prototype.Commit)=="function","Invalid prototype field 'Commit': Expected function, got %q instead.",type(prototype.Commit))
self:Assert(type(prototype.Close)=="function","Invalid prototype field 'Close': Expected function, got %q instead.",type(prototype.Close))
self:Assert(prototype.Delete==nilortype(prototype.Delete)=="function","Invalid prototype field 'Delete': Expected function, got %q instead.",type(prototype.Delete))
-- prototype is now guaranteed to have Init, Create, Open, Update functions, and is thus well-formed.
end
localoldPrototype=self.prototypes[prototype.id]-- need in case of closing active stores
self:Assert(store~=nil,"Failed to create a new store of type %q.",storeType)
self:Assert(self.storeMap[store]==nil,"Store Type %q produced an store object already registered with Archivist instead of creating a new one.",storeType)
end
ifid==nilthen
id=self:GenerateID()
end
self.activeStores[storeType][id]=store
self.storeMap[store]={
id=id,
prototype=self.prototypes[storeType],
type=storeType
}
ifimage==nilthen
-- save initial image via Commit
image=self.prototypes[storeType]:Commit(store)
end
self:Assert(image~=nil,"Create Verb failed to generate initial image for archive.")
self.sv[storeType][id]={
timestamp=time(),
version=self.prototypes[storeType].version,
data=self:Archive(image)
}
returnstore,id
end
-- clones archived data and/or active store object to newId
-- also provides an active store object of the cloned data if openStore is set
-- Closes store (if open), then deletes data from archive
-- Prototype is given opportunity to perform actions using image (usually, to delete other sub stores)
-- if store type is not registered, then force flag must be set in order to delete data,
-- to reduce the chance of accidents
functionArchivist:Delete(storeType,id,force)
do-- arg validation
self:Warn(forceortype(storeType=="string")andself.sv[storeType],"There are no stores to delete.")
self:Assert(forceorself.prototypes[storeType],"Store type should be registered before deleting a store. Call Delete again with arg #3 == true to override this.")
self:Assert(self.storeMap[store],"Unrecognized store was provided.")
localinfo=self.storeMap[store]
returnself:Delete(info.type,info.id)
end
-- unpacks data in the archive into an active store object
-- if store is already active, then returns active store object
functionArchivist:Open(storeType,id,...)
do-- arg validation
self:Assert(type(storeType)=="string"andself.prototypes[storeType],"Store type must be registered before opening a store.")
self:Assert(type(id)=="string"and(self.sv[storeType][id]orself.activeStores[storeType][id]),"Could not find a store with that ID. Did you mean to call Archivist:Create?")