Skip to content

Commit cd602bb

Browse files
Merge pull request #74 from DiamondLightSource/backup
Backup
2 parents c690b4b + 803ff54 commit cd602bb

File tree

16 files changed

+483
-189
lines changed

16 files changed

+483
-189
lines changed

build.bat

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ cd dls_barcode
66
REM use to debug the exe file
77
REM pyinstaller --onefile --icon=..\resources\icons\qr_code.ico --debug --clean main.py
88

9-
pyinstaller --onefile --windowed --icon=..\resources\icons\qr_code.ico --clean main.py
9+
pyinstaller --onefile --windowed --icon="C:\Users\rqq82173\PycharmProjects\PuckBarcodeReader\resources\icons\qr_code.ico" --clean "C:\Users\rqq82173\PycharmProjects\PuckBarcodeReader\dls_barcode\main.py"
1010

1111
move dist\main.exe ..\bin\barcode.exe
1212
rd /S /Q build
1313
rd /S /Q dist
1414
del main.spec
15-
16-
@echo start barcode.exe --config_file .\config.ini>"..\bin\launch_barcode.bat"

dls_barcode/config/barcode_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __init__(self, file, file_manager):
4444

4545
self.store_directory = add(DirectoryConfigItem, "Store Directory", default=default_store)
4646
self.store_capacity = add(IntConfigItem, "Results History Size", default=50)
47+
self.backup_time = add(IntConfigItem, "Backup in Weeks", default=3)
4748

4849
self.console_frame = add(BoolConfigItem, "Print Frame Summary", default=False)
4950
self.slot_images = add(BoolConfigItem, "Save Debug Images", default=False)

dls_barcode/config/barcode_config_dialog.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def _init_ui(self):
5555

5656
self.start_group("Store")
5757
self._add_control(StoreDirectoryConfigControl(cfg.store_directory))
58+
add(cfg.backup_time)
5859
add(cfg.store_capacity)
5960

6061
self.start_group("Debug")

dls_barcode/data_store/backup.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import time
2+
3+
from dls_barcode.data_store.comms_manager import CommsManager
4+
5+
6+
class Backup:
7+
8+
"""
9+
Backup class maintains the short time backaup of records which is kept in the same folder as the store files.
10+
"""
11+
12+
WEEK_IN_SECONDS = 604800
13+
14+
def __init__(self, comms_man, backup_time):
15+
self._comms = comms_man
16+
self._backup_time = backup_time.value()
17+
self._records = self._comms.load_records_from_file()
18+
19+
def _truncate_record_list(self):
20+
self._records = list(filter(lambda x: not self._is_old(x), self._records))
21+
22+
def backup_records(self, to_back):
23+
self._records.extend(to_back)
24+
self._truncate_record_list()
25+
self._comms.to_csv_file(self._records)
26+
27+
def _is_old(self, record):
28+
tm = time.time()
29+
record_time = record.timestamp
30+
delta = tm - record_time
31+
weeks = self._backup_time * self.WEEK_IN_SECONDS # weeks in seconds
32+
return delta > weeks
33+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import os
2+
3+
from dls_barcode.data_store.record import Record
4+
from dls_util.file import FileManager
5+
6+
7+
class CommsManager:
8+
""" Maintains communication between store/backup and the file manager.
9+
"""
10+
11+
def __init__(self, directory, file_name):
12+
self._file_manager = FileManager()
13+
self._directory = directory.value()
14+
self._file_name = file_name
15+
16+
def load_records_from_file(self):
17+
""" Clear the current record store and load a new set of records from the specified file. """
18+
file = os.path.join(self._directory, self._file_name + '.txt')
19+
records = []
20+
21+
if not self._file_manager.is_file(file):
22+
return records
23+
24+
lines = self._file_manager.read_lines(file)
25+
for line in lines:
26+
try:
27+
record = Record.from_string(line)
28+
records.append(record)
29+
except Exception:
30+
print("Failed to parse store Record: {}".format(line))
31+
32+
return records
33+
34+
def to_file(self, records):
35+
""" Save the contents of the store to the backing file
36+
"""
37+
file = os.path.join(self._directory, self._file_name + '.txt')
38+
record_lines = [rec.to_string() + "\n" for rec in records]
39+
self._file_manager.write_lines(file, record_lines)
40+
41+
def to_csv_file(self, records):
42+
""" Save the contents of the store to the backing csv file
43+
"""
44+
csv_file = os.path.join(self._directory, self._file_name + ".csv")
45+
record_lines = [rec.to_csv_string() + "\n" for rec in records]
46+
self._file_manager.write_lines(csv_file, record_lines)
47+
48+
def make_img_dir(self):
49+
img_dir = os.path.join(self._directory, "img_dir")
50+
if not self._file_manager.is_dir(img_dir):
51+
self._file_manager.make_dir(img_dir)
52+
return img_dir
53+
54+
def remove_img_file(self, record):
55+
if self._file_manager.is_file(record.image_path):
56+
self._file_manager.remove(record.image_path)

