diff --git a/.gitignore b/.gitignore index 4ff6ca7..08f30ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *.pyc +.vscode __pycache__ L0laapk3_FactorioMaps_*.zip -autorun.lua \ No newline at end of file +autorun.lua +web/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 671abbf..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "python.pythonPath": "D:\\Python371\\python.exe", - "editor.insertSpaces": false -} \ No newline at end of file diff --git a/README.md b/README.md index a2f148e..3e14d0a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Mod portal link: https://mods.factorio.com/mod/L0laapk3_FactorioMaps # Configuration Heres a list of flags that `auto.py` can accept: -|                flag                | Description | +|                  flag                  | Description | | --- | --- | | `--dayonly` | Do not take nighttime screenshots (For now, this setting needs to be the same across one timeline). | | `--nightonly` | Do not take daytime screenshots. | @@ -60,11 +60,11 @@ Image quality settings can be changed in the top of `zoom.py`. # Hosting this on a server If you wish to host your map for other people to a server, you need to take into account the following considerations: (You can change these once in `index.html.template` and they will be used for all future snapshots.) -1. All references to `https://cdn.jsdelivr.net/gh/L0laapk3/Leaflet.OpacityControls` (should be replaced with self hosted versions. The files are on https://github.com/L0laapk3/Leaflet.OpacityControls. 1. Of the files that this program generates, the files required to be hosted are: * `index.html` * `mapInfo.js` * All __images__ in `Images\`. + * All files in `lib\`. All other files, including txt and other non-image files in `Images\`, are not used by the client. Some of them are temporary files, some of them are used as savestate to create additional snapshots on the timeline. # Known limitations diff --git a/auto.py b/auto.py index 9a18eeb..4627a00 100644 --- a/auto.py +++ b/auto.py @@ -10,7 +10,7 @@ from subprocess import call import datetime import urllib.request, urllib.error, urllib.parse -from shutil import copy, rmtree, get_terminal_size as tsize +from shutil import copy, copytree, rmtree, get_terminal_size as tsize from zipfile import ZipFile import tempfile from PIL import Image, ImageChops @@ -19,40 +19,103 @@ from crop import crop from ref import ref from zoom import zoom +from updateLib import update as updateLib -def auto(*args): +def printErase(arg): + try: + tsiz = tsize()[0] + print("\r{}{}\n".format(arg, " " * (tsiz*math.ceil(len(arg)/tsiz)-len(arg) - 1)), end="", flush=True) + except e: + #raise + pass +def startGameAndReadGameLogs(results, condition, popenArgs, tmpDir, pidBlacklist, rawTags, **kwargs): + + pipeOut, pipeIn = os.pipe() + p = subprocess.Popen(popenArgs, stdout=pipeIn) - def printErase(arg): - try: - tsiz = tsize()[0] - print("\r{}{}\n".format(arg, " " * (tsiz*math.ceil(len(arg)/tsiz)-len(arg) - 1)), end="", flush=True) - except e: - #raise - pass + def handleGameLine(line): + line = line.rstrip('\n') + m = re.match(r'^\ *\d+(?:\.\d+)? *Script *@__L0laapk3_FactorioMaps__\/data-final-fixes\.lua:\d+: FactorioMaps_Output_RawTagPaths:([^:]+):(.*)$', line, re.IGNORECASE) + if m is not None: + rawTags[m.group(1)] = m.group(2) + if rawTags["__used"]: + raise Exception("Tags added after they were used.") + elif "err" in line.lower() or "warn" in line.lower() or "exception" in line.lower() or "fail" in line.lower() or (kwargs.get("verbosegame", False) and len(line) > 0): + printErase("[GAME] %s" % line) + with os.fdopen(pipeOut, 'r') as pipef: + + line = pipef.readline() + handleGameLine(line) + isSteam = line.rstrip("\n").endswith("Initializing Steam API.") + + if isSteam: + print("WARNING: running in limited support mode trough steam. Consider using standalone factorio instead.\n\t Please alt tab to steam and confirm the steam 'start game with arguments' popup.\n\t (Yes, you'll have to do this every time with the steam version)\n\t Also, if you have any default arguments set in steam for factorio, you'll have to remove them.") + attrs = ('pid', 'name', 'create_time') + oldest = None + pid = None + while pid is None: + for proc in psutil.process_iter(attrs=attrs): + pinfo = proc.as_dict(attrs=attrs) + if pinfo["name"] == "factorio.exe" and pinfo["pid"] not in pidBlacklist and (pid is None or pinfo["create_time"] < oldest): + oldest = pinfo["create_time"] + pid = pinfo["pid"] + if pid is None: + time.sleep(1) + print(f"PID: {pid}") + else: + pid = p.pid + + results.extend((isSteam, pid)) + with condition: + condition.notify() + + if isSteam: + pipef.close() + with open(os.path.join(tmpDir, "factorio-current.log"), "r") as f: + while psutil.pid_exists(pid): + where = f.tell() + line = f.readline() + if not line: + time.sleep(0.4) + f.seek(where) + else: + handleGameLine(line) - def kill(pid): - if psutil.pid_exists(pid): + else: + while True: + line = pipef.readline() + handleGameLine(line) - if os.name == 'nt': - cmd = ("taskkill", "/pid", str(pid)) - else: - cmd = ("kill", str(pid)) - subprocess.check_call(cmd, stdout=subprocess.DEVNULL, shell=True) - while psutil.pid_exists(pid): - time.sleep(0.1) - printErase("killed factorio") - time.sleep(0.1) +def auto(*args): + + lock = threading.Lock() + def kill(pid, onlyStall=False): + with lock: + if not onlyStall and psutil.pid_exists(pid): + + if os.name == 'nt': + cmd = ("taskkill", "/pid", str(pid)) + else: + cmd = ("kill", str(pid)) + subprocess.check_call(cmd, stdout=subprocess.DEVNULL, shell=True) + + while psutil.pid_exists(pid): + time.sleep(0.1) + + printErase("killed factorio") + + #time.sleep(0.1) def parseArg(arg): @@ -171,6 +234,10 @@ def parseArg(arg): + updateLib(False) + + + #TODO: integrety check, if done files arent there or there are any bmp's left, complain. @@ -185,10 +252,10 @@ def linkDir(src, dest): modListPath = os.path.join(kwargs["modpath"], "mod-list.json") if "modpath" in kwargs else "../mod-list.json" if "modpath" in kwargs and not os.path.samefile(kwargs["modpath"], "../../mods"): - for file in os.listdir(kwargs["modpath"]): - if re.match(r'^L0laapk3_FactorioMaps_', file, flags=re.IGNORECASE): + for f in os.listdir(kwargs["modpath"]): + if re.match(r'^L0laapk3_FactorioMaps_', f, flags=re.IGNORECASE): print("Found other factoriomaps mod in custom mod folder, deleting.") - path = os.path.join(kwargs["modpath"], file) + path = os.path.join(kwargs["modpath"], f) if os.path.islink(path): os.unlink(path) else: @@ -214,20 +281,9 @@ def changeModlist(newState): changeModlist(True) - - rawTags = {} - rawTagsUsed = False - def printGameLog(pipe): - with os.fdopen(pipe) as reader: - while True: - line = reader.readline().rstrip('\n') - m = re.match(r'^\ *\d+(?:\.\d+)? *Script *@__L0laapk3_FactorioMaps__\/data-final-fixes\.lua:\d+: FactorioMaps_Output_RawTagPaths:([^:]+):(.*)$', line, re.IGNORECASE) - if m is not None: - rawTags[m.group(1)] = m.group(2) - if rawTagsUsed: - raise Exception("Tags added after they were used.") - elif "err" in line.lower() or "warn" in line.lower() or "exception" in line.lower() or "fail" in line.lower() or (kwargs.get("verbosegame", False) and len(line) > 0): - printErase("[GAME] %s" % line) + manager = mp.Manager() + rawTags = manager.dict() + rawTags["__used"] = False @@ -240,10 +296,6 @@ def printGameLog(pipe): - logIn, logOut = os.pipe() - logthread = threading.Thread(target=printGameLog, args=[logIn]) - logthread.daemon = True - logthread.start() @@ -322,29 +374,36 @@ def printGameLog(pipe): copy("../../player-data.json", os.path.join(tmpDir, "player-data.json")) pid = None + isSteam = None pidBlacklist = [p.info["pid"] for p in psutil.process_iter(attrs=['pid', 'name']) if p.info['name'] == "factorio.exe"] - p = subprocess.Popen((factorioPath, '--load-game', os.path.abspath(os.path.join("../../saves", savename+".zip")), '--disable-audio', '--config', configPath, "--mod-directory", os.path.abspath(kwargs["modpath"] if "modpath" in kwargs else "../../mods")), stdout=logOut) - time.sleep(1) - if p.poll() is not None: - print("WARNING: running in limited support mode trough steam. Consider using standalone factorio instead.\n\t Please open steam and confirm the steam 'start game with arguments' popup.") - attrs = ('pid', 'name', 'create_time') - oldest = None - while pid is None: - for proc in psutil.process_iter(attrs=attrs): - pinfo = proc.as_dict(attrs=attrs) - if pinfo["name"] == "factorio.exe" and pinfo["pid"] not in pidBlacklist and (pid is None or pinfo["create_time"] < oldest): - oldest = pinfo["create_time"] - pid = pinfo["pid"] - if pid is None: - time.sleep(1) - else: - pid = p.pid + popenArgs = (factorioPath, '--load-game', os.path.abspath(os.path.join("../../saves", savename+".zip")), '--disable-audio', '--config', configPath, "--mod-directory", os.path.abspath(kwargs["modpath"] if "modpath" in kwargs else "../../mods")) + + condition = mp.Condition() + + + results = manager.list() + + startLogProcess = mp.Process(target=startGameAndReadGameLogs, args=(results, condition, popenArgs, tmpDir, pidBlacklist, rawTags), kwargs=kwargs) + startLogProcess.daemon = True + startLogProcess.start() + + + with condition: + condition.wait() + isSteam, pid = results[:] + + + if isSteam is None: + raise Exception("isSteam error") + if pid is None: + raise Exception("pid error") + - if not os.path.exists(datapath): - while not os.path.exists(datapath): - time.sleep(0.4) + + while not os.path.exists(datapath): + time.sleep(0.4) latest = [] with open(datapath, 'r') as f: @@ -367,9 +426,9 @@ def waitKill(isKilled, pid): else: time.sleep(0.4) - killthread = threading.Thread(target=waitKill, args=(isKilled, pid)) - killthread.daemon = True - killthread.start() + killThread = threading.Thread(target=waitKill, args=(isKilled, pid)) + killThread.daemon = True + killThread.start() @@ -404,19 +463,26 @@ def refZoom(): if screenshot != latest[-1]: refZoom() else: - if not isKilled[0]: - isKilled[0] = True - kill(pid) + + startLogProcess.terminate() + + # I have receieved a bug report from feidan in which he describes what seems like that this doesnt kill factorio? + + onlyStall = isKilled[0] + isKilled[0] = True + kill(pid, onlyStall) if savename == savenames[-1]: refZoom() + else: workthread = threading.Thread(target=refZoom) workthread.daemon = True workthread.start() - os.close(logOut) + + @@ -455,7 +521,7 @@ def refZoom(): key = lambda t: t[1]) - rawTagsUsed = True + rawTags["__used"] = True for _, tag in tags.items(): dest = os.path.join(workfolder, tag["iconPath"]) os.makedirs(os.path.dirname(dest), exist_ok=True) @@ -521,6 +587,7 @@ def refZoom(): print("creating index.html") copy("index.html.template", os.path.join(workfolder, "index.html")) + copytree("web", os.path.join(workfolder, "lib")) diff --git a/index.html.template b/index.html.template index ed561b8..7e0e050 100644 --- a/index.html.template +++ b/index.html.template @@ -3,12 +3,11 @@ FactorioMaps - - + + - + - - - + + + - + - - - - + + - - - + + @@ -339,7 +335,7 @@ if (layer[daytime] == "true") { var LLayer = L.tileLayer(undefined, { id: layer.path, - attribution: 'FactorioMaps', + attribution: 'FactorioMaps', minNativeZoom: DEBUG ? 20 : layer.zoom.min, maxNativeZoom: layer.zoom.max, minZoom: layer.zoom.min >= 1 ? layer.zoom.min - 1 : 1, diff --git a/info.json b/info.json index 6c56415..5a01c71 100644 --- a/info.json +++ b/info.json @@ -1,6 +1,6 @@ { "name": "L0laapk3_FactorioMaps", - "version": "3.2.0", + "version": "3.2.1", "title": "FactorioMaps", "author": "L0laapk3", "contact": "https://github.com/L0laapk3/", diff --git a/makeZip.py b/makeZip.py index 7c90f64..42aa88c 100644 --- a/makeZip.py +++ b/makeZip.py @@ -3,6 +3,7 @@ from shutil import rmtree, copy, make_archive import shutil +from updateLib import update as updateLib folderName = os.path.basename(os.path.realpath(".")) @@ -25,13 +26,19 @@ ".vscode", "__pycache__" ) +excludeFiles = ( + ".gitignore", + ".gitattributes", + "makezip.py" +) +updateLib(False) for root, dirs, files in os.walk("."): dirs[:] = [d for d in dirs if d not in excludeDirs] for file in files: if file[-4:].lower() == ".pyc": continue - if file.lower() in (".gitignore", ".gitattributes", "makezip.py"): + if file.lower() in excludeFiles: continue src = os.path.normpath(os.path.join(root, file)) diff --git a/updateLib.py b/updateLib.py new file mode 100644 index 0000000..31ba674 --- /dev/null +++ b/updateLib.py @@ -0,0 +1,85 @@ +from shutil import rmtree, copytree +import os +from urllib.parse import urlparse +from urllib.request import urlretrieve, build_opener, install_opener +from tempfile import gettempdir + + + +urlList = ( + "https://cdn.jsdelivr.net/npm/leaflet@1.4.0/dist/leaflet.css", + "https://cdn.jsdelivr.net/npm/leaflet@1.4.0/dist/leaflet-src.min.js", + "https://cdn.jsdelivr.net/npm/leaflet.fullscreen@1.4.5/Control.FullScreen.css", + "https://cdn.jsdelivr.net/npm/leaflet.fullscreen@1.4.5/Control.FullScreen.min.js", + "https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js", + "https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css", + "https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js", + "https://cdn.jsdelivr.net/gh/L0laapk3/Leaflet.OpacityControls@1/Control.Opacity.css", + "https://cdn.jsdelivr.net/gh/L0laapk3/Leaflet.OpacityControls@1/Control.Opacity.js", + "https://factorio.com/static/img/favicon.ico", + "https://i.factoriomaps.com/t92kw.png", +) + + + + +CURRENTVERSION = 1 + + + + + + +def update(Force=True): + + targetPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web") + + if not Force: + try: + with open(os.path.join(targetPath, "VERSION"), "r") as f: + if f.readline() == str(CURRENTVERSION): + return False + except FileNotFoundError: + pass + + tempPath = os.path.join(gettempdir(), "FactorioMapsTmpLib") + try: + rmtree(tempPath) + except (FileNotFoundError, NotADirectoryError): + pass + + os.makedirs(tempPath, exist_ok=True) + + + opener = build_opener() + opener.addheaders = [('User-agent', 'Mozilla/5.0 U GUYS SUCK WHY ARE YOU BLOCKING Python-urllib')] + install_opener(opener) + + for url in urlList: + print(f"downloading {url}") + urlretrieve(url, os.path.join(tempPath, os.path.basename(urlparse(url).path))) + + + try: + rmtree(targetPath) + except (FileNotFoundError, NotADirectoryError): + pass + + + copytree(tempPath, targetPath) + with open(os.path.join(targetPath, "VERSION"), "w") as f: + f.write(str(CURRENTVERSION)) + + + try: + rmtree(tempPath) + except (FileNotFoundError, NotADirectoryError): + pass + + return True + + + + +if __name__ == '__main__': + update(True) \ No newline at end of file diff --git a/updates.json b/updates.json index 1338b7d..45590de 100644 --- a/updates.json +++ b/updates.json @@ -19,5 +19,6 @@ "3.1.0": "!Better implementation of tag image extraction", "3.1.1": "!Fixed bugs when no mod path is specified", "3.1.2": "!Fixed bug where the entire script-output folder is sometimes removed due to inconsistent symlink behavior", - "3.2.0": ["!Considerable speedup for most users that haven't bothered changing the factorio config.ini", "Loads of bugfixes"] + "3.2.0": ["!Considerable speedup for most users that haven't bothered changing the factorio config.ini", "Loads of bugfixes"], + "3.2.1": "Fixed various bugs" } \ No newline at end of file