Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: recovery features #423

Merged
merged 12 commits into from
Dec 20, 2023
31 changes: 29 additions & 2 deletions languages/ru_RU/LC_MESSAGES/sportorg.po
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: SportOrg\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-12 11:13+0300\n"
"PO-Revision-Date: 2023-08-27 15:09+0500\n"
"PO-Revision-Date: 2023-12-17 19:16+0500\n"
"Last-Translator: SportOrg\n"
"Language-Team: SportOrg\n"
"Language: ru_RU\n"
Expand All @@ -12,7 +12,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Loco-Source-Locale: ru_RU\n"
"X-Generator: Poedit 3.3.2\n"
"X-Generator: Poedit 3.4.1\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Loco-Parser: loco_parse_po\n"
"X-Poedit-Basepath: ../../../sportorg\n"
Expand Down Expand Up @@ -1568,3 +1568,30 @@ msgstr "Ошибка открытия файла. Файл уже открыт

msgid "Multi day race"
msgstr "Многодневная гонка"

msgid "SportOrg HTML"
msgstr "Файл HTML, SportOrg"

msgid "SportOrg HTML report (*.html)"
msgstr "Файл HTML, SportOrg (*.html)"

msgid "Open SportOrg HTML file"
msgstr "Открыть файл HTML, SportOrg"

msgid "SportOrg SI log"
msgstr "Файл лога чипов, SportOrg"

msgid "SportOrg SI log (*.log)"
msgstr "Файл лога чипов, SportOrg (*.log)"

msgid "Open SportOrg SI log file"
msgstr "Открыть файл лога чипов, SportOrg"

msgid "SPORTident master station CSV"
msgstr "Файл CSV мастер-станции SPORTident"

msgid "CSV file (*.csv)"
msgstr "Файл CSV (*.csv)"

msgid "Open SPORTident master station backup file"
msgstr "Открыть файл CSV мастер-станции SPORTident"
4 changes: 2 additions & 2 deletions sportorg/gui/dialogs/file_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from sportorg.modules.configs.configs import Config, ConfigFile


def get_open_file_name(caption='', filter_text=''):
def get_open_file_name(caption='', filter_text='', set_dir=True):
result = QFileDialog.getOpenFileName(None, caption, get_default_dir(), filter_text)[
0
]
if result:
if result and set_dir:
set_default_dir(os.path.dirname(os.path.abspath(result)))
return result

Expand Down
23 changes: 23 additions & 0 deletions sportorg/gui/menu/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
from sportorg.modules.live.live import live_client
from sportorg.modules.ocad import ocad
from sportorg.modules.ocad.ocad import OcadImportException
from sportorg.modules.recovery import (
recovery_si_master_csv,
recovery_sportorg_html,
recovery_sportorg_si_log,
)
from sportorg.modules.rfid_impinj.rfid_impinj import ImpinjClient
from sportorg.modules.sfr.sfrreader import SFRReaderClient
from sportorg.modules.sportident.sireader import SIReaderClient
Expand Down Expand Up @@ -345,6 +350,24 @@ def execute(self):
)


class RecoverySportorgHtmlAction(Action, metaclass=ActionFactory):
def execute(self):
recovery_sportorg_html.recovery()
self.app.refresh()


class RecoverySportorgSiLogAction(Action, metaclass=ActionFactory):
def execute(self):
recovery_sportorg_si_log.recovery()
self.app.refresh()


class RecoverySportidentMasterCsvAction(Action, metaclass=ActionFactory):
def execute(self):
recovery_si_master_csv.recovery()
self.app.refresh()


