From c38ace983a34f771e9db546ea22f9a1287fa9642 Mon Sep 17 00:00:00 2001 From: Paul Liverman III Date: Fri, 25 May 2018 16:32:09 -0700 Subject: [PATCH] close #5 #6 simpler usage & SSL support --- ReadMe.md | 58 ++++++++++++++--------- check.lua | 97 -------------------------------------- check.moon | 79 ------------------------------- itchy.lua | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++ itchy.moon | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++ usage.lua | 21 +++++---- 6 files changed, 315 insertions(+), 208 deletions(-) delete mode 100644 check.lua delete mode 100644 check.moon create mode 100644 itchy.lua create mode 100644 itchy.moon diff --git a/ReadMe.md b/ReadMe.md index 80e277e..792c20e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -5,30 +5,36 @@ published on [itch.io](https://itch.io/). ## Installation -Just copy `check.lua` to where you want in your source. +Copy `itchy.lua` to where you want in your source. It is recommended that you +install [luajit-request](https://github.com/LPGhatguy/luajit-request) as it +allows for itchy to use HTTPS connections. ## Usage Start it as a thread with a configuration table. Wait for "itchy" channel to respond with a table of version information. -```lua -versionChecker = love.thread.newThread("lib/itchy/check.lua") -- wherever you save it.. -versionChecker:start({ - target = "guard13007/asteroid-dodge", -- target/url must be defined - version = "1.0.0" -- optional, config options listed below -}) +Require itchy, and run `check_version` with a configuration table. Periodically +run `new_version` to see if data has been returned yet. -newVersion = love.thread.getChannel("itchy") -if newVersion:getCount() > 0 then - local data = newVersion:demand() -- see example data below - -- easiest usage is to just print something like this to the user - print("Version: 1.0.0 Latest version: " .. data.message) +```lua +local itchy = require "lib.itchy" -- or wherever you saved it +local game = { + target = "guard13007/asteroid-dodge", -- target or url must be defined + version = "1.0.0" -- optional, config options listed below +} +itchy:check_version(game) + +-- somewhere where this will be called periodically +local data = itchy:new_version(game) -- passing the game table is not necessary +if data then + -- easiest usage, just print the message to the user + love.graphics.print("Version: 1.0.0 Latest: " .. data.message) end ``` -Since it is run as a thread, you can cancel it with `versionChecker:kill()` and -start it again or with a different configuration later with `:start()`. +You can cancel it with `itchy:kill_version_checker(game)`, and start a new +version checker with `itchy:check_version({})` any time you like. ### Version Information Example @@ -52,7 +58,7 @@ based on this value. If it is unable to extract it or compare, `version` and `latest` will be `nil`. If HTTP status 200 (OK) is encountered or a version is successfully parsed from -a response, the script terminates (or moves on to checking on an `interval`.) +a response, the script terminates (or moves on to checking on an interval.) Otherwise, it will keep trying with an exponential back-off starting at a 1 second delay, capped at retrying every 10 minutes. @@ -63,17 +69,25 @@ At minimum a `url` or `target` must be specified. * `url` (string) If you have a different URL to check for the latest version from, you can specify it here. * `target` (string) The target string of your game on itch.io - (username/game-slug) + (username/game-slug). * `channel` (string) If you do not specify the channel name to look for on itch.io, it will use `osx` for Mac OS / OS X, `win32` for Windows, `linux` for Linux, `android` for Android, `ios` for iOS, and if any other OS is returned by `love.system.getOS()` it will use that string as-is. * `version` (string/number) Version of the game running right now. -* `proxy` (string) This library uses an [HTTP proxy](https://github.com/Guard13007/insecure-proxy) - for the HTTPS call to itch.io's API. By default it uses `https://104.236.139.220:16343` - which is on a DigitalOcean VPS I own. If you'd rather use a different proxy - server, you can specify it here. * `interval` (number) If specified, a check for the latest version will happen again every `interval` seconds. -* `thread_channel` (string) If specified, will use a different named thread - channel to return results to. +* `luajit_request` (string) luajit-request is checked for in `.` and `lib/.`, if + you have it elsewhere, specify its location here. + +The following options are available, but generally should be left for itchy to +handle itself: + +* `proxy` (string) An [HTTP proxy](https://github.com/Guard13007/insecure-proxy) + is used if luajit-request is unavailable, unless `proxy == false`. By default, + `https://104.236.139.220:16343` is used, which is running on a DigitalOcean + VPS I own. You can specify a different proxy here. +* `thread_channel` (string) itchy uses a channel named `itchy` for version + checking. You can call itchy's functions with different data tables and it + will use different threads & channels for each. You can also specify a channel + name here. diff --git a/check.lua b/check.lua deleted file mode 100644 index 5548fc5..0000000 --- a/check.lua +++ /dev/null @@ -1,97 +0,0 @@ -require("love.timer") -local thread, timer -do - local _obj_0 = love - thread, timer = _obj_0.thread, _obj_0.timer -end -local http = require("socket.http") -local check -check = function(data) - local send = thread.getChannel(data.thread_channel or "itchy") - local exponential_backoff = 1 - while true do - local _continue_0 = false - repeat - do - local result = { } - if data.url then - result.body, result.status = http.request(data.url) - elseif data.proxy then - if not (data.target) then - result.message = "'target' or 'url' must be defined!" - send:push(result) - return false - end - result.body, result.status = http.request(tostring(data.proxy) .. "/get/https://api.itch.io/wharf/latest?target=" .. tostring(data.target) .. "&channel_name=" .. tostring(data.channel)) - end - if not (result.body) then - result.message = "socket.http.request error: " .. tostring(result.status) - send:push(result) - return false - end - result.version = result.body:match('%s*{%s*"latest"%s*:%s*"(.+)"%s*}%s*') - result.version = tonumber(result.version) or result.version - if data.version then - result.latest = result.version == data.version - end - if result.status ~= 200 and (not result.version) then - result.message = "unknown, error getting latest version: HTTP " .. tostring(result.status) .. ", trying again in " .. tostring(exponential_backoff) .. " seconds" - send:push(result) - timer.sleep(exponential_backoff) - exponential_backoff = exponential_backoff * 2 - if exponential_backoff > 10 * 60 then - exponential_backoff = 10 * 60 - end - _continue_0 = true - break - elseif result.latest ~= nil then - if result.latest then - result.message = tostring(result.version) .. ", you have the latest version" - else - result.message = tostring(result.version) .. ", there is a newer version available!" - end - else - result.message = result.version - end - send:push(result) - return true - end - _continue_0 = true - until true - if not _continue_0 then - break - end - end -end -local start -start = function(data) - if not (data.proxy or data.url) then - data.proxy = "http://45.55.113.149:16343" - end - if not (data.channel) then - require("love.system") - local os = love.system.getOS() - local _exp_0 = os - if "OS X" == _exp_0 then - data.channel = "osx" - elseif "Windows" == _exp_0 then - data.channel = "win32" - elseif "Linux" == _exp_0 then - data.channel = "linux" - elseif "Android" == _exp_0 then - data.channel = "android" - elseif "iOS" == _exp_0 then - data.channel = "ios" - else - data.channel = os - end - end - check(data) - if data.interval then - while true do - timer.sleep(data.interval) - check(data) - end - end -end -return start(...) diff --git a/check.moon b/check.moon deleted file mode 100644 index d11fd99..0000000 --- a/check.moon +++ /dev/null @@ -1,79 +0,0 @@ -require "love.timer" -import thread, timer from love - -http = require "socket.http" - -check = (data) -> - send = thread.getChannel data.thread_channel or "itchy" - - exponential_backoff = 1 - while true - result = {} - if data.url - result.body, result.status = http.request data.url - elseif data.proxy - unless data.target - result.message = "'target' or 'url' must be defined!" - send\push result - return false - result.body, result.status = http.request "#{data.proxy}/get/https://api.itch.io/wharf/latest?target=#{data.target}&channel_name=#{data.channel}" - - unless result.body - result.message = "socket.http.request error: #{result.status}" - send\push result - return false - - result.version = result.body\match '%s*{%s*"latest"%s*:%s*"(.+)"%s*}%s*' - result.version = tonumber(result.version) or result.version - result.latest = if data.version - result.version == data.version - - if result.status != 200 and (not result.version) - result.message = "unknown, error getting latest version: HTTP #{result.status}, trying again in #{exponential_backoff} seconds" - send\push result - timer.sleep exponential_backoff - exponential_backoff *= 2 - exponential_backoff = 10 * 60 if exponential_backoff > 10 * 60 -- maximum backoff is 10 minutes - continue - elseif result.latest != nil - if result.latest - result.message = "#{result.version}, you have the latest version" - else - result.message = "#{result.version}, there is a newer version available!" - else - result.message = result.version - - send\push result - return true - --- data should be a table of information -start = (data) -> - data.proxy = "http://45.55.113.149:16343" unless data.proxy or data.url - - -- channel can be autodetected if not specified - unless data.channel - require "love.system" - os = love.system.getOS! - switch os - when "OS X" - data.channel = "osx" - when "Windows" - data.channel = "win32" - when "Linux" - data.channel = "linux" - when "Android" - data.channel = "android" - when "iOS" - data.channel = "ios" - else - data.channel = os - - check data - - -- if we should check again every x seconds, wait, and do so - if data.interval - while true - timer.sleep data.interval - check data - -start(...) diff --git a/itchy.lua b/itchy.lua new file mode 100644 index 0000000..44ebb2e --- /dev/null +++ b/itchy.lua @@ -0,0 +1,136 @@ +local thread +thread = function(...) + require("love.thread") + require("love.timer") + local timer + do + local _obj_0 = love + thread, timer = _obj_0.thread, _obj_0.timer + end + local http = require("socket.http") + local _, libcurl = pcall(function() + return require("luajit-request") + end) + if not (libcurl) then + _, libcurl = pcall(function() + return require("lib.luajit-request") + end) + end + local request + request = function(data) + local result = { } + if not (libcurl) then + if data.luajit_request then + _, libcurl = pcall(function() + return require(data.luajit_request) + end) + end + end + if libcurl then + local response = libcurl.send(data.url or "https://api.itch.io/wharf/latest?target=" .. tostring(data.target) .. "&channel_name=" .. tostring(data.channel)) + result.body = response.body + result.status = response.code + else + if not (data.proxy) then + result.message = "Could not load libcurl." + return nil, result + end + result.body, result.status = http.request(data.url or tostring(data.proxy) .. "/get/https://api.itch.io/wharf/latest?target=" .. tostring(data.target) .. "&channel_name=" .. tostring(data.channel)) + if not (result.body) then + result.message = "socket.http.request error: " .. tostring(result.status) + result.status = nil + return nil, result + end + end + return true, result + end + local check + check = function(data) + local send = thread.getChannel(data.thread_channel or "itchy") + local exponential_backoff = 1 + while true do + local _continue_0 = false + repeat + do + local result = { } + if not (data.url or data.target) then + result.message = "'target' or 'url' be must be defined!" + send:push(result) + return false + end + local ok + ok, result = request(data) + if not (ok) then + send:push(result) + return false + end + result.version = result.body:match('%s*{%s*"latest"%s*:%s*"(.+)"%s*}%s*') + result.version = tonumber(result.version) or result.version + if data.version then + result.latest = result.version == data.version + end + if result.status ~= 200 and (not result.version) then + result.message = "unknown, error getting latest version: HTTP " .. tostring(result.status) .. ", trying again in " .. tostring(exponential_backoff) .. " seconds..." + send:push(result) + timer.sleep(exponential_backoff) + exponential_backoff = exponential_backoff * 2 + if exponential_backoff > 10 * 60 then + exponential_backoff = 10 * 60 + end + _continue_0 = true + break + elseif result.latest ~= nil then + if result.latest then + result.message = tostring(result.version) .. ", you have the latest vesion" + else + result.message = tostring(result.version) .. ", there is a newer version available!" + end + else + result.message = result.version + end + send:push(result) + return true + end + _continue_0 = true + until true + if not _continue_0 then + break + end + end + end + local start + start = function(data) + if data.proxy == nil and (not data.url) then + data.proxy = "http://45.55.113.149:16343" + end + if not (data.channel) then + require("love.system") + local os = love.system.getOS() + local _exp_0 = os + if "OS X" == _exp_0 then + data.channel = "osx" + elseif "Windows" == _exp_0 then + data.channel = "win32" + elseif "Linux" == _exp_0 then + data.channel = "linux" + elseif "Android" == _exp_0 then + data.channel = "android" + elseif "iOS" == _exp_0 then + data.channel = "ios" + else + data.channel = os + end + end + check(data) + if data.interval then + while true do + timer.sleep(data.interval) + check(data) + end + end + end + return start(...) +end +if not (love.graphics or love.window) then + return thread(...) +end diff --git a/itchy.moon b/itchy.moon new file mode 100644 index 0000000..e7c8c03 --- /dev/null +++ b/itchy.moon @@ -0,0 +1,132 @@ +thread = (...) -> + require "love.thread" + require "love.timer" + import thread, timer from love + + http = require "socket.http" + _, libcurl = pcall -> return require "luajit-request" + unless libcurl + _, libcurl = pcall -> return require "lib.luajit-request" + + request = (data) -> + result = {} + unless libcurl + if data.luajit_request + _, libcurl = pcall -> return require data.luajit_request + if libcurl + response = libcurl.send data.url or "https://api.itch.io/wharf/latest?target=#{data.target}&channel_name=#{data.channel}" + result.body = response.body + result.status = response.code + else + unless data.proxy + result.message = "Could not load libcurl." + return nil, result + result.body, result.status = http.request data.url or "#{data.proxy}/get/https://api.itch.io/wharf/latest?target=#{data.target}&channel_name=#{data.channel}" + unless result.body + result.message = "socket.http.request error: #{result.status}" + result.status = nil + return nil, result + return true, result + + check = (data) -> + send = thread.getChannel data.thread_channel or "itchy" + + exponential_backoff = 1 + while true + result = {} + unless data.url or data.target + result.message = "'target' or 'url' be must be defined!" + send\push result + return false + ok, result = request data + + unless ok + send\push result + return false + + result.version = result.body\match '%s*{%s*"latest"%s*:%s*"(.+)"%s*}%s*' + result.version = tonumber(result.version) or result.version + result.latest = if data.version + result.version == data.version + + if result.status != 200 and (not result.version) + result.message = "unknown, error getting latest version: HTTP #{result.status}, trying again in #{exponential_backoff} seconds..." + send\push result + timer.sleep exponential_backoff + exponential_backoff *= 2 + exponential_backoff = 10 * 60 if exponential_backoff > 10 * 60 -- maximum backoff is 10 minutes + continue + elseif result.latest != nil + if result.latest + result.message = "#{result.version}, you have the latest vesion" + else + result.message = "#{result.version}, there is a newer version available!" + else + result.message = result.version + + send\push result + return true + + -- data should be a table of information + start = (data) -> + data.proxy = "http://45.55.113.149:16343" if data.proxy == nil and (not data.url) + + -- channel can be autodetected if not specified + unless data.channel + require "love.system" + os = love.system.getOS! + switch os + when "OS X" + data.channel = "osx" + when "Windows" + data.channel = "win32" + when "Linux" + data.channel = "linux" + when "Android" + data.channel = "android" + when "iOS" + data.channel = "ios" + else + data.channel = os + + check data + + -- if we should check again every x seconds, wait, and do so + if data.interval + while true + timer.sleep data.interval + check data + + start(...) + +-- detect if we are running in a thread, run directly if we are +return thread(...) unless love.graphics or love.window + +thread_data = love.filesystem.newFileData string.dump(thread), "itchy version checker" + +counter = 1 +configs, results = {}, {} +local default_data + +itchy = { + check_version: (data) -> + default_data = data unless default_data + if (not data.thread_channel) and next configs + data.thread_channel = "itchy-#{counter}" + counter += 1 + configs[data] = data + love.thread.newThread(thread_data)\start data + new_version: (data=default_data) -> + if data and configs[data] + channel = love.thread.getChannel data.thread_channel or "itchy" + if channel\getCount! > 0 + results[data] = channel\demand! + return results[data] -- nil or data (new or old) + kill_version_checker: (data=default_data) -> + configs[data] = nil + results[data] = nil + default_data = nil if data == default_data + -- we don't kill the thread, as that can crash the game +} + +return itchy diff --git a/usage.lua b/usage.lua index 6ad7ea1..8058907 100644 --- a/usage.lua +++ b/usage.lua @@ -1,12 +1,13 @@ -versionChecker = love.thread.newThread("lib/itchy/check.lua") -- wherever you save it.. -versionChecker:start({ - target = "guard13007/asteroid-dodge", -- target/url must be defined - version = "1.0.0" -- optional -}) +local itchy = require "lib.itchy" -- or wherever you saved it +local game = { + target = "guard13007/asteroid-dodge", -- target or url must be defined + version = "1.0.0" -- optional, config options listed below +} +itchy:check_version(game) -newVersion = love.thread.getChannel("itchy") -if newVersion:getCount() > 0 then - local data = newVersion:demand() - -- easiest usage is to just print something like this to the user - print("Version: 1.0.0 Latest version: " .. data.message) +-- somewhere where this will be called periodically +local data = itchy:new_version(game) -- passing the game table is not necessary +if data then + -- easiest usage, just print the message to the user + love.graphics.print("Version: 1.0.0 Latest: " .. data.message) end