Skip to content

Commit 9e4d913

Browse files
authored
Merge pull request #79 from ashiven/linux
Linux
2 parents 30854af + 0a8d9fc commit 9e4d913

File tree

5 files changed

+172
-26
lines changed

5 files changed

+172
-26
lines changed

.github/workflows/pyinstaller.yml

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ jobs:
1111
strategy:
1212
fail-fast: false
1313
matrix:
14-
os: ["windows-latest"] # Currently, other OS's are not supported
14+
os: ["windows-latest", "ubuntu-latest"]
1515

1616
env:
1717
MAIN_FILE: '"./cs2tracker/__main__.py"'
1818
NAME: '"cs2tracker"'
1919
ICON: '"./assets/icon.png"'
20-
ICON_INCLUDE: '"./assets/icon.png;./assets"'
21-
DATA_DIR_INCLUDE: '"./cs2tracker/data;./data"'
22-
NODE_MODULES_INCLUDE: '"./node_modules;./node_modules"'
20+
WINDOWS_ICON_INCLUDE: '"./assets/icon.png;./assets"'
21+
WINDOWS_DATA_DIR_INCLUDE: '"./cs2tracker/data;./data"'
22+
WINDOWS_NODE_MODULES_INCLUDE: '"./node_modules;./node_modules"'
23+
LINUX_ICON_INCLUDE: '"./assets/icon.png:./assets"'
24+
LINUX_DATA_DIR_INCLUDE: '"./cs2tracker/data:./data"'
25+
LINUX_NODE_MODULES_INCLUDE: '"./node_modules:./node_modules"'
2326

2427
steps:
2528
- name: Checkout code
@@ -44,38 +47,83 @@ jobs:
4447
- name: Install PyInstaller
4548
run: pip install pyinstaller
4649

