Skip to content

Commit

Permalink
Fix Path handling (#72)
Browse files Browse the repository at this point in the history
* Fix Path handling
* Port to pathlib
* Replace with re.sub
* Tidied regular expression
* Ensure escaping is done on single path level at a time
* Version bump to 2.3.0
* pylint & black fixes
  • Loading branch information
Emersont1 authored Mar 16, 2023
1 parent bf98823 commit b64e819
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 49 deletions.
2 changes: 2 additions & 0 deletions itchiodl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# pylint: disable=consider-using-from-import
import itchiodl.utils as utils
from .login import LoginWeb, LoginAPI
from .bundle import Bundle
from .library import Library
Expand Down
74 changes: 36 additions & 38 deletions itchiodl/game.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import re
import json
import os
import urllib
import datetime
import shutil
from pathlib import Path
import requests


import itchiodl.utils
from itchiodl import utils


class Game:
Expand All @@ -31,6 +29,11 @@ def __init__(self, data):

self.files = []
self.downloads = []
self.dir = (
Path(".")
/ utils.clean_path(self.publisher_slug)
/ utils.clean_path(self.game_slug)
)

def load_downloads(self, token):
"""Load all downloads for this game"""
Expand All @@ -53,17 +56,13 @@ def download(self, token, platform):
"""Download a singular file"""
print("Downloading", self.name)

# if os.path.exists(f"{self.publisher_slug}/{self.game_slug}.json"):
# if out_folder.with_suffix(".json").exists():
# print(f"Skipping Game {self.name}")
# return

self.load_downloads(token)

if not os.path.exists(self.publisher_slug):
os.mkdir(self.publisher_slug)

if not os.path.exists(f"{self.publisher_slug}/{self.game_slug}"):
os.mkdir(f"{self.publisher_slug}/{self.game_slug}")
self.dir.mkdir(parents=True, exist_ok=True)

for d in self.downloads:
if (
Expand All @@ -75,7 +74,7 @@ def download(self, token, platform):
continue
self.do_download(d, token)

with open(f"{self.publisher_slug}/{self.game_slug}.json", "w") as f:
with self.dir.with_suffix(".json").open("w") as f:
json.dump(
{
"name": self.name,
Expand All @@ -93,42 +92,41 @@ def do_download(self, d, token):
"""Download a single file, checking for existing files"""
print(f"Downloading {d['filename']}")

file = itchiodl.utils.clean_path(d["filename"] or d["display_name"] or d["id"])
path = itchiodl.utils.clean_path(f"{self.publisher_slug}/{self.game_slug}")
filename = d["filename"] or d["display_name"] or d["id"]

if os.path.exists(f"{path}/{file}"):
print(f"File Already Exists! {file}")
if os.path.exists(f"{path}/{file}.md5"):
out_file = self.dir / filename

with open(f"{path}/{file}.md5", "r") as f:
if out_file.exists():
print(f"File Already Exists! {filename}")
md5_file = out_file.with_suffix(".md5")
if md5_file.exists():
with md5_file.open("r") as f:
md5 = f.read().strip()

if md5 == d["md5_hash"]:
print(f"Skipping {self.name} - {file}")
print(f"Skipping {self.name} - {filename}")
return
print(f"MD5 Mismatch! {file}")
print(f"MD5 Mismatch! {filename}")
else:
md5 = itchiodl.utils.md5sum(f"{path}/{file}")
md5 = utils.md5sum(str(out_file))
if md5 == d["md5_hash"]:
print(f"Skipping {self.name} - {file}")
print(f"Skipping {self.name} - {filename}")

# Create checksum file
with open(f"{path}/{file}.md5", "w") as f:
with md5_file.open("w") as f:
f.write(d["md5_hash"])
return
# Old Download or corrupted file?
corrupted = False
if corrupted:
os.remove(f"{path}/{file}")
out_file.remove()
return

if not os.path.exists(f"{path}/old"):
os.mkdir(f"{path}/old")
old_dir = self.dir / "old"
old_dir.mkdir(exist_ok=True)

print(f"Moving {file} to old/")
print(f"Moving {filename} to old/")
timestamp = datetime.datetime.now().strftime("%Y-%m-%d")
print(timestamp)
shutil.move(f"{path}/{file}", f"{path}/old/{timestamp}-{file}")
out_file.rename(old_dir / f"{timestamp}-{filename}")

# Get UUID
r = requests.post(
Expand All @@ -150,16 +148,16 @@ def do_download(self, d, token):
)
# response_code = urllib.request.urlopen(url).getcode()
try:
itchiodl.utils.download(url, path, self.name, file)
except itchiodl.utils.NoDownloadError:
utils.download(url, self.dir, self.name, filename)
except utils.NoDownloadError:
print("Http response is not a download, skipping")

with open("errors.txt", "a") as f:
f.write(
f""" Cannot download game/asset: {self.game_slug}
Publisher Name: {self.publisher_slug}
Path: {path}
File: {file}
Path: {out_file}
File: {filename}
Request URL: {url}
This request failed due to a missing response header
This game/asset has been skipped please download manually
Expand All @@ -174,8 +172,8 @@ def do_download(self, d, token):
f.write(
f""" Cannot download game/asset: {self.game_slug}
Publisher Name: {self.publisher_slug}
Path: {path}
File: {file}
Path: {out_file}
File: {filename}
Request URL: {url}
Request Response Code: {e.code}
Error Reason: {e.reason}
Expand All @@ -186,10 +184,10 @@ def do_download(self, d, token):
return

# Verify
if itchiodl.utils.md5sum(f"{path}/{file}") != d["md5_hash"]:
print(f"Failed to verify {file}")
if utils.md5sum(out_file) != d["md5_hash"]:
print(f"Failed to verify {filename}")
return

# Create checksum file
with open(f"{path}/{file}.md5", "w") as f:
with out_file.with_suffix(".md5").open("w") as f:
f.write(d["md5_hash"])
23 changes: 15 additions & 8 deletions itchiodl/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from bs4 import BeautifulSoup

from itchiodl.game import Game
from itchiodl.utils import NoDownloadError


class Library:
Expand Down Expand Up @@ -89,15 +90,21 @@ def load_games(self, publisher):
def download_library(self, platform=None):
"""Download all games in the library"""
with ThreadPoolExecutor(max_workers=self.jobs) as executor:
i = [0]
i = [0, 0]
l = len(self.games)
lock = threading.RLock()

def dl(i, g):
x = g.download(self.login, platform)
with lock:
i[0] += 1
print(f"Downloaded {g.name} ({i[0]} of {l})")
return x

executor.map(functools.partial(dl, i), self.games)
try:
g.download(self.login, platform)
with lock:
i[0] += 1
print(f"Downloaded {g.name} ({i[0]} of {l})")
except NoDownloadError as e:
print(e)
i[1] += 1

r = executor.map(functools.partial(dl, i), self.games)
for _ in r:
pass
print(f"Downloaded {i[0]} Games, {i[1]} Errors")
4 changes: 2 additions & 2 deletions itchiodl/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ def download(url, path, name, file):
def clean_path(path):
"""Cleans a path on windows"""
if sys.platform in ["win32", "cygwin", "msys"]:
path_clean = re.replace(r"[\<\>\:\"\/\\\|\?\*]", "-", path)
path_clean = re.sub(r"[<>:|?*\"\/\\]", "-", path)
return path_clean
return path


def md5sum(path):
"""Returns the md5sum of a file"""
md5 = hashlib.md5()
with open(path, "rb") as f:
with path.open("rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
md5.update(chunk)
return md5.hexdigest()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "itchiodl"
version = "2.2.0"
version = "2.3.0"
description = "Python Scripts for downloading / archiving your itchio library"
authors = ["Peter Taylor <[email protected]>"]
license = "MIT"
Expand Down

0 comments on commit b64e819

Please sign in to comment.