class AddObjectAction(Action, metaclass=ActionFactory):
def execute(self):
self.app.add_object()
Expand Down
12 changes: 12 additions & 0 deletions sportorg/gui/menu/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ def menu_list():
'title': translate('IOF xml'),
'action': 'IOFEntryListImportAction',
},
{
'title': translate('SPORTident master station CSV'),
'action': 'RecoverySportidentMasterCsvAction',
},
{
'title': translate('SportOrg SI log'),
'action': 'RecoverySportorgSiLogAction',
},
{
'title': translate('SportOrg HTML'),
'action': 'RecoverySportorgHtmlAction',
},
],
},
{
Expand Down
71 changes: 71 additions & 0 deletions sportorg/modules/recovery/recovery_si_master_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""
Parse backup memory CSV file of BSM SPORTident station generated by SI Config Plus

-- Format:
CSV, separator ";"
Needed positions: card number (2), start (15), finish (21), splits (44): count of punches, (code + time) * n

No;Read on;SIID;Start no;Clear CN;Clear DOW;Clear time;Clear_r CN;Clear_r DOW;Clear_r time;Check CN;Check DOW;Check time
;Start CN;Start DOW;Start time;Start_r CN;Start_r DOW;Start_r time;Finish CN;Finish DOW;Finish time;Finish_r CN;Finish_r
DOW;Finish_r time;Class;First name;Last name;Club;Country;Email;Date of birth;Sex;Phone;Street;ZIP;City;Hardware version
;Software version;Battery date;Battery voltage;Clear count;Character set;SEL_FEEDBACK;No. of records;Record 1 CN;Record
1 DOW;Record 1 time;Record 2 CN;Record 2 DOW;Record 2 time;Record 3 CN;

-- Example:
440;2023-12-17 11:59:02;2007313;;2;Su; 12:40:50;;;;2;Su; 12:40:50;;;;;;;1;Su; 14:50:45;;;;;2007313;SPORTident Ru;;;;;;;;
;;;;;;;;;10;34;Su; 12:41:33;49;Su; 12:54:39;43;Su; 13:22:28;39;Su; 13:51:28;42;Su; 14:02:34;46;Su; 14:15:04;40;Su; 14:31
:03;47;Su; 14:39:54;37;Su; 14:47:02;90;Su; 14:49:21;

"""
from sportorg.common.otime import OTime
from sportorg.gui.dialogs.file_dialog import get_open_file_name
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

эта функция не должна быть в modules. Лучше передай в recovery готовый путь. Должна быть правильная работа с зависимостями

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ОК, хорошо, буду передавать путь

from sportorg.language import translate
from sportorg.models.memory import ResultSportident, Split, race
from sportorg.modules.sportident.fix_time_sicard_5 import fix_time
from sportorg.utils.time import hhmmss_to_time

POS_CARD = 2
POS_START = 15
POS_FINISH = 21
POS_COUNT = 44
race = race()


def recovery():
file_name = get_open_file_name(
translate('Open SPORTident master station backup file'),
translate('CSV file (*.csv)'),
False,
)

text_file = open(file_name, 'r')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from pathlib import Path

Path(...).read_lines()

Лучше сделать так. Ты же здесь файл даже не закрываешь)
Те если сделал open, надо делать close, можно было через контекстный менеджер with ..., но лучше Path использовать в этом кейсе

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ОК, согласен, можно было бы и закрыть файл ) Переделаю на Path

lines = text_file.readlines()

separator = ';'

zero_time_val = race.get_setting('system_zero_time', (8, 0, 0))
zero_time = OTime(
hour=zero_time_val[0], minute=zero_time_val[1], sec=zero_time_val[2]
)

for line in lines:
tokens = line.split(separator)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

лучше использовать модуль csv в питоне, он вообще в питоне из коробки доступен)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

принято

if tokens[0] == 'No' or len(tokens) < 45:
continue

res = ResultSportident()
res.card_number = int(tokens[POS_CARD])
res.start_time = hhmmss_to_time(tokens[POS_START])
res.finish_time = hhmmss_to_time(tokens[POS_FINISH])

punch_count = int(tokens[POS_COUNT])
existing_punches = (len(tokens) - POS_COUNT - 1) // 3

for i in range(min(punch_count, existing_punches)):
punch = Split()
punch.code = tokens[POS_COUNT + 3 * i + 1]
punch.time = hhmmss_to_time(tokens[POS_COUNT + 3 * i + 3])
res.splits.append(punch)

fix_time(res, zero_time)
race.results.append(res)
42 changes: 42 additions & 0 deletions sportorg/modules/recovery/recovery_sportorg_html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Parse SportOrg HTML report (containing full json)

"""
import os.path
import string
from io import open
from random import choices
from tempfile import gettempdir

from sportorg.gui.dialogs.file_dialog import get_open_file_name
from sportorg.gui.dialogs.sportorg_import_dialog import SportOrgImportDialog
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тут тоже самое. Надо инверсию зависимостей или передать готовые значения

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ок

from sportorg.language import translate
from sportorg.modules.backup.json import get_races_from_file


def recovery():
file_name = get_open_file_name(
translate('Open SportOrg HTML file'),
translate('SportOrg HTML report (*.html)'),
False,
)

text_file = open(file_name, 'r', encoding='utf-8')
lines = text_file.readlines()

for line in lines:
if line.find("var race = {\"courses\":") > -1:
json = line.strip()[11:-1]

# save json to tmp file and op[en with standard import action
tmp_filename = os.path.join(
gettempdir(),
'sportorg_' + ''.join(choices(string.ascii_letters, k=10)) + '.json',
)
temp_file = open(tmp_filename, 'w')
temp_file.write(json)
temp_file.close()

with open(tmp_filename) as f:
attr = get_races_from_file(f)
SportOrgImportDialog(*attr).exec_()
89 changes: 89 additions & 0 deletions sportorg/modules/recovery/recovery_sportorg_si_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""
Parse backup SI log generated in C:\\Program Files (x86)\\SportOrg\\log

-- Format:

start
[SI_CARD]
[START] - 0:00:00 if not exist
[FINISH] - 0:00:00 if not exist
split_start
[CODE_1] [TIME_1]
...
[CODE_N] [TIME_N]
split_end
end

-- Example:

start
8013787
00:00:00
11:39:25
split_start
57 11:43:05
69 11:37:59
90 11:39:07
split_end
end

"""
from sportorg.common.otime import OTime
from sportorg.gui.dialogs.file_dialog import get_open_file_name
from sportorg.language import translate
from sportorg.models.memory import ResultSportident, Split, race
from sportorg.modules.sportident.fix_time_sicard_5 import fix_time
from sportorg.utils.time import hhmmss_to_time

race = race()


def recovery():
zero_time_val = race.get_setting('system_zero_time', (8, 0, 0))
zero_time = OTime(
hour=zero_time_val[0], minute=zero_time_val[1], sec=zero_time_val[2]
)

file_name = get_open_file_name(
translate('Open SportOrg SI log file'),
translate('SportOrg SI log (*.log)'),
False,
)

text_file = open(file_name, 'r')
lines = text_file.readlines()

cur_res = ResultSportident()
read_num = False
read_start = False
read_spl = False
read_finish = False

for line in lines:
line = line.strip()
if read_num:
cur_res.card_number = int(line)
read_start = True
read_num = False
elif read_start:
cur_res.start_time = hhmmss_to_time(line)
read_start = False
read_finish = True
elif read_finish:
cur_res.finish_time = hhmmss_to_time(line)
read_finish = False
elif line == 'end':
fix_time(cur_res, zero_time)
race.results.append(cur_res)
cur_res = ResultSportident()
elif line == 'start':
read_num = True
elif line == 'split_start':
read_spl = True
elif line == 'split_end':
read_spl = False
elif read_spl:
spl = Split()
spl.code = line.split(' ')[0]
spl.time = hhmmss_to_time(line.split(' ')[1])
cur_res.splits.append(spl)
45 changes: 45 additions & 0 deletions sportorg/modules/sportident/fix_time_sicard_5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from sportorg.common.otime import OTime

DEF_START_TIME = OTime(hour=8)


def if_si_card_5(card_number):
return card_number < 1000000


def _fix_time(time, zero_time):
"""
takes nearest to zero time, assuming, that original time is in only 12h format (SPORTident SICard 5)
exclude 00:00:00, meaning no time
00:15:18 (10:00:00) -> 12:15:18
00:15:18 (14:00:00) -> 00:15:18
11:15:18 (10:00:00) -> 11:15:18
11:15:18 (17:00:00) -> 23:15:18
22:15:18 (10:00:00) -> 10:15:18
22:15:18 (11:00:00) -> 22:15:18
"""
origin_time = time

if time == OTime(0):
return time

time_12h = OTime(hour=12)

if time >= time_12h:
time -= time_12h

if zero_time > time:
if zero_time - time < time_12h:
time += time_12h

return time


# fix time in result for SI Card 5 (12h format)
def fix_time(res, zero_time=DEF_START_TIME):
if if_si_card_5(res.card_number):
res.finish_time = _fix_time(res.finish_time, zero_time)
res.start_time = _fix_time(res.start_time, zero_time)

for i in range(len(res.splits)):
res.splits[i].time = _fix_time(res.splits[i].time, zero_time)