issue126-backup #198
@ -34,8 +34,6 @@ def do_autosave():
|
|||||||
backuping = BackupManager()
|
backuping = BackupManager()
|
||||||
|
|
||||||
files = backuping.search_unsaved()
|
files = backuping.search_unsaved()
|
||||||
print(files)
|
|
||||||
print(sys.executable, sys.argv)
|
|
||||||
|
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
bck_name = backuping.create_entry(pid)
|
bck_name = backuping.create_entry(pid)
|
||||||
@ -43,12 +41,17 @@ mplQt = NMRMainWindow(bck_file=bck_name)
|
|||||||
|
|
||||||
do_autosave()
|
do_autosave()
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
mplQt.management.load_files(f)
|
||||||
|
f.unlink()
|
||||||
|
|
||||||
timer = QtCore.QTimer()
|
timer = QtCore.QTimer()
|
||||||
timer.timeout.connect(do_autosave)
|
timer.timeout.connect(do_autosave)
|
||||||
timer.start(3 * 1000)
|
timer.start(3 * 60 * 1000)
|
||||||
|
|
||||||
app.aboutToQuit.connect(backuping.close)
|
app.aboutToQuit.connect(backuping.close)
|
||||||
|
|
||||||
mplQt.show()
|
mplQt.show()
|
||||||
|
|
||||||
|
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
|
@ -19,11 +19,11 @@ class BackupManager(QtCore.QObject):
|
|||||||
con = sqlite3.connect(DB_FILE)
|
con = sqlite3.connect(DB_FILE)
|
||||||
con.execute(
|
con.execute(
|
||||||
"CREATE TABLE IF NOT EXISTS sessions "
|
"CREATE TABLE IF NOT EXISTS sessions "
|
||||||
"(pid INTEGER NOT NULL, backup_file TEXT NOT NULL, last_save INTEGER);"
|
"(pid INTEGER NOT NULL, backup_file TEXT NOT NULL, last_save TEXT);"
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_entry(self, pid: int):
|
def create_entry(self, pid: int):
|
||||||
backup_path = config_paths() / f'{datetime.now().strftime("%Y-%m-%d_%H%M%S")}.nmr'
|
backup_path = config_paths() / f'autosave_{datetime.now().strftime("%Y-%m-%d_%H%M%S")}_{pid}.nmr'
|
||||||
|
|
||||||
con = sqlite3.connect(DB_FILE)
|
con = sqlite3.connect(DB_FILE)
|
||||||
con.execute('INSERT INTO sessions VALUES(?, ?, ?);',
|
con.execute('INSERT INTO sessions VALUES(?, ?, ?);',
|
||||||
@ -34,17 +34,78 @@ class BackupManager(QtCore.QObject):
|
|||||||
|
|
||||||
return backup_path
|
return backup_path
|
||||||
|
|
||||||
def remove_entry(self, pid: int = None):
|
def update_last_save(self):
|
||||||
|
con = sqlite3.connect(DB_FILE)
|
||||||
|
con.execute(
|
||||||
|
'UPDATE sessions SET last_save = ? WHERE pid = ?',
|
||||||
|
(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), self._pid)
|
||||||
|
)
|
||||||
|
con.commit()
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
def search_unsaved(self):
|
||||||
|
con = sqlite3.connect(DB_FILE)
|
||||||
|
cursor = con.cursor()
|
||||||
|
res = cursor.execute('SELECT sessions.* FROM sessions;')
|
||||||
|
con.commit()
|
||||||
|
data = res.fetchall()
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
missing_processes = []
|
||||||
|
|
||||||
|
for pid, fname, save_date in data:
|
||||||
|
try:
|
||||||
|
os.kill(pid, 0)
|
||||||
|
except ProcessLookupError:
|
||||||
|
if Path(fname).exists():
|
||||||
|
missing_processes.append((pid, fname, save_date))
|
||||||
|
else:
|
||||||
|
# remove entries without valid file
|
||||||
|
self.remove_row(pid)
|
||||||
|
|
||||||
|
if missing_processes:
|
||||||
|
msg = QLonelyBackupWindow()
|
||||||
|
msg.add_files(missing_processes)
|
||||||
|
msg.exec()
|
||||||
|
toberead = msg.recover
|
||||||
|
|
||||||
|
for pid in toberead[0]:
|
||||||
|
self.remove_file(pid)
|
||||||
|
|
||||||
|
for pid in toberead[1]:
|
||||||
|
self.remove_row(pid)
|
||||||
|
|
||||||
|
return list(toberead[1].values())
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def remove_row(self, pid):
|
||||||
|
con = sqlite3.connect(DB_FILE)
|
||||||
|
con.execute('DELETE FROM sessions WHERE pid = ?', (pid,))
|
||||||
|
con.commit()
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
def remove_file(self, pid: int = None):
|
||||||
if pid is None:
|
if pid is None:
|
||||||
pid = self._pid
|
pid = self._pid
|
||||||
|
|
||||||
con = sqlite3.connect(DB_FILE)
|
con = sqlite3.connect(DB_FILE)
|
||||||
cursor = con.cursor()
|
cursor = con.cursor()
|
||||||
|
|
||||||
res = cursor.execute('DELETE FROM sessions WHERE pid = ?;', (pid,))
|
# remove backup file
|
||||||
|
res = cursor.execute('SELECT sessions.backup_file FROM sessions WHERE pid = ?', (pid,))
|
||||||
con.commit()
|
con.commit()
|
||||||
|
|
||||||
self.delete_db_if_empty()
|
fname = Path(res.fetchone()[0])
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
fname.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
# because autosave backups in a *.nmr.0 file and then moves it to *.nmr we look also for this one.
|
||||||
|
fname.with_suffix('.nmr.0').unlink(missing_ok=True)
|
||||||
|
|
||||||
|
# after removal of file also remove entry
|
||||||
|
self.remove_row(pid)
|
||||||
|
|
||||||
def delete_db_if_empty(self):
|
def delete_db_if_empty(self):
|
||||||
con = sqlite3.connect(DB_FILE)
|
con = sqlite3.connect(DB_FILE)
|
||||||
@ -54,59 +115,93 @@ class BackupManager(QtCore.QObject):
|
|||||||
|
|
||||||
remaining_processes = res.fetchone()
|
remaining_processes = res.fetchone()
|
||||||
con.close()
|
con.close()
|
||||||
|
|
||||||
if remaining_processes is None:
|
if remaining_processes is None:
|
||||||
Path(DB_FILE).unlink()
|
Path(DB_FILE).unlink()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.remove_entry()
|
self.remove_file()
|
||||||
self.delete_db_if_empty()
|
self.delete_db_if_empty()
|
||||||
|
|
||||||
def search_unsaved(self):
|
|
||||||
con = sqlite3.connect(DB_FILE)
|
|
||||||
cursor = con.cursor()
|
|
||||||
res = cursor.execute('SELECT sessions.pid, sessions.backup_file FROM sessions;')
|
|
||||||
con.commit()
|
|
||||||
data = res.fetchall()
|
|
||||||
con.close()
|
|
||||||
|
|
||||||
missing_processes = []
|
class QLonelyBackupWindow(QtWidgets.QDialog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
self.recover = [[], {}] # list of pid to delete, dict of files to read {pid: file}
|
||||||
|
|
||||||
for pid, fname in data:
|
self.setWindowTitle('Adopt a file!')
|
||||||
try:
|
self.resize(720, 320)
|
||||||
os.kill(pid, 0)
|
|
||||||
except ProcessLookupError:
|
|
||||||
missing_processes.append((pid, fname))
|
|
||||||
|
|
||||||
if missing_processes:
|
layout = QtWidgets.QVBoxLayout(self)
|
||||||
msg = QtWidgets.QMessageBox()
|
self.label = QtWidgets.QLabel(self)
|
||||||
msg.exec()
|
self.label.setText('Abandoned backup file(s) looking for a loving home!\n'
|
||||||
|
'(Files will all be loaded to same instance)')
|
||||||
|
layout.addWidget(self.label)
|
||||||
|
|
||||||
def update_last_save(self):
|
self.table = QtWidgets.QTableWidget(self)
|
||||||
con = sqlite3.connect(DB_FILE)
|
self.table.setColumnCount(4)
|
||||||
con.execute(
|
self.table.setHorizontalHeaderLabels(['File name', 'Last saved', 'File size', 'Action'])
|
||||||
'UPDATE sessions SET last_save = ? WHERE pid = ?',
|
self.table.setGridStyle(QtCore.Qt.PenStyle.DashLine)
|
||||||
(datetime.now(), self._pid)
|
# self.table.horizontalHeader().setStretchLastSection(True)
|
||||||
)
|
self.table.verticalHeader().setVisible(False)
|
||||||
con.commit()
|
self.table.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
||||||
con.close()
|
layout.addWidget(self.table)
|
||||||
#
|
|
||||||
# def check_for_backup(self):
|
self.buttons = QtWidgets.QDialogButtonBox(self)
|
||||||
# backup_by_date = sorted(backups, key=lambda x: x[1])
|
self.buttons.setOrientation(QtCore.Qt.Orientation.Horizontal)
|
||||||
# msg = (QtWidgets.QMessageBox()
|
self.buttons.setStandardButtons(QtWidgets.QDialogButtonBox.Ok)
|
||||||
# , 'Backup found',)
|
layout.addWidget(self.buttons)
|
||||||
# f'{len(backups)} backup files in directory {backup_by_date[-1][0].parent} found.\n\n'
|
|
||||||
# f'Latest backup date: {backup_by_date[-1][1]}\n\n'
|
self.buttons.accepted.connect(self.accept)
|
||||||
# f'Press Ok to load, Cancel to delete backup, Close to do nothing.',
|
|
||||||
# QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Close
|
def add_files(self, entries):
|
||||||
# )
|
self.table.setRowCount(len(entries))
|
||||||
#
|
for i, (pid, path, date) in enumerate(entries):
|
||||||
# if msg == QtWidgets.QMessageBox.Ok:
|
path = Path(path)
|
||||||
# self.management.load_files([str(backup_by_date[-1][0])])
|
item1 = QtWidgets.QTableWidgetItem(path.name)
|
||||||
# backup_by_date[-1][0].unlink()
|
item1.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
|
||||||
# elif msg == QtWidgets.QMessageBox.Cancel:
|
item1.setData(QtCore.Qt.ItemDataRole.UserRole, pid)
|
||||||
# backup_by_date[-1][0].unlink()
|
item1.setData(QtCore.Qt.ItemDataRole.UserRole+1, path)
|
||||||
# else:
|
self.table.setItem(i, 0, item1)
|
||||||
# pass
|
|
||||||
#
|
item2 = QtWidgets.QTableWidgetItem(date)
|
||||||
|
item2.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
|
||||||
|
self.table.setItem(i, 1, item2)
|
||||||
|
|
||||||
|
size = path.stat().st_size
|
||||||
|
size_cnt = 0
|
||||||
|
while size > 1024:
|
||||||
|
# make file size human-readable
|
||||||
|
size /= 1024
|
||||||
|
size_cnt += 1
|
||||||
|
if size_cnt == 5:
|
||||||
|
break
|
||||||
|
|
||||||
|
byte = ['bytes', 'kB', 'MiB', 'GiB', 'TiB', 'PiB'][size_cnt]
|
||||||
|
|
||||||
|
item3 = QtWidgets.QTableWidgetItem(f'{size:.2f} {byte}')
|
||||||
|
item3.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
|
||||||
|
self.table.setItem(i, 2, item3)
|
||||||
|
|
||||||
|
cw = QtWidgets.QComboBox(self)
|
||||||
|
cw.addItems(['Load', 'Delete', 'Keep for later'])
|
||||||
|
self.table.setCellWidget(i, 3, cw)
|
||||||
|
|
||||||
|
self.table.resizeColumnsToContents()
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
for i in range(self.table.rowCount()):
|
||||||
|
decision = self.table.cellWidget(i, 3).currentIndex()
|
||||||
|
item = self.table.item(i, 0)
|
||||||
|
pid = item.data(QtCore.Qt.ItemDataRole.UserRole)
|
||||||
|
if decision == 0:
|
||||||
|
# load file
|
||||||
|
self.recover[1][pid] = item.data(QtCore.Qt.ItemDataRole.UserRole+1)
|
||||||
|
elif decision == 1:
|
||||||
|
# delete
|
||||||
|
self.recover[0].append(pid)
|
||||||
|
else:
|
||||||
|
# do nothing
|
||||||
|
pass
|
||||||
|
|
||||||
|
super().accept()
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .codeeditor import _make_textformats
|
from .codeeditor import _make_textformats
|
||||||
@ -6,12 +5,14 @@ from ..Qt import QtWidgets, QtCore, QtGui
|
|||||||
from nmreval.configs import config_paths
|
from nmreval.configs import config_paths
|
||||||
|
|
||||||
|
|
||||||
STYLES = {'INFO': _make_textformats('black'),
|
STYLES = {
|
||||||
|
'INFO': _make_textformats('black'),
|
||||||
'WARNING': _make_textformats('blue'),
|
'WARNING': _make_textformats('blue'),
|
||||||
'ERROR': _make_textformats('red', 'bold'),
|
'ERROR': _make_textformats('red', 'bold'),
|
||||||
'DEBUG': _make_textformats('black', 'italic'),
|
'DEBUG': _make_textformats('black', 'italic'),
|
||||||
'file': _make_textformats('red', 'italic'),
|
'file': _make_textformats('red', 'italic'),
|
||||||
'PyError': _make_textformats('red', 'bold-italic')}
|
'PyError': _make_textformats('red', 'bold-italic'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class LogHighlighter(QtGui.QSyntaxHighlighter):
|
class LogHighlighter(QtGui.QSyntaxHighlighter):
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime
|
|
||||||
import os
|
import os
|
||||||
import pathlib
|
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from numpy import geomspace, linspace
|
from numpy import geomspace, linspace
|
||||||
@ -34,7 +33,7 @@ from ..math.smooth import QSmooth
|
|||||||
from ..nmr.coupling_calc import QCoupCalcDialog
|
from ..nmr.coupling_calc import QCoupCalcDialog
|
||||||
from ..nmr.t1_from_tau import QRelaxCalc
|
from ..nmr.t1_from_tau import QRelaxCalc
|
||||||
from .._py.basewindow import Ui_BaseWindow
|
from .._py.basewindow import Ui_BaseWindow
|
||||||
from ..lib.utils import UpdateDialog, Updater
|
from ..lib.utils import UpdateDialog
|
||||||
|
|
||||||
|
|
||||||
class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
||||||
@ -1110,6 +1109,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
|
|||||||
# TODO better separate thread may it takes some time to save
|
# TODO better separate thread may it takes some time to save
|
||||||
self.status.setText('Autosave...')
|
self.status.setText('Autosave...')
|
||||||
success = NMRWriter(self.management.graphs, self.management.data).export(self.__backup_path.with_suffix('.nmr.0'))
|
success = NMRWriter(self.management.graphs, self.management.data).export(self.__backup_path.with_suffix('.nmr.0'))
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.__backup_path.with_suffix('.nmr.0').rename(self.__backup_path)
|
self.__backup_path.with_suffix('.nmr.0').rename(self.__backup_path)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user