mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
439 lines
9.5 KiB
Lua
439 lines
9.5 KiB
Lua
--[[
|
|
| 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 |