dls_barcode/data_store/record.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,4 @@ def _formatted_date(self):
162162
"""
163163
return datetime.datetime.fromtimestamp(self.timestamp).strftime(Record.DATE_FORMAT)
164164

165+

dls_barcode/data_store/store.py

Lines changed: 18 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,22 @@
55

66

77
class Store:
8+
89
""" Maintains a list of records of previous barcodes scans. Any changes (additions
910
or deletions) are automatically written to the backing file.
1011
"""
11-
def __init__(self, directory, store_capacity, file_manager):
12+
MIN_STORE_CAPACITY = 2
13+
14+
def __init__(self, comms_manager, backup, store_capacity):
1215
""" Initializes a new instance of Store.
1316
"""
1417
self._store_capacity = store_capacity
15-
self._directory = directory
16-
self._file_manager = file_manager
17-
self._file = os.path.join(directory, "store.txt")
18-
self._csv_file = os.path.join(directory, "store.csv")
19-
self._img_dir = os.path.join(directory, "img_dir")
20-
21-
if not self._file_manager.is_dir(self._img_dir):
22-
self._file_manager.make_dir(self._img_dir)
23-
24-
self.records = []
25-
self._load_records_from_file()
26-
self._sort_records()
27-
28-
def _load_records_from_file(self):
29-
""" Clear the current record store and load a new set of records from the specified file. """
30-
self.records = []
31-
32-
if not self._file_manager.is_file(self._file):
33-
return
34-
35-
lines = self._file_manager.read_lines(self._file)
36-
for line in lines:
37-
try:
38-
record = Record.from_string(line)
39-
self.records.append(record)
40-
except Exception:
41-
print("Failed to parse store Record: {}".format(line))
42-
18+
self._backup = backup
19+
self._comms_manager = comms_manager
20+
self._img_dir = self._comms_manager.make_img_dir()
21+
self.records = self._comms_manager.load_records_from_file()
4322
self._truncate_record_list()
23+
self._sort_records()
4424

4525
def size(self):
4626
""" Returns the number of records in the store
@@ -79,44 +59,35 @@ def delete_records(self, records_to_delete):
7959
"""
8060
for record in records_to_delete:
8161
self.records.remove(record)
82-
if self._file_manager.is_file(record.image_path):
83-
self._file_manager.remove(record.image_path)
62+
self._comms_manager.remove_img_file(record)
8463

8564
self._process_change()
8665

66+
def backup_records(self, records_to_backup):
67+
self._backup.backup_records(records_to_backup)
68+
8769
def _truncate_record_list(self):
88-
min_store_capacity = 2
89-
actual_store_capacity = max(self._store_capacity.value(), min_store_capacity)
70+
71+
actual_store_capacity = max(self._store_capacity.value(), self.MIN_STORE_CAPACITY)
9072

9173
if len(self.records) > actual_store_capacity:
9274
to_delete = self.records[actual_store_capacity:]
75+
self.backup_records(to_delete)
9376
self.delete_records(to_delete)
9477

9578
def _process_change(self):
9679
""" Sort the records and save to file.
9780
"""
9881
self._sort_records()
9982
self._truncate_record_list()
100-
self._to_file()
101-
self._to_csv_file()
83+
self._comms_manager.to_file(self.records)
84+
self._comms_manager.to_csv_file(self.records)
10285

10386
def _sort_records(self):
10487
""" Sort the records in descending date order (most recent first).
10588
"""
10689
self.records.sort(reverse=True, key=lambda record: record.timestamp)
10790

108-
def _to_file(self):
109-
""" Save the contents of the store to the backing file
110-
"""
111-
record_lines = [rec.to_string() + "\n" for rec in self.records]
112-
self._file_manager.write_lines(self._file, record_lines)
113-
114-
def _to_csv_file(self):
115-
""" Save the contents of the store to the backing csv file
116-
"""
117-
record_lines = [rec.to_csv_string() + "\n" for rec in self.records]
118-
self._file_manager.write_lines(self._csv_file, record_lines)
119-
12091
def _merge_holder_image_into_pins_image(self, holder_img, pins_img):
12192
factor = 0.22 * pins_img.width / holder_img.width
12293
small_holder_img = holder_img.rescale(factor)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from dls_barcode.data_store import Store
2+
from dls_barcode.data_store.backup import Backup
3+
from dls_barcode.data_store.comms_manager import CommsManager
4+
5+
6+
class StoreManager:
7+
8+
def __init__(self, directory, store_capacity, backup_time):
9+
self._store_capacity = store_capacity
10+
self._directory = directory
11+
self._backup_time = backup_time
12+
13+
def create_store(self):
14+
comms_manager = self._create_store_comms_manager()
15+
backup_comms_manager = self._create_backup_comms_manager()
16+
backup = self._create_backup(backup_comms_manager)
17+
return Store(comms_manager, backup, self._store_capacity)
18+
19+
def _create_store_comms_manager(self):
20+
return CommsManager(self._directory, "store")
21+
22+
def _create_backup(self, backup_comms_manager):
23+
return Backup(backup_comms_manager, self._backup_time)
24+
25+
def _create_backup_comms_manager(self):
26+
return CommsManager(self._directory, "backup")
27+
28+
29+
30+
31+

dls_barcode/gui/record_table.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QHBoxLayout, QTableWidget, QMessageBox
66

77
from dls_barcode.data_store import Store
8+
from dls_barcode.data_store.store_manager import StoreManager
89
from dls_util.file import FileManager
910

1011
# todo: allow delete key to be used for deletion
@@ -22,7 +23,7 @@ def __init__(self, barcode_table, image_frame, options):
2223
super(ScanRecordTable, self).__init__()
2324

2425
# Read the store from file
25-
self._store = Store(options.store_directory.value(), options.store_capacity, FileManager())
26+
self._store = StoreManager(options.store_directory, options.store_capacity, options.backup_time).create_store()
2627
self._options = options
2728

2829
self._barcodeTable = barcode_table
@@ -115,7 +116,7 @@ def _record_selected(self):
115116
self._imageFrame.display_puck_image(marked_image)
116117
except IndexError:
117118
self._barcodeTable.clear()
118-
self._imageFrame.clear_frame("Record table empty\nNothing to display")
119+
# self._imageFrame.clear_frame("Record table empty\nNothing to display")
119120

120121
def _delete_selected_records(self):
121122
""" Called when the 'Delete' button is pressed. Deletes all of the selected records
@@ -135,7 +136,9 @@ def _delete_selected_records(self):
135136
record = self._store.get_record(index)
136137
records_to_delete.append(record)
137138

139+
self._store.backup_records(records_to_delete)
138140
self._store.delete_records(records_to_delete)
141+
139142
self._load_store_records()
140143

141144
def is_latest_holder_barcode(self, holder_barcode):

dls_util/file/file_manager.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ def write_lines(self, file_path, lines):
1414
with open(file_path, 'w') as file:
1515
file.writelines(lines)
1616

17+
def append_lines(self, file_path, lines):
18+
"""Calls file.writelines, so doesn't append any new line characters"""
19+
with open(file_path, 'a') as file:
20+
file.writelines(lines)
21+
1722
def is_file(self, path):
1823
return os.path.isfile(path)
1924

0 commit comments

Comments
 (0)