Skip to content

Commit

Permalink
Reimplement duplicated run detection as a file lock
Browse files Browse the repository at this point in the history
  • Loading branch information
DevilXD committed Aug 28, 2023
1 parent bb87b27 commit e03ea75
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 45 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ __pycache__
/cache
/*.jar
log.txt
/lock.file
settings.json
/lang/English.json
8 changes: 4 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"type": "python",
"request": "launch",
"program": "main.py",
"args": ["--no-run-check", "-vv"],
"args": ["-vv"],
"console": "integratedTerminal",
"justMyCode": false
},
Expand All @@ -25,7 +25,7 @@
"type": "python",
"request": "launch",
"program": "main.py",
"args": ["--no-run-check", "-vv", "--tray"],
"args": ["-vv", "--tray"],
"console": "integratedTerminal",
"justMyCode": false
},
Expand All @@ -34,7 +34,7 @@
"type": "python",
"request": "launch",
"program": "main.py",
"args": ["--no-run-check", "-vvv"],
"args": ["-vvv"],
"console": "integratedTerminal",
"justMyCode": false
},
Expand All @@ -43,7 +43,7 @@
"type": "python",
"request": "launch",
"program": "main.py",
"args": ["--no-run-check", "-vvv", "--debug-ws"],
"args": ["-vvv", "--debug-ws"],
"console": "integratedTerminal",
"justMyCode": false
},
Expand Down
1 change: 1 addition & 0 deletions constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def _resource_path(relative_path: Path | str) -> Path:
# Other Paths
LOG_PATH = Path(WORKING_DIR, "log.txt")
CACHE_PATH = Path(WORKING_DIR, "cache")
LOCK_PATH = Path(WORKING_DIR, "lock.file")
CACHE_DB = Path(CACHE_PATH, "mapping.json")
COOKIES_PATH = Path(WORKING_DIR, "cookies.jar")
SETTINGS_PATH = Path(WORKING_DIR, "settings.json")
Expand Down
74 changes: 33 additions & 41 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
from tkinter import messagebox
from typing import IO, NoReturn

if sys.platform == "win32":
import win32gui

if sys.platform == "linux" and sys.version_info >= (3, 10):
import truststore
truststore.inject_into_ssl()
Expand All @@ -30,8 +27,8 @@
from settings import Settings
from version import __version__
from exceptions import CaptchaRequired
from utils import resource_path, set_root_icon
from constants import CALL, SELF_PATH, FILE_FORMATTER, LOG_PATH, WINDOW_TITLE
from utils import lock_file, resource_path, set_root_icon
from constants import CALL, SELF_PATH, FILE_FORMATTER, LOG_PATH, LOCK_PATH

warnings.simplefilter("default", ResourceWarning)

Expand Down Expand Up @@ -107,9 +104,6 @@ def debug_gql(self) -> int:
parser.add_argument("--tray", action="store_true")
parser.add_argument("--log", action="store_true")
# undocumented debug args
parser.add_argument(
"--no-run-check", dest="no_run_check", action="store_true", help=argparse.SUPPRESS
)
parser.add_argument(
"--debug-ws", dest="_debug_ws", action="store_true", help=argparse.SUPPRESS
)
Expand All @@ -131,40 +125,29 @@ def debug_gql(self) -> int:
# get rid of unneeded objects
del root, parser

# check if we're not already running
if sys.platform == "win32":
try:
exists = win32gui.FindWindow(None, WINDOW_TITLE)
except AttributeError:
# we're not on Windows - continue
exists = False
if exists and not settings.no_run_check:
# already running - exit
sys.exit(3)

# set language
try:
_.set_language(settings.language)
except ValueError:
# this language doesn't exist - stick to English
pass

# handle logging stuff
if settings.logging_level > logging.DEBUG:
# redirect the root logger into a NullHandler, effectively ignoring all logging calls
# that aren't ours. This always runs, unless the main logging level is DEBUG or lower.
logging.getLogger().addHandler(logging.NullHandler())
logger = logging.getLogger("TwitchDrops")
logger.setLevel(settings.logging_level)
if settings.log:
handler = logging.FileHandler(LOG_PATH)
handler.setFormatter(FILE_FORMATTER)
logger.addHandler(handler)
logging.getLogger("TwitchDrops.gql").setLevel(settings.debug_gql)
logging.getLogger("TwitchDrops.websocket").setLevel(settings.debug_ws)

# client run
async def main():
# set language
try:
_.set_language(settings.language)
except ValueError:
# this language doesn't exist - stick to English
pass

# handle logging stuff
if settings.logging_level > logging.DEBUG:
# redirect the root logger into a NullHandler, effectively ignoring all logging calls
# that aren't ours. This always runs, unless the main logging level is DEBUG or lower.
logging.getLogger().addHandler(logging.NullHandler())
logger = logging.getLogger("TwitchDrops")
logger.setLevel(settings.logging_level)
if settings.log:
handler = logging.FileHandler(LOG_PATH)
handler.setFormatter(FILE_FORMATTER)
logger.addHandler(handler)
logging.getLogger("TwitchDrops.gql").setLevel(settings.debug_gql)
logging.getLogger("TwitchDrops.websocket").setLevel(settings.debug_ws)

exit_status = 0
client = Twitch(settings)
loop = asyncio.get_running_loop()
Expand Down Expand Up @@ -200,4 +183,13 @@ async def main():
client.gui.close_window()
sys.exit(exit_status)

asyncio.run(main())
try:
# use lock_file to check if we're not already running
success, file = lock_file(LOCK_PATH)
if not success:
# already running - exit
sys.exit(3)

asyncio.run(main())
finally:
file.close()
24 changes: 24 additions & 0 deletions utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import io
import os
import sys
import json
Expand Down Expand Up @@ -77,6 +78,29 @@ def format_traceback(exc: BaseException, **kwargs: Any) -> str:
return ''.join(traceback.format_exception(type(exc), exc, **kwargs))


def lock_file(path: Path) -> tuple[bool, io.TextIOWrapper]:
file = path.open('w', encoding="utf8")
file.write('ツ')
file.flush()
if sys.platform == "win32":
import msvcrt
try:
# we need to lock at least one byte for this to work
msvcrt.locking(file.fileno(), msvcrt.LK_NBLCK, max(path.stat().st_size, 1))
except Exception:
return False, file
return True, file
if sys.platform == "linux":
import fcntl
try:
fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
except Exception:
return False, file
return True, file
# for unsupported systems, just always return True
return True, file


def json_minify(data: JsonType | list[JsonType]) -> str:
"""
Returns minified JSON for payload usage.
Expand Down

0 comments on commit e03ea75

Please sign in to comment.