|
#!/usr/bin/env python3 |
|
""" |
|
use "tqsl" command line utility to upload default WSJT-X log file to LoTW |
|
|
|
MIT License |
|
by W2NRL |
|
""" |
|
|
|
import shutil |
|
import subprocess |
|
import os |
|
import functools |
|
from pathlib import Path |
|
from dateutil.parser import parse |
|
import argparse |
|
import platform |
|
from datetime import datetime |
|
import re |
|
|
|
|
|
@functools.cache |
|
def find_tqsl(): |
|
|
|
path = None |
|
|
|
if platform.system() == "Darwin": |
|
p = Path("/Applications/TrustedQSL/tqsl.app/Contents/MacOS") |
|
if p.is_dir(): |
|
path = p |
|
|
|
if tqsl := shutil.which("tqsl", path=path): |
|
return tqsl |
|
|
|
raise FileNotFoundError("TQSL command line utility not found on PATH.") |
|
|
|
|
|
@functools.cache |
|
def find_log_dir() -> Path: |
|
system = platform.system() |
|
|
|
match system: |
|
case "Linux": |
|
d = Path.home() / ".local/share/WSJT-X" |
|
case "Darwin": |
|
d = Path.home() / "Library/Application Support/WSJT-X" |
|
case "Windows": |
|
d = Path(os.environ["LOCALAPPDATA"]) / "WSJT-X" |
|
case _: |
|
raise ValueError(f"Unsupported OS: {system}") |
|
|
|
if not d.is_dir(): |
|
raise FileNotFoundError(f"Log directory not found: {d}") |
|
|
|
return d |
|
|
|
|
|
def upload_new_log(log_file: Path, t0: datetime) -> int: |
|
|
|
tqsl_exe = find_tqsl() |
|
|
|
opts = ["-a", "compliant", f"-b {t0:%Y-%m-%d}", "-d", "-u", "-x", str(log_file)] |
|
cmd = [tqsl_exe, *opts] |
|
|
|
print(" ".join(cmd)) |
|
ret = subprocess.run(cmd, capture_output=True, text=True) |
|
|
|
# the -x batch option also makes tqsl output on stderr only |
|
# this output is lines ending with \n |
|
if ret.returncode == 0: |
|
lines = ret.stderr.split('\n') |
|
upload_line = next((line for line in lines if line.startswith("Attempting to upload")), None) |
|
if upload_line: |
|
if "Attempting to upload one QSO" in upload_line: |
|
return 1 |
|
|
|
match = re.search(r"Attempting to upload (\d+) QSOs", upload_line) |
|
if match: |
|
return int(match.group(1)) |
|
else: |
|
if "Final Status: No QSOs to upload" in ret.stderr: |
|
return 0 |
|
|
|
return -1 |
|
|
|
|
|
if __name__ == "__main__": |
|
parser = argparse.ArgumentParser(description="Upload WSJT-X log file to LoTW using TQSL.") |
|
parser.add_argument("-f", "--log_file", type=Path, help="Path to the WSJT-X log file to upload.") |
|
parser.add_argument( |
|
"-d", |
|
"--date", |
|
type=str, |
|
help="Date to use for the upload (YYYY-MM-DD). Defaults to today.", |
|
default=datetime.now(), |
|
) |
|
|
|
args = parser.parse_args() |
|
|
|
if args.log_file is None: |
|
log_file = find_log_dir() / "wsjtx_log.adi" |
|
else: |
|
log_file = Path(args.log_file).expanduser() |
|
|
|
if not log_file.exists(): |
|
raise FileNotFoundError(f"Log file not found: {log_file}") |
|
|
|
if isinstance(args.date, str): |
|
t0 = parse(args.date) |
|
else: |
|
t0 = args.date |
|
|
|
N = upload_new_log(log_file, t0) |
|
if N > 0: |
|
print(f"{N} QSOs uploaded to LoTW.") |
|
elif N == 0: |
|
print("No new QSOs to upload.") |
|
else: |
|
raise SystemExit("Failed to upload log file.") |