47-
- name: Locate eurofxref-hist.zip
50+
- name: Locate eurofxref-hist.zip Windows
51+
if: matrix.os == 'windows-latest'
4852
run: |
4953
$ZIP_PATH = python -c "import currency_converter, os; print(os.path.join(os.path.dirname(currency_converter.__file__), 'eurofxref-hist.zip'))"
50-
echo "CURRENCY_INCLUDE='$ZIP_PATH;./currency_converter'" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
54+
echo "WINDOWS_CURRENCY_INCLUDE='$ZIP_PATH;./currency_converter'" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
5155
52-
- name: Locate nodejs-bin files
56+
- name: Locate eurofxref-hist.zip Linux
57+
if: matrix.os == 'ubuntu-latest'
58+
run: |
59+
ZIP_PATH=$(python -c "import currency_converter, os; print(os.path.join(os.path.dirname(currency_converter.__file__), 'eurofxref-hist.zip'))")
60+
echo "LINUX_CURRENCY_INCLUDE=$ZIP_PATH:./currency_converter" >> $GITHUB_ENV
61+
62+
- name: Locate nodejs-bin files Windows
63+
if: matrix.os == 'windows-latest'
5364
run: |
5465
$NODE_BIN_PATH = python -c "import nodejs, os; print(os.path.dirname(nodejs.__file__))"
55-
echo "NODE_BIN_INCLUDE='$NODE_BIN_PATH;./nodejs'" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
66+
echo "WINDOWS_NODE_BIN_INCLUDE='$NODE_BIN_PATH;./nodejs'" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
67+
68+
- name: Locate nodejs-bin files Linux
69+
if: matrix.os == 'ubuntu-latest'
70+
run: |
71+
NODE_BIN_PATH=$(python -c "import nodejs, os; print(os.path.dirname(nodejs.__file__))")
72+
echo "LINUX_NODE_BIN_INCLUDE=$NODE_BIN_PATH:./nodejs" >> $GITHUB_ENV
5673
57-
- name: Build executable
74+
- name: Build Windows executable
75+
if: matrix.os == 'windows-latest'
5876
run: |
5977
pyinstaller --noconfirm --onefile --windowed --name ${{ env.NAME }} --icon ${{ env.ICON }} `
60-
--add-data ${{ env.ICON_INCLUDE }} `
61-
--add-data ${{ env.DATA_DIR_INCLUDE }} `
62-
--add-data ${{ env.NODE_MODULES_INCLUDE }} `
63-
--add-data ${{ env.CURRENCY_INCLUDE }} `
64-
--add-data ${{ env.NODE_BIN_INCLUDE }} `
78+
--add-data ${{ env.WINDOWS_ICON_INCLUDE }} `
79+
--add-data ${{ env.WINDOWS_DATA_DIR_INCLUDE }} `
80+
--add-data ${{ env.WINDOWS_NODE_MODULES_INCLUDE }} `
81+
--add-data ${{ env.WINDOWS_CURRENCY_INCLUDE }} `
82+
--add-data ${{ env.WINDOWS_NODE_BIN_INCLUDE }} `
83+
${{ env.MAIN_FILE }}
84+
85+
- name: Build Linux executable
86+
if: matrix.os == 'ubuntu-latest'
87+
run: |
88+
pyinstaller --noconfirm --onefile --windowed --name ${{ env.NAME }} --icon ${{ env.ICON }} \
89+
--add-data ${{ env.LINUX_ICON_INCLUDE }} \
90+
--add-data ${{ env.LINUX_DATA_DIR_INCLUDE }} \
91+
--add-data ${{ env.LINUX_NODE_MODULES_INCLUDE }} \
92+
--add-data ${{ env.LINUX_CURRENCY_INCLUDE }} \
93+
--add-data ${{ env.LINUX_NODE_BIN_INCLUDE }} \
6594
${{ env.MAIN_FILE }}
6695
6796
- name: List files in dist folder
6897
run: ls -R dist/
6998

70-
- name: Zip windows executable
99+
- name: Zip Windows executable
100+
if: matrix.os == 'windows-latest'
71101
run: Compress-Archive -Path "dist/cs2tracker.exe" -DestinationPath "cs2tracker-windows.zip"
72102

73-
- name: Generate SHA256 checksum
103+
- name: Zip Linux executable
104+
if: matrix.os == 'ubuntu-latest'
105+
run: zip cs2tracker-linux.zip dist/cs2tracker
106+
107+
- name: Generate SHA256 checksum Windows
108+
if: matrix.os == 'windows-latest'
74109
run: shasum -a 256 cs2tracker-windows.zip > cs2tracker-windows.zip.sha256
75110

76-
- name: Upload windows executable
111+
- name: Generate SHA256 checksum Linux
112+
if: matrix.os == 'ubuntu-latest'
113+
run: shasum -a 256 cs2tracker-linux.zip > cs2tracker-linux.zip.sha256
114+
115+
- name: Upload Windows executable
116+
if: matrix.os == 'windows-latest'
77117
uses: alexellis/upload-assets@0.4.0
78118
env:
79119
GITHUB_TOKEN: ${{ github.token }}
80120
with:
81121
asset_paths: '["cs2tracker-windows.zip", "cs2tracker-windows.zip.sha256"]'
122+
123+
- name: Upload Linux executable
124+
if: matrix.os == 'ubuntu-latest'
125+
uses: alexellis/upload-assets@0.4.0
126+
env:
127+
GITHUB_TOKEN: ${{ github.token }}
128+
with:
129+
asset_paths: '["cs2tracker-linux.zip", "cs2tracker-linux.zip.sha256"]'

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
__pycache__
55
venv
66
venv-test
7+
venv-wsl
78

89
# Python build output
910
build

cs2tracker/constants.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,36 @@
1818
class OSType(enum.Enum):
1919
WINDOWS = "Windows"
2020
LINUX = "Linux"
21+
MACOS = "MacOS"
2122

2223

23-
OS = OSType.WINDOWS if sys.platform.startswith("win") else OSType.LINUX
24-
PYTHON_EXECUTABLE = sys.executable
24+
if sys.platform.startswith("win"):
25+
OS = OSType.WINDOWS
26+
elif sys.platform.startswith("linux"):
27+
OS = OSType.LINUX
28+
elif sys.platform.startswith("darwin"):
29+
OS = OSType.MACOS
30+
else:
31+
raise NotImplementedError(f"Unsupported OS: {sys.platform}")
2532

2633

34+
PYTHON_EXECUTABLE = sys.executable
2735
RUNNING_IN_EXE = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
2836

2937
if RUNNING_IN_EXE:
3038
MEIPASS_DIR = sys._MEIPASS # type: ignore pylint: disable=protected-access
3139
MODULE_DIR = MEIPASS_DIR
3240
PROJECT_DIR = MEIPASS_DIR
33-
APP_DATA_DIR = os.path.join(os.path.expanduser("~"), "AppData", "Local")
41+
42+
if OS == OSType.WINDOWS:
43+
APP_DATA_DIR = os.path.join(os.path.expanduser("~"), "AppData", "Local")
44+
elif OS == OSType.LINUX:
45+
APP_DATA_DIR = os.environ.get(
46+
"XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share")
47+
)
48+
else:
49+
raise NotImplementedError(f"Unsupported OS: {OS}")
50+
3451
DATA_DIR = os.path.join(APP_DATA_DIR, "cs2tracker", "data")
3552
os.makedirs(DATA_DIR, exist_ok=True)
3653

cs2tracker/scraper/background_task.py

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
from subprocess import DEVNULL, call
2+
from subprocess import DEVNULL, STDOUT, CalledProcessError, call, check_output, run
33

44
from cs2tracker.constants import (
55
BATCH_FILE,
@@ -18,6 +18,18 @@
1818
f"powershell -WindowStyle Hidden -Command \"Start-Process '{BATCH_FILE}' -WindowStyle Hidden\""
1919
)
2020

21+
LINUX_BACKGROUND_TASK_SCHEDULE = "0 12 * * *"
22+
LINUX_BACKGROUND_TASK_CMD = (
23+
f"bash -c 'cd {PROJECT_DIR} && {PYTHON_EXECUTABLE} -m cs2tracker --only-scrape'"
24+
)
25+
LINUX_BACKGROUND_TASK_CMD_EXE = f"bash -c 'cd {PROJECT_DIR} && {PYTHON_EXECUTABLE} --only-scrape'"
26+
27+
if RUNNING_IN_EXE:
28+
LINUX_CRON_JOB = f"{LINUX_BACKGROUND_TASK_SCHEDULE} {LINUX_BACKGROUND_TASK_CMD_EXE}"
29+
else:
30+
LINUX_CRON_JOB = f"{LINUX_BACKGROUND_TASK_SCHEDULE} {LINUX_BACKGROUND_TASK_CMD}"
31+
32+
2133
console = get_console()
2234

2335

@@ -34,8 +46,17 @@ def identify(cls):
3446
return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
3547
found = return_code == 0
3648
return found
49+
elif OS == OSType.LINUX:
50+
try:
51+
existing_jobs = (
52+
check_output(["crontab", "-l"], stderr=STDOUT).decode("utf-8").strip()
53+
)
54+
except CalledProcessError:
55+
existing_jobs = ""
56+
57+
found = LINUX_CRON_JOB in existing_jobs.splitlines()
58+
return found
3759
else:
38-
# TODO: implement finder for cron jobs
3960
return False
4061

4162
@classmethod
@@ -83,17 +104,69 @@ def _toggle_windows(cls, enabled: bool):
83104
]
84105
return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
85106
if return_code == 0:
86-
console.print("[bold green][+] Background task enabled.")
107+
console.info("Background task enabled.")
87108
else:
88109
console.error("Failed to enable background task.")
89110
else:
90111
cmd = ["schtasks", "/delete", "/tn", WIN_BACKGROUND_TASK_NAME, "/f"]
91112
return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
92113
if return_code == 0:
93-
console.print("[bold green][-] Background task disabled.")
114+
console.info("Background task disabled.")
94115
else:
95116
console.error("Failed to disable background task.")
96117

118+
@classmethod
119+
def _toggle_linux(cls, enabled: bool):
120+
"""
121+
Create or delete a daily background task that runs the scraper on Linux.
122+
123+
:param enabled: If True, the task will be created; if False, the task will be
124+
deleted.
125+
"""
126+
try:
127+
existing_jobs = check_output(["crontab", "-l"], stderr=STDOUT).decode("utf-8").strip()
128+
except CalledProcessError:
129+
existing_jobs = ""
130+
131+
cron_lines = existing_jobs.splitlines()
132+
133+
if enabled and LINUX_CRON_JOB not in cron_lines:
134+
updated_jobs = (
135+
existing_jobs + "\n" + LINUX_CRON_JOB + "\n"
136+
if existing_jobs
137+
else LINUX_CRON_JOB + "\n"
138+
)
139+
try:
140+
run(
141+
["crontab", "-"],
142+
input=updated_jobs.encode("utf-8"),
143+
stdout=DEVNULL,
144+
stderr=DEVNULL,
145+
check=True,
146+
)
147+
console.info("Background task enabled.")
148+
except CalledProcessError:
149+
console.error("Failed to enable background task.")
150+
151+
elif not enabled and LINUX_CRON_JOB in cron_lines:
152+
updated_jobs = "\n".join(
153+
line for line in cron_lines if line.strip() != LINUX_CRON_JOB
154+
).strip()
155+
try:
156+
if updated_jobs:
157+
run(
158+
["crontab", "-"],
159+
input=(updated_jobs + "\n").encode("utf-8"),
160+
stdout=DEVNULL,
161+
stderr=DEVNULL,
162+
check=True,
163+
)
164+
else:
165+
run(["crontab", "-r"], stdout=DEVNULL, stderr=DEVNULL, check=True)
166+
console.info("Background task disabled.")
167+
except CalledProcessError:
168+
console.error("Failed to disable background task.")
169+
97170
@classmethod
98171
def toggle(cls, enabled: bool):
99172
"""
@@ -104,6 +177,7 @@ def toggle(cls, enabled: bool):
104177
"""
105178
if OS == OSType.WINDOWS:
106179
cls._toggle_windows(enabled)
180+
elif OS == OSType.LINUX:
181+
cls._toggle_linux(enabled)
107182
else:
108-
# TODO: implement toggle for cron jobs
109183
pass

cs2tracker/scraper/parser.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ class CSGOTraderParser(BaseParser):
127127
CSGOTRADER_PRICE_LIST = "https://prices.csgotrader.app/latest/{}.json"
128128
PRICE_INFO = "Owned: {:<10} {:<10}: ${:<10} Total: ${:<10}"
129129
NEEDS_TIMEOUT = False
130-
SOURCES = [PriceSource.STEAM, PriceSource.BUFF163, PriceSource.YOUPIN898]
130+
SOURCES = [PriceSource.STEAM, PriceSource.BUFF163, PriceSource.CSFLOAT]
131131

132132
@classmethod
133133
def get_item_page_url(cls, item_href, source=PriceSource.STEAM):
@@ -176,6 +176,12 @@ def parse_item_price(cls, item_page, item_href, source=PriceSource.STEAM):
176176
raise ValueError(
177177
f"CSGOTrader: Could not find recent youpin898 price: {url_decoded_name}"
178178
)
179+
elif source == PriceSource.CSFLOAT:
180+
price = price_info.get("price")
181+
if not price:
182+
raise ValueError(
183+
f"CSGOTrader: Could not find recent csfloat price: {url_decoded_name}"
184+
)
179185
else:
180186
price = price_info.get("starting_at")
181187
if not price:

0 commit comments

Comments
 (0)