1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
| #!/usr/bin/python3
# -*- coding: utf-8 -*-
"""DemoPLOT v0.04
From iamvic on www.cyberforum.ru
Restrictions: Python 3.x, PyQt5, PostgreSQL, LibreOffice and no more.
"""
import copy
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import QMutex, QMutexLocker
from PyQt5.QtCore import QObject, QThread
from PyQt5.QtWidgets import QApplication
from PyQt5.QtSql import QSqlDatabase, QSqlQuery
from libre_store import print_libre_document
class Thread_postgresql(QThread):
connect_parm = [
'localhost', # хост
'5432', # порт
'postgres', # база данных
'postgres', # пользователь
'postgres', # пароль
'0x0' # hex(id(связаный_индикатор))
]
thread_mutex = QMutex()
connection_created = pyqtSignal()
duplicated_thread = pyqtSignal()
invalid_using = pyqtSignal()
db_or_query_invalid = pyqtSignal(str)
no_query_results = pyqtSignal()
lo_server_invalid = pyqtSignal()
drop_linked_pbar = pyqtSignal(str)
set_maximum_for_pbar = pyqtSignal(int)
set_current_for_pbar = pyqtSignal(int)
def set_connection_parm(self, parm):
'''передать потоку параметры соединения извне
(для наружного употребления)
'''
with QMutexLocker(self.thread_mutex):
rc = (type(parm) == list and
len(parm) == 6 and
all(type(x) == str for x in parm) and
parm[1].isdigit() and
int(parm[1]) in range(1,65535))
if rc:
self.connect_parm = copy.deepcopy(parm)
return rc
def get_connection_parm(self):
'''получить переданные снаружи параметры соединения
(для внутреннего употребления)
'''
with QMutexLocker(self.thread_mutex):
rc = copy.deepcopy(self.connect_parm)
return rc
def run(self):
'''именно этот код будет выполняться в дополнительном потоке
после вызова QThread.start()
'''
def inner_run(connect_name, current_parm):
'''поскольку удалить соединение можно только вне области
видимости соединения, то его создание и вся работа с БД
выполняется только в теле этой функции, а удаление соединения
производится снаружи после выхода из неё
'''
db = QSqlDatabase.addDatabase('QPSQL', connect_name)
db.setHostName(current_parm[0])
db.setPort(int(current_parm[1]))
db.setDatabaseName(current_parm[2])
db.setUserName(current_parm[3])
db.setPassword(current_parm[4])
if not db.open():
self.db_or_query_invalid.emit(
db.lastError().text())
else:
query_set = QSqlQuery(db)
rc = query_set.exec("SET application_name TO"
" '{!s} (thread {!s})';".format(
QApplication.applicationName(),
hex(id(self))))
if not rc:
self.db_or_query_invalid.emit(
query_set.lastError().text())
else:
self.connection_created.emit()
query_data = QSqlQuery(
'SELECT * FROM pg_stat_activity ORDER BY pid;', db=db)
if query_data.size() < 1:
self.no_query_results.emit()
elif not print_libre_document(query_data, self):
self.lo_server_invalid.emit()
db.close()
# закрыли БД
return
# и вышли из функции
proxy = QObject()
# именовать соединение будем по идентификатору доп.потока.
# чтобы убедиться в том, что для запуска использовался именно
# метод QThread.start(), создаём вспомогательный объект,
# живущий в дополнительном потоке (self живёт в основном).
# вспомогательный объект не имеет родителя, поскольку
# он - первый и, в данном случае, единственный на весь доп.поток.
# а родитель, как известно, должен жить в том же потоке,
# что и его отпрыски.
if id(self) == id(proxy.thread()):
# вспомогательный объект действительно живёт
# в дополнительном потоке (т.е. в self)
# => использовали именно QThread.start()
connect_name = 'thread_{!s}_main_{!s}_({!s})'.format(
# по идентификаторам дополнительного и основного потоков
# создали уникальное имя соединения вида
# thread_0x12345678_main_0x1234abcd_(True)
hex(id(self)),
# thread_0x12345678_...
hex(id(self.thread())),
# ...main_0x1234abcd_...
(id(QApplication.instance().thread()) ==
id(self.thread()) == id(proxy.thread().thread()) )
# ...(True)
# (True) подтверждает, что дополнительным потоком
# управляет именно основной (вот такая паранойя :)
)
if any(s.split('_')[1] == hex(id(proxy.thread()))
for s in QSqlDatabase.connectionNames()):
# параноидальная проверка с поиском дубликата
# идентификатора дополнительного потока
# в именах существующих соединений
# (совершенно лишняя, но пусть будет)
# дубликат найден => сообщаем и выходим
self.duplicated_thread.emit()
else:
# дубликат не найден => продолжаем работу
current_parm = self.get_connection_parm()
# запросили параметры соединения, переданные снаружи
inner_run(connect_name, current_parm)
# отработали с базой данных
QSqlDatabase.removeDatabase(connect_name)
# удалили соединение
else:
# вспомогательный объект живёт в потоке,
# отличном от дополнительного (т.е. не в self)
# => использовали QThread.run() вместо QThread.start()
self.invalid_using.emit()
# отправляем сигнал о неправильном использовании
self.drop_linked_pbar.emit(self.get_connection_parm()[5])
pass
# завершили выполнение потока
# если дополнительный поток был запущен методом QThread.start(),
# то это вызовет отправку сигнала QThread.finished() |