Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
AUNaseef authored Apr 12, 2021
1 parent 1f0b971 commit 65f670d
Show file tree
Hide file tree
Showing 11 changed files with 1,018 additions and 0 deletions.
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
## Introduction
CLI program and API to automate installation of [GloriousEggroll](https://github.com/GloriousEggroll/)'s [Proton-GE](https://github.com/GloriousEggroll/proton-ge-custom)

## Installation
Install from Python Package Index
```
pip install protonup
```
Install from source
```
git clone https://github.com/AUNaseef/protonup && cd protonup
python3 setup.py install
```

## Usage
Set your installation directory before running the program with `-d "your/compatibilitytools.d/directory"`

Example:
```
protonup -d "~/.steam/root/compatibilitytools.d/"
```
---
To update to the latest version, just run `protonup` from a command line

Example:
```
protonup
```
---
Install a specific version with `-t "version tag"`

Example:
```
protonup -t 6.5-GE-2
```
---
By default the downloads are stored in a temporary folder. Change it with `-o "custom/downlod/directory"`

Example:
```
protonup -o ~/Downloads
```
---
List existing installations with `-l`

Example:
```
protonup -l
```
---
Remove existing installations with `-r "version tag`

Example:
```
protonup -r 6.5-GE-2
```
---
Use `--download` to download Proton-GE to the current working directory without installing it, you can override destination with `-o`

Example:
```
protonup --download
```
---
Use `-y` toggle to carry out actions without any logging or interaction

Example:
```
protonup --download -o ~/Downloads -y
```
---
### Restart Steam after making changes
7 changes: 7 additions & 0 deletions protonup/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""ProtonUp - Manage Proton-GE Installations"""
def main():
from .cli import main
return main()

from .api import install_directory, installed_versions
from .api import fetch_data, get_proton, remove_proton
2 changes: 2 additions & 0 deletions protonup/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .cli import main
main()
134 changes: 134 additions & 0 deletions protonup/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""ProtonUp API"""
import os
import shutil
from configparser import ConfigParser
import tarfile
import requests
from .utilities import download
from .constants import CONFIG_FILE, PROTONGE_URL, MIB
from .constants import TEMP_DIR, DEFAULT_INSTALL_DIR


def fetch_data(tag):
"""
Fetch ProtonGE release information from github
Return Type: dict
Content(s):
'version', date', 'download', 'size', 'checksum'
"""
url = PROTONGE_URL + (f'tags/{tag}' if tag and tag is not 'latest' else 'latest')
data = requests.get(url).json()
if 'tag_name' not in data:
return # invalid tag

values = {'version': data['tag_name'], 'date': data['published_at'].split('T')[0]}
for asset in data['assets']:
if asset['name'].endswith('sha512sum'):
values['checksum'] = asset['browser_download_url']
elif asset['name'].endswith('tar.gz'):
values['download'] = asset['browser_download_url']
values['size'] = asset['size']
return values


def install_directory(target=None):
"""
Custom install directory
Return Type: str
"""
config = ConfigParser()
if target and target.lower() != 'get':
if target.lower() == 'default':
target = DEFAULT_INSTALL_DIR
if not target.endswith('/'):
target += '/'
if not config.has_section('protonup'):
config.add_section('protonup')
config['protonup']['installdir'] = target
os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)
with open(CONFIG_FILE, 'w') as file:
config.write(file)
return target
elif os.path.exists(CONFIG_FILE):
config.read(CONFIG_FILE)
if config.has_option('protonup', 'installdir'):
return os.path.expanduser(config['protonup']['installdir'])
else:
return DEFAULT_INSTALL_DIR


def installed_versions():
"""
List of proton installations
Return Type: str[]
"""
installdir = install_directory()
versions_found = []

if os.path.exists(installdir):
folders = os.listdir(installdir)
# Find names of directories with proton
for folder in folders:
if os.path.exists(f'{installdir}/{folder}/proton'):
versions_found.append(folder)

return versions_found


def get_proton(version=None, yes=True, dl_only=False, output=None):
"""Download and (optionally) install Proton"""
installdir = install_directory()
data = fetch_data(tag=version)

if not data or 'download' not in data:
if not yes:
print('[ERROR] invalid tag / binary not found')
return False # invalid tag or no download link

protondir = installdir + 'Proton-' + data['version']

if os.path.exists(protondir) and not dl_only:
# TODO: Check if data['hash'] matches installation
if not yes:
print(f"[INFO] Proton-{data['version']} already installed")
return False

if not yes:
print(f"Ready to download Proton-{data['version']}",
f"\nSize : {round(data['size']/MIB, 2)} MiB",
f"\nPublished : {data['date']}")
if not input("Continue? (Y/N): ") in ['y', 'Y']:
return

# Prepare destination
destination = output if output else (os.getcwd() if dl_only else TEMP_DIR)
if not destination.endswith('/'):
destination += '/'
destination += data['download'].split('/')[-1]
destination = os.path.expanduser(destination)

download(url=data['download'], destination=destination, show_progress=not yes)
# TODO: Check if data['hash'] matches the downloaded file

