mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 13:53:45 +03:00
Upload
This commit is contained in:
439
lua/pac3/libraries/resource.lua
Normal file
439
lua/pac3/libraries/resource.lua
Normal file
@@ -0,0 +1,439 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local resource = {}
|
||||
local luadata = include("luadata.lua")
|
||||
|
||||
local function llog(...)
|
||||
print("[resource] ", ...)
|
||||
end
|
||||
|
||||
local function wlog(...)
|
||||
print("[resource warning] ", ...)
|
||||
end
|
||||
|
||||
local function R(path)
|
||||
if file.Exists(path, "DATA") then
|
||||
return path
|
||||
end
|
||||
end
|
||||
|
||||
local function utility_CreateCallbackThing(cache)
|
||||
cache = cache or {}
|
||||
local self = {}
|
||||
|
||||
function self:check(path, callback, extra)
|
||||
if cache[path] then
|
||||
if cache[path].extra_callbacks then
|
||||
for key, old in pairs(cache[path].extra_callbacks) do
|
||||
local callback = extra[key]
|
||||
if callback then
|
||||
cache[path].extra_callbacks[key] = function(...)
|
||||
old(...)
|
||||
callback(...)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if cache[path].callback then
|
||||
if not istable(cache[path].callback) then cache[path].callback = {cache[path].callback} end
|
||||
table.insert(cache[path].callback, callback)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function self:start(path, callback, extra)
|
||||
if not istable(callback) then callback = {callback} end
|
||||
cache[path] = {callback = table.Copy(callback), extra_callbacks = table.Copy(extra or {})}
|
||||
end
|
||||
|
||||
function self:callextra(path, key, out)
|
||||
if not cache[path] or not cache[path].extra_callbacks[key] then return end
|
||||
return cache[path].extra_callbacks[key](out)
|
||||
end
|
||||
|
||||
function self:stop(path, out, ...)
|
||||
if not cache[path] then return end
|
||||
|
||||
if istable(cache[path].callback) then
|
||||
for i, func in ipairs(cache[path].callback) do
|
||||
func(out, ...)
|
||||
end
|
||||
elseif cache[path].callback then
|
||||
cache[path].callback(out, ...)
|
||||
end
|
||||
|
||||
cache[path] = out
|
||||
end
|
||||
|
||||
function self:get(path)
|
||||
return cache[path]
|
||||
end
|
||||
|
||||
function self:uncache(path)
|
||||
cache[path] = nil
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local DOWNLOAD_FOLDER = "pac3_cache/downloads/"
|
||||
local etags_file = "pac3_cache/resource_etags.txt"
|
||||
|
||||
file.CreateDir(DOWNLOAD_FOLDER)
|
||||
|
||||
local maxAgeConvar = CreateConVar("pac_downloads_cache_maxage", "604800", FCVAR_ARCHIVE, "Maximum age of cache entries in seconds, default is 1 week.")
|
||||
local function clearCacheAfter( time )
|
||||
for _, fileName in ipairs(file.Find(DOWNLOAD_FOLDER .. "*", "DATA")) do
|
||||
local fullPath = DOWNLOAD_FOLDER .. fileName
|
||||
|
||||
if file.Time(fullPath, "DATA") < time then
|
||||
file.Delete(fullPath)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
clearCacheAfter(os.time() - maxAgeConvar:GetInt())
|
||||
|
||||
local function rename_file(a, b)
|
||||
local str_a = file.Read(a, "DATA")
|
||||
file.Delete(a, "DATA")
|
||||
file.Write(b, str_a)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function download(from, to, callback, on_fail, on_header, check_etag, etag_path_override, need_extension)
|
||||
if check_etag then
|
||||
local data = luadata.ReadFile(etags_file)
|
||||
local etag = data[etag_path_override or from]
|
||||
|
||||
--llog("checking if ", etag_path_override or from, " has been modified. etag is: ", etag)
|
||||
|
||||
HTTP({
|
||||
method = "HEAD",
|
||||
url = pac.FixGMODUrl(from),
|
||||
success = function(code, body, header)
|
||||
local res = header.ETag or header["Last-Modified"]
|
||||
|
||||
if not res then return end
|
||||
|
||||
if res ~= etag then
|
||||
if etag then
|
||||
llog(from, ": etag has changed ", res)
|
||||
else
|
||||
llog(from, ": no previous etag stored", res)
|
||||
end
|
||||
download(from, to, callback, on_fail, on_header, nil, etag_path_override, need_extension)
|
||||
else
|
||||
--llog(from, ": etag is the same")
|
||||
check_etag()
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local file
|
||||
|
||||
local allowed = {
|
||||
[".txt"] = true,
|
||||
[".jpg"] = true,
|
||||
[".png"] = true,
|
||||
[".vtf"] = true,
|
||||
[".dat"] = true,
|
||||
}
|
||||
|
||||
return pac.HTTPGet(
|
||||
from,
|
||||
function(body, len, header)
|
||||
do
|
||||
if need_extension then
|
||||
local ext = header["Content-Type"] and (header["Content-Type"]:match(".-/(.-);") or header["Content-Type"]:match(".-/(.+)")) or "dat"
|
||||
if ext == "jpeg" then ext = "jpg" end
|
||||
|
||||
if body:StartWith("VTF") then
|
||||
ext = "vtf"
|
||||
end
|
||||
|
||||
if allowed["." .. ext] then
|
||||
to = to .. "." .. ext
|
||||
else
|
||||
to = to .. ".dat"
|
||||
end
|
||||
end
|
||||
|
||||
local file_, err = _G.file.Open(DOWNLOAD_FOLDER .. to .. "_temp.dat", "wb", "DATA")
|
||||
file = file_
|
||||
|
||||
if not file then
|
||||
llog("resource download error: ", err)
|
||||
on_fail()
|
||||
return false
|
||||
end
|
||||
|
||||
local etag = header.ETag or header["Last-Modified"]
|
||||
|
||||
if etag then
|
||||
local data = luadata.ReadFile(etags_file) or {}
|
||||
data[etag_path_override or from] = etag
|
||||
luadata.WriteFile(etags_file)
|
||||
end
|
||||
|
||||
on_header(header)
|
||||
end
|
||||
|
||||
file:Write(body)
|
||||
file:Close()
|
||||
|
||||
|
||||
local full_path = DOWNLOAD_FOLDER .. to .. "_temp.dat"
|
||||
if full_path then
|
||||
local ok, err = rename_file(full_path, full_path:gsub("(.+)_temp%.dat", "%1"))
|
||||
|
||||
if not ok then
|
||||
llog("unable to rename %q: %s", full_path, err)
|
||||
on_fail()
|
||||
return
|
||||
end
|
||||
|
||||
local full_path = R(DOWNLOAD_FOLDER .. to)
|
||||
|
||||
if full_path then
|
||||
resource.BuildCacheFolderList(full_path:match(".+/(.+)"))
|
||||
|
||||
callback(full_path)
|
||||
|
||||
--llog("finished donwnloading ", from)
|
||||
else
|
||||
wlog("resource download error: %q not found!", DOWNLOAD_FOLDER .. to)
|
||||
on_fail()
|
||||
end
|
||||
else
|
||||
wlog("resource download error: %q not found!", DOWNLOAD_FOLDER .. to)
|
||||
on_fail()
|
||||
end
|
||||
end,
|
||||
function(...)
|
||||
on_fail(...)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
local cb = utility_CreateCallbackThing()
|
||||
local ohno = false
|
||||
|
||||
function resource.Download(path, callback, on_fail, crc, check_etag)
|
||||
on_fail = on_fail or function(reason) llog(path, ": ", reason) end
|
||||
|
||||
local url
|
||||
local existing_path
|
||||
local need_extension
|
||||
|
||||
if path:find("^.-://") then
|
||||
local redownload = false
|
||||
|
||||
if path:StartWith("_") then
|
||||
path = path:sub(2)
|
||||
redownload = true
|
||||
end
|
||||
|
||||
if not resource.url_cache_lookup then
|
||||
resource.BuildCacheFolderList()
|
||||
end
|
||||
|
||||
url = path
|
||||
local crc = (crc or pac.Hash(path))
|
||||
|
||||
if not redownload and resource.url_cache_lookup[crc] then
|
||||
path = resource.url_cache_lookup[crc]
|
||||
existing_path = R(DOWNLOAD_FOLDER .. path)
|
||||
need_extension = false
|
||||
else
|
||||
path = crc
|
||||
existing_path = false
|
||||
need_extension = true
|
||||
end
|
||||
end
|
||||
|
||||
if not existing_path then
|
||||
check_etag = nil
|
||||
end
|
||||
|
||||
if not ohno then
|
||||
local old = callback
|
||||
callback = function(path)
|
||||
if old then old(path) end
|
||||
end
|
||||
end
|
||||
|
||||
if existing_path and not check_etag then
|
||||
ohno = true
|
||||
|
||||
if isfunction(callback) then
|
||||
callback(existing_path)
|
||||
elseif istable(callback) then
|
||||
for i, func in ipairs(callback) do
|
||||
func(existing_path)
|
||||
end
|
||||
end
|
||||
|
||||
ohno = false
|
||||
return true
|
||||
end
|
||||
|
||||
if check_etag then
|
||||
check_etag = function()
|
||||
if ohno then return end
|
||||
ohno = true
|
||||
cb:callextra(path, "check_etag", existing_path)
|
||||
ohno = false
|
||||
cb:stop(path, existing_path)
|
||||
cb:uncache(path)
|
||||
end
|
||||
end
|
||||
|
||||
if cb:check(path, callback, {on_fail = on_fail, check_etag = check_etag}) then return true end
|
||||
|
||||
cb:start(path, callback, {on_fail = on_fail, check_etag = check_etag})
|
||||
|
||||
if url then
|
||||
if not check_etag then
|
||||
-- llog("downloading ", url)
|
||||
end
|
||||
|
||||
download(
|
||||
url,
|
||||
path,
|
||||
function(...)
|
||||
cb:stop(path, ...)
|
||||
cb:uncache(path)
|
||||
end,
|
||||
function(...)
|
||||
cb:callextra(path, "on_fail", ... or path .. " not found")
|
||||
cb:uncache(path)
|
||||
end,
|
||||
function(header)
|
||||
-- check file crc stuff here/
|
||||
return true
|
||||
end,
|
||||
check_etag,
|
||||
nil,
|
||||
need_extension
|
||||
)
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function resource.BuildCacheFolderList(file_name)
|
||||
if not resource.url_cache_lookup then
|
||||
local tbl = {}
|
||||
for _, file_name in ipairs((file.Find(DOWNLOAD_FOLDER .. "*", "DATA"))) do
|
||||
local name = file_name:match("(%w+)%.")
|
||||
if name then
|
||||
tbl[name] = file_name
|
||||
else
|
||||
llog("bad file in downloads/cache folder: ", file_name)
|
||||
file.Delete(DOWNLOAD_FOLDER .. file_name)
|
||||
end
|
||||
end
|
||||
resource.url_cache_lookup = tbl
|
||||
end
|
||||
|
||||
if file_name then
|
||||
resource.url_cache_lookup[file_name:match("(.-)%.")] = file_name
|
||||
end
|
||||
end
|
||||
|
||||
function resource.ClearDownloads()
|
||||
local dirs = {}
|
||||
|
||||
for _, path in ipairs((vfs.Find(DOWNLOAD_FOLDER))) do
|
||||
file.Delete(DOWNLOAD_FOLDER .. path)
|
||||
end
|
||||
|
||||
resource.BuildCacheFolderList()
|
||||
end
|
||||
|
||||
function resource.CheckDownloadedFiles()
|
||||
local files = luadata.ReadFile(etags_file)
|
||||
local count = table.Count(files)
|
||||
|
||||
llog("checking " .. count .. " files for updates..")
|
||||
|
||||
local i = 0
|
||||
|
||||
for path, etag in pairs(files) do
|
||||
resource.Download(path, function() i = i + 1 if i == count then llog("done checking for file updates") end end, llog, nil, true)
|
||||
end
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
|
||||
local memory = {}
|
||||
|
||||
timer.Create("pac3_resource_gc", 0.25, 0, function()
|
||||
for k,v in pairs(memory) do
|
||||
if not v.ply:IsValid() then
|
||||
memory[k] = nil
|
||||
file.Delete(v.path)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
pac.AddHook("ShutDown", "resource_gc", function()
|
||||
for _, name in ipairs((file.Find(DOWNLOAD_FOLDER .. "*", "DATA"))) do
|
||||
file.Delete(DOWNLOAD_FOLDER .. name)
|
||||
end
|
||||
end)
|
||||
|
||||
function resource.DownloadTexture(url, callback, ply)
|
||||
if not url:find("^.-://") then return end
|
||||
|
||||
return resource.Download(
|
||||
url,
|
||||
function(path)
|
||||
local frames
|
||||
local mat
|
||||
|
||||
if path:EndsWith(".vtf") then
|
||||
local f = file.Open(path, "rb", "DATA")
|
||||
f:Seek(24)
|
||||
frames = f:ReadShort()
|
||||
f:Close()
|
||||
|
||||
mat = CreateMaterial(tostring({}), "VertexLitGeneric", {})
|
||||
mat:SetTexture("$basetexture", "../data/" .. path)
|
||||
else
|
||||
mat = Material("../data/" .. path, "mips smooth noclamp")
|
||||
end
|
||||
|
||||
local tex = mat:GetTexture("$basetexture")
|
||||
if tex then
|
||||
callback(tex, frames)
|
||||
|
||||
memory[url] = {ply = ply, path = path}
|
||||
elseif ply == pac.LocalPlayer then
|
||||
pac.Message(Color(255, 50, 50), "$basetexture from ", url, " is nil")
|
||||
end
|
||||
end,
|
||||
function()
|
||||
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return resource
|
||||
Reference in New Issue
Block a user