# Installation
if not dl_only:
if os.path.exists(protondir):
shutil.rmtree(protondir)
tarfile.open(destination, "r:gz").extractall(install_directory())
if not yes:
print('[INFO] Installed in: ' + protondir)
elif not yes:
print('[INFO] Dowloaded to: ' + destination)

shutil.rmtree(TEMP_DIR, ignore_errors=True)


def remove_proton(version=None, yes=True):
"""Uninstall existing proton installation"""
target = install_directory() + "Proton-" + version
if os.path.exists(target):
if yes or input(f'Do you want to remove {version}? (y/n) : ') in ['y', 'Y']:
shutil.rmtree(install_directory() + 'Proton-' + version)
else:
return False
return True
39 changes: 39 additions & 0 deletions protonup/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""ProtonUp CLI"""
import sys
import argparse
from .api import install_directory, installed_versions
from .api import get_proton, remove_proton
from .utilities import folder_size
from .constants import TEMP_DIR, MIB


def parse_arguments():
"""Parse commandline arguments"""
parser = argparse.ArgumentParser(usage="%(prog)s", description="Manage Proton-GE Installations",
epilog="GPLv3 - Repo : https://github.com/AUNaseef/protonup")
parser.add_argument('-t', '--tag', type=str, default=None, help='install a specific version')
parser.add_argument('-l', '--list', action='store_true', help='list installed version')
parser.add_argument('-r', '--rem', type=str, default=None, metavar='TAG',
help='remove existing installations')
parser.add_argument('-o', '--output', type=str, default=None, metavar='DIR',
help='set download directory')
parser.add_argument('-d', '--dir', type=str, default=None, help='set installation directory')
parser.add_argument('-y', '--yes', action='store_true', help='disable prompts and logs')
parser.add_argument('--download', action='store_true', help='download only')
return parser.parse_args()


def main():
"""Start here"""
args = parse_arguments()
if args.dir:
print(f"Install directory set to '{install_directory(args.dir)}'")
if args.tag or not (args.rem or args.list or args.dir):
get_proton(version=args.tag, yes=args.yes, dl_only=args.download,
output=args.output)
if args.rem:
if not remove_proton(version=args.rem, yes=args.yes):
print('Proton-{version} not installed')
if args.list:
for item in installed_versions():
print(f"{item} - {round(folder_size(install_directory() + item)/MIB, 2)} MiB")
8 changes: 8 additions & 0 deletions protonup/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Constant Values"""
import os
CONFIG_FILE = os.path.expanduser('~/.config/protonup/config.ini')
DEFAULT_INSTALL_DIR = os.path.expanduser('~/.steam/root/compatibilitytools.d/')
TEMP_DIR = '/tmp/protonup/'
PROTONGE_URL = 'https://api.github.com/repos/GloriousEggroll/proton-ge-custom/releases/'
MIB = 1048576 # One mebibyte in bytes
BUFFER_SIZE = 65536 # Work with 64 kb chunks
55 changes: 55 additions & 0 deletions protonup/utilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Utilities"""
import os
import sys
import hashlib
import requests
from .constants import MIB, BUFFER_SIZE


def download(url, destination, show_progress=False):
"""Download files"""
try:
file = requests.get(url, stream=True)
except OSError:
return False

if show_progress:
f_size = int(file.headers.get('content-length'))
f_size_mib = round(f_size / MIB, 2)
c_count = f_size / BUFFER_SIZE
c_current = 1
destination = os.path.expanduser(destination)
os.makedirs(os.path.dirname(destination), exist_ok=True)
with open(destination, 'wb') as dest:
for chunk in file.iter_content(chunk_size=BUFFER_SIZE):
if chunk:
dest.write(chunk)
dest.flush()
if show_progress:
progress = min(round((c_current / c_count) * 100, 2), 100.00)
downloaded = round((c_current * BUFFER_SIZE) / MIB, 2)
sys.stdout.write(f'\rDownloaded {progress}% - {downloaded} MiB/{f_size_mib} MiB')
c_current += 1
if show_progress:
sys.stdout.write('\n')
return True


def get_sha512(filename):
sha512sum = hashlib.sha512()
with open(filename, 'rb') as file:
while True:
data = file.read(BUFFER_SIZE)
if not data:
break
sha512sum.update(data)
return sha512sum.hexdigest()


def folder_size(folder):
"""Calculate the size of a folder"""
size = 0
for root, dirs, files in os.walk(folder, onerror=None):
for file in files:
size += os.path.getsize(os.path.join(root, file))
return size
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
22 changes: 22 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[metadata]
name = protonup
version = 0.0.2
description = Manage Proton-GE Installations
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/AUNaseef/protonup
author = Naseef
license = GPL-3.0
license_files =
LICENSE

[options]
packages = find:
install_requires =
requests
python_requires = >3.6

[options.entry_points]
console_scripts =
protonup = protonup:main

2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import setuptools
setuptools.setup()

0 comments on commit 65f670d

Please sign in to comment.