Major refactoring & Minor bug fixes

This commit is contained in:
AccentuSoft
2022-09-27 00:14:51 +03:00
parent c2b3f6e710
commit f41de445bc
25 changed files with 977 additions and 1381 deletions

View File

@@ -29,42 +29,37 @@ class EntitiesDB:
"""
Load DiGraph from LinkScope Database file - msgpack dumped object.
"""
self.dbLock.acquire()
if self.database is not None:
self.save()
databaseFile = Path(self.mainWindow.SETTINGS.value("Project/FilesDir")).joinpath("LocalEntitiesDB.lsdb")
self.messageHandler.debug('Opening Database at: ' + str(databaseFile))
try:
dbFile = open(databaseFile, "rb")
self.database = self.mainWindow.RESOURCEHANDLER.reconstructGraphFullFromFile(load(dbFile))
dbFile.close()
self.messageHandler.info('Loaded Local Entities Database.')
except FileNotFoundError:
self.messageHandler.info('Creating new Local Entities Database.')
self.database = nx.DiGraph()
except Exception as exc:
self.messageHandler.error('Cannot parse Database: ' + str(exc) + "\nCreating new Local Entities Database.",
popUp=True)
self.database = nx.DiGraph()
finally:
self.dbLock.release()
with self.dbLock:
if self.database is not None:
self.save()
databaseFile = Path(self.mainWindow.SETTINGS.value("Project/FilesDir")).joinpath("LocalEntitiesDB.lsdb")
self.messageHandler.debug(f'Opening Database at: {str(databaseFile)}')
try:
with open(databaseFile, "rb") as dbFile:
self.database = self.mainWindow.RESOURCEHANDLER.reconstructGraphFullFromFile(load(dbFile))
self.messageHandler.info('Loaded Local Entities Database.')
except FileNotFoundError:
self.messageHandler.info('Creating new Local Entities Database.')
self.database = nx.DiGraph()
except Exception as exc:
self.messageHandler.error(f'Cannot parse Database: {str(exc)}\nCreating new Local Entities Database.',
popUp=True)
self.database = nx.DiGraph()
def resetTimeline(self) -> None:
"""
Reset the timeline on dockBarThree to reflect the current state of the database.
"""
self.dbLock.acquire()
if self.database is not None:
self.mainWindow.resetTimeline(self.database)
self.dbLock.release()
with self.dbLock:
if self.database is not None:
self.mainWindow.resetTimeline(self.database)
def updateTimeline(self, node, added: bool, updateGraph: bool = True) -> None:
"""
Update the timeline on dockBarThree to reflect the newest change of the database.
"""
self.dbLock.acquire()
self.mainWindow.updateTimeline(node, added, updateGraph)
self.dbLock.release()
with self.dbLock:
self.mainWindow.updateTimeline(node, added, updateGraph)
def save(self) -> None:
"""
@@ -75,41 +70,38 @@ class EntitiesDB:
databaseFile = Path(self.mainWindow.SETTINGS.value("Project/FilesDir")).joinpath("LocalEntitiesDB.lsdb")
if databaseFile is None:
raise ValueError('Database File is None, cannot save database.')
self.dbLock.acquire()
tmpSavePath = databaseFile.with_suffix(databaseFile.suffix + '.tmp')
dbFile = open(tmpSavePath, "wb")
dump(self.mainWindow.RESOURCEHANDLER.deconstructGraphForFileDump(self.database), dbFile)
dbFile.close()
move(tmpSavePath, databaseFile)
self.messageHandler.info('Database Saved.')
self.dbLock.release()
with self.dbLock:
tmpSavePath = databaseFile.with_suffix(f'{databaseFile.suffix}.tmp')
with open(tmpSavePath, "wb") as dbFile:
dump(self.mainWindow.RESOURCEHANDLER.deconstructGraphForFileDump(self.database), dbFile)
move(tmpSavePath, databaseFile)
self.messageHandler.info('Database Saved.')
def addEntity(self, entJson: dict, fromServer: bool = False, updateTimeline: bool = True) -> Union[dict, None]:
"""
Adds the entity represented by the json dictionary to the database.
"""
self.dbLock.acquire()
returnValue = None
with self.dbLock:
returnValue = None
# Check if we're overwriting an existing entity
exists = None
if entJson.get('uid') is not None:
exists = self.getEntityNoLock(entJson.get('uid'))
# Check if we're overwriting an existing entity
exists = None
if entJson.get('uid') is not None:
exists = self.getEntityNoLock(entJson.get('uid'))
entity = self.resourceHandler.getEntityJson(
entJson.get('Entity Type'),
entJson)
entity = self.resourceHandler.getEntityJson(
entJson.get('Entity Type'),
entJson)
if entity is None:
return returnValue
# Use uid as key. Code is holdover from the time when primary field == uid.
self.database.add_node(entity['uid'], **entity)
returnValue = entity
if exists:
# Update canvases if the node already exists.
self.mainWindow.updateEntityNodeLabelsOnCanvases(entity['uid'], entity[list(entity)[1]])
if entity is None:
self.dbLock.release()
return returnValue
# Use uid as key. Code is holdover from the time when primary field == uid.
self.database.add_node(entity['uid'], **entity)
returnValue = entity
if exists:
# Update canvases if the node already exists.
self.mainWindow.updateEntityNodeLabelsOnCanvases(entity['uid'], entity[list(entity)[1]])
self.dbLock.release()
if not fromServer:
self.mainWindow.sendLocalDatabaseUpdateToServer(entity, 1)
self.mainWindow.populateEntitiesWidget(returnValue, add=True)
@@ -123,32 +115,31 @@ class EntitiesDB:
return returnValue
def addEntities(self, entitiesJsonList: Union[list, set, tuple], fromServer: bool = False) -> list:
self.dbLock.acquire()
returnValue = []
with self.dbLock:
returnValue = []
for entJson in entitiesJsonList:
# Check if we're overwriting an existing entity
exists = None
if entJson.get('uid') is not None:
exists = self.getEntityNoLock(entJson.get('uid'))
for entJson in entitiesJsonList:
# Check if we're overwriting an existing entity
exists = None
if entJson.get('uid') is not None:
exists = self.getEntityNoLock(entJson.get('uid'))
entity = self.resourceHandler.getEntityJson(
entJson.get('Entity Type'),
entJson)
entity = self.resourceHandler.getEntityJson(
entJson.get('Entity Type'),
entJson)
if entity is None:
continue
# Use uid as key. Code is holdover from the time when primary field == uid.
self.database.add_node(entity['uid'], **entity)
returnValue.append(entity)
if exists:
# Update canvases if the node already exists.
self.mainWindow.updateEntityNodeLabelsOnCanvases(entity['uid'], entity[list(entity)[1]])
if not fromServer:
self.mainWindow.sendLocalDatabaseUpdateToServer(entity, 1)
self.mainWindow.populateEntitiesWidget(entity, add=True)
if entity is None:
continue
# Use uid as key. Code is holdover from the time when primary field == uid.
self.database.add_node(entity['uid'], **entity)
returnValue.append(entity)
if exists:
# Update canvases if the node already exists.
self.mainWindow.updateEntityNodeLabelsOnCanvases(entity['uid'], entity[list(entity)[1]])
if not fromServer:
self.mainWindow.sendLocalDatabaseUpdateToServer(entity, 1)
self.mainWindow.populateEntitiesWidget(entity, add=True)
self.dbLock.release()
self.resetTimeline()
return returnValue
@@ -162,42 +153,40 @@ class EntitiesDB:
:param fromServer:
:return:
"""
self.dbLock.acquire()
exists = self.isLinkNoLock(linkJson['uid'])
link = self.resourceHandler.getLinkJson(linkJson)
if link is None:
# This can technically be caused by a race condition if the user
# either tries really hard or gets really unlucky.
# Caused by deleting a node faster than the link can be created.
self.messageHandler.error("Attempted to add Link with "
"no uid to database.", popUp=True)
self.dbLock.release()
return None
else:
linkUID = link['uid']
if exists:
newRes = link.get('Resolution')
newNotes = link.get('Notes')
if newRes and newRes != exists['Resolution']:
if overwrite:
link['Resolution'] = newRes
else:
link['Resolution'] = exists['Resolution'] + ' | ' + newRes
if newNotes and newNotes != exists['Notes'] and newNotes != 'None':
if overwrite:
link['Notes'] = str(newNotes)
else:
link['Notes'] = exists['Notes'] + '\n\n' + str(newNotes)
exists.update(link)
link.update(exists)
# Update canvases if the link already exists.
# We can do this before updating the database here because the GUI will be updated only after this
# function returns. If we ever execute this function outside the main event loop, we will need
# to alter the execution flow.
self.mainWindow.updateLinkLabelsOnCanvases(linkUID[0] + linkUID[1], link['Resolution'])
self.database.add_edge(linkUID[0], linkUID[1], **link)
with self.dbLock:
exists = self.isLinkNoLock(linkJson['uid'])
link = self.resourceHandler.getLinkJson(linkJson)
if link is None:
# This can technically be caused by a race condition if the user
# either tries really hard or gets really unlucky.
# Caused by deleting a node faster than the link can be created.
self.messageHandler.error("Attempted to add Link with "
"no uid to database.", popUp=True)
return None
else:
linkUID = link['uid']
if exists:
newRes = link.get('Resolution')
newNotes = link.get('Notes')
if newRes and newRes != exists['Resolution']:
if overwrite:
link['Resolution'] = newRes
else:
link['Resolution'] = f"{exists['Resolution']} | {newRes}"
if newNotes and newNotes != exists['Notes'] and newNotes != 'None':
if overwrite:
link['Notes'] = str(newNotes)
else:
link['Notes'] = f"{exists['Notes']}\n\n{str(newNotes)}"
exists.update(link)
link.update(exists)
# Update canvases if the link already exists.
# We can do this before updating the database here because the GUI will be updated only after this
# function returns. If we ever execute this function outside the main event loop, we will need
# to alter the execution flow.
self.mainWindow.updateLinkLabelsOnCanvases(f"{linkUID[0]}{linkUID[1]}", link['Resolution'])
self.database.add_edge(linkUID[0], linkUID[1], **link)
self.dbLock.release()
if not fromServer:
if overwrite:
self.mainWindow.sendLocalDatabaseUpdateToServer(link, 3)
@@ -209,51 +198,41 @@ class EntitiesDB:
"""
Returns the attributes of the given entity uid as a dict.
"""
self.dbLock.acquire()
returnValue = None
try:
returnValue = self.database.nodes[uid]
except KeyError:
self.messageHandler.warning(
"Tried to get entity with nonexistent UID: " + uid)
finally:
self.dbLock.release()
return returnValue
with self.dbLock:
returnValue = None
try:
returnValue = self.database.nodes[uid]
except KeyError:
self.messageHandler.warning(f"Tried to get entity with nonexistent UID: {uid}")
finally:
return returnValue
def getAllEntities(self) -> Union[None, list]:
"""
Returns a list containing the Json representation of every entity in the database.
"""
self.dbLock.acquire()
returnValue = None
try:
returnValue = []
for node in self.database.nodes():
returnValue += [self.database.nodes[node]]
except KeyError:
self.messageHandler.error(
"Tried to get entity with nonexistent UID.")
finally:
self.dbLock.release()
return returnValue
with self.dbLock:
returnValue = None
try:
returnValue = [self.database.nodes[node] for node in self.database.nodes()]
except KeyError:
self.messageHandler.error("Tried to get entity with nonexistent UID.")
finally:
return returnValue
def getAllLinks(self) -> Union[None, list]:
"""
Returns a list containing the Json representation of every link in the database.
:return:
"""
self.dbLock.acquire()
returnValue = None
try:
returnValue = []
for edge in self.database.edges():
returnValue += [self.database.edges[edge]]
except KeyError:
self.messageHandler.error(
"Tried to get link with nonexistent UID.")
finally:
self.dbLock.release()
return returnValue
with self.dbLock:
returnValue = None
try:
returnValue = [self.database.edges[edge] for edge in self.database.edges()]
except KeyError:
self.messageHandler.error("Tried to get link with nonexistent UID.")
finally:
return returnValue
def getEntityNoLock(self, uid: str) -> Union[None, dict]:
"""
@@ -274,28 +253,27 @@ class EntitiesDB:
"""
Returns the attributes of the given link uid as a dict.
"""
self.dbLock.acquire()
returnValue = None
try:
returnValue = self.database.edges[uid]
except KeyError:
self.messageHandler.error(
"Tried to get link with nonexistent UID.")
finally:
self.dbLock.release()
return returnValue
with self.dbLock:
returnValue = None
try:
returnValue = self.database.edges[uid]
except KeyError:
self.messageHandler.error(
"Tried to get link with nonexistent UID.")
finally:
return returnValue
def removeEntity(self, uid: str, fromServer=False, updateTimeLine=True) -> None:
"""
Removes the entity with the given uid, if it exists.
"""
self.dbLock.acquire()
ent = None
if self.isNodeNoLock(uid):
ent = self.getEntityNoLock(uid)
self.mainWindow.populateEntitiesWidget(ent, add=False)
self.database.remove_node(uid)
self.dbLock.release()
with self.dbLock:
ent = None
if self.isNodeNoLock(uid):
ent = self.getEntityNoLock(uid)
self.mainWindow.populateEntitiesWidget(ent, add=False)
self.database.remove_node(uid)
if ent is not None:
self.mainWindow.handleGroupNodeUpdateAfterEntityDeletion(uid) # Blocking - locks the db.
if not fromServer:
@@ -308,10 +286,9 @@ class EntitiesDB:
Removes the link with the given uid (in string or tuple form),
if it exists.
"""
self.dbLock.acquire()
if self.isLinkNoLock(uid):
self.database.remove_edge(uid[0], uid[1])
self.dbLock.release()
with self.dbLock:
if self.isLinkNoLock(uid):
self.database.remove_edge(uid[0], uid[1])
if not fromServer:
self.mainWindow.sendLocalDatabaseUpdateToServer({"uid": uid}, 2)
@@ -319,14 +296,13 @@ class EntitiesDB:
"""
Checks if an entity with the specified primary attribute exists.
"""
self.dbLock.acquire()
result = False
for node in self.database.nodes():
details = self.database.nodes[node]
if details[list(details)[1]] == primaryAttr:
result = True
break
self.dbLock.release()
with self.dbLock:
result = False
for node in self.database.nodes():
details = self.database.nodes[node]
if details[list(details)[1]] == primaryAttr:
result = True
break
return result
def getEntityOfType(self, primaryAttr: str, entityType: str) -> Union[dict, None]:
@@ -337,13 +313,12 @@ class EntitiesDB:
primaryField = self.resourceHandler.getPrimaryFieldForEntityType(entityType)
if primaryField is None:
return result
self.dbLock.acquire()
for node in self.database.nodes():
details = self.database.nodes[node]
if details['Entity Type'] == entityType and details[primaryField] == primaryAttr:
result = dict(details)
break
self.dbLock.release()
with self.dbLock:
for node in self.database.nodes():
details = self.database.nodes[node]
if details['Entity Type'] == entityType and details[primaryField] == primaryAttr:
result = dict(details)
break
return result
def getLinkIfExists(self, uid) -> Union[None, dict]:
@@ -351,36 +326,29 @@ class EntitiesDB:
Returns the attributes of the given link uid as a dict.
Does not create an error if the link does not exist.
"""
self.dbLock.acquire()
returnValue = None
try:
returnValue = self.database.edges[uid]
except KeyError:
pass
finally:
self.dbLock.release()
return returnValue
with self.dbLock:
returnValue = None
try:
returnValue = self.database.edges[uid]
except KeyError:
pass
finally:
return returnValue
def getIncomingLinks(self, uid: str):
"""
Get all incoming edges for the given entity uid (primary attribute).
"""
self.dbLock.acquire()
returnValue = None
if self.isNodeNoLock(uid):
returnValue = self.database.in_edges(uid)
self.dbLock.release()
with self.dbLock:
returnValue = self.database.in_edges(uid) if self.isNodeNoLock(uid) else None
return returnValue
def getOutgoingLinks(self, uid: str):
"""
Get all outgoing edges for the given entity uid (primary attribute).
"""
self.dbLock.acquire()
returnValue = None
if self.isNodeNoLock(uid):
returnValue = self.database.out_edges(uid)
self.dbLock.release()
with self.dbLock:
returnValue = self.database.out_edges(uid) if self.isNodeNoLock(uid) else None
return returnValue
def isNode(self, uid: Union[str, list, tuple]) -> bool:
@@ -388,11 +356,8 @@ class EntitiesDB:
Returns True if the uid (primary attribute) given exists as
an entity, and False otherwise.
"""
self.dbLock.acquire()
returnValue = False
if isinstance(uid, str) and self.database.nodes.get(uid) is not None:
returnValue = True
self.dbLock.release()
with self.dbLock:
returnValue = isinstance(uid, str) and self.database.nodes.get(uid) is not None
return returnValue
def isNodeNoLock(self, uid: str) -> bool:
@@ -402,19 +367,14 @@ class EntitiesDB:
Used only in this class, as it does not lock.
"""
if self.database.nodes.get(uid) is not None:
return True
return False
return self.database.nodes.get(uid) is not None
def isLink(self, uid: Union[str, list, tuple]) -> bool:
"""
Returns True if the uid given exists as a link, and False otherwise.
"""
self.dbLock.acquire()
returnValue = False
if isinstance(uid, tuple) and self.database.edges.get(uid) is not None:
returnValue = True
self.dbLock.release()
with self.dbLock:
returnValue = isinstance(uid, tuple) and self.database.edges.get(uid) is not None
return returnValue
def isLinkNoLock(self, uid: tuple) -> Union[bool, dict]:
@@ -428,60 +388,43 @@ class EntitiesDB:
return False
def getEntityType(self, uid: str) -> Union[None, dict]:
self.dbLock.acquire()
returnValue = None
try:
returnValue = self.getEntityNoLock(uid)['Entity Type']
except KeyError:
pass
finally:
self.dbLock.release()
return returnValue
with self.dbLock:
returnValue = None
try:
returnValue = self.getEntityNoLock(uid)['Entity Type']
except KeyError:
pass
finally:
return returnValue
def mergeDatabases(self, newDB_nodes: dict, newDB_edges: dict, fromServer=True) -> None:
"""
Merges the existing database with the one provided.
Overwrites older attributes with newer ones.
Overwrites older attributes with newer ones based on date last edited.
"""
self.dbLock.acquire()
differenceGraph = nx.DiGraph()
# Note: If we ever receive a node without a 'Date Last Edited' field, ignore it.
differenceGraph.add_nodes_from([(x, newDB_nodes[x])
for x in newDB_nodes if (x not in self.database.nodes()) or
(
x in self.database.nodes() and
newDB_nodes[x].get('Date Last Edited') and
newDB_nodes[x]['Date Last Edited'] >
self.database.nodes[x]['Date Last Edited']
)
])
differenceGraph.add_edges_from([(x, y, newDB_edges[(x, y)])
for x, y in newDB_edges if ((x, y) not in self.database.edges()) or
(
(x, y) in self.database.edges() and
newDB_edges[(x, y)].get('Date Last Edited') and
newDB_edges[(x, y)]['Date Last Edited'] >
self.database.edges[(x, y)]['Date Last Edited']
)
])
if differenceGraph.number_of_nodes() > 0:
self.database = nx.compose(self.database, differenceGraph)
# Some nodes given by differenceGraph may be empty dicts, with an existing node's uid as the key.
for node in differenceGraph.nodes:
self.mainWindow.populateEntitiesWidget(self.database.nodes[node], add=True)
if not fromServer:
if self.mainWindow.FCOM.isConnected():
# diffNew = nx.DiGraph()
# diffNew.add_nodes_from([(x, self.database.nodes[x])
# for x in self.database.nodes() if x not in differenceGraph.nodes()
# ])
# diffNew.add_edges_from([(x, y, self.database.edges[(x, y)])
# for x, y in self.database.edges() if (x, y) not in differenceGraph.edges()
# ])
with self.dbLock:
differenceGraph = nx.DiGraph()
differenceGraph.add_nodes_from([(n, nDict)
for n, nDict in newDB_nodes.items() if (n not in self.database.nodes()) or
(
nDict.get('Date Last Edited', '') >
self.database.nodes[n].get('Date Last Edited', '')
)
])
differenceGraph.add_edges_from([(e[0], e[1], eDict)
for e, eDict in newDB_edges.items() if (e not in self.database.edges()) or
(
eDict.get('Date Last Edited', '') >
self.database.edges[e].get('Date Last Edited', '')
)
])
if differenceGraph.number_of_nodes():
self.database = nx.compose(self.database, differenceGraph)
# Some nodes given by differenceGraph may be empty dicts, with an existing node's uid as the key.
for node in differenceGraph.nodes:
self.mainWindow.populateEntitiesWidget(self.database.nodes[node], add=True)
if not fromServer and self.mainWindow.FCOM.isConnected():
# Assume we are already synced with server, so just send the difference.
self.mainWindow.FCOM.syncDatabase(self.mainWindow.SETTINGS.value("Project/Server/Project"),
differenceGraph)
self.dbLock.release()

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import contextlib
from ast import literal_eval
from typing import Union
from pathlib import Path
@@ -165,7 +166,7 @@ class CommunicationsHandler(QtCore.QObject):
except ConnectionRefusedError:
self.mainWindow.MESSAGEHANDLER.error("Did not connect: Server not running.", popUp=True, exc_info=False)
except Exception as exception:
self.mainWindow.MESSAGEHANDLER.error("Did not connect: " + str(exception))
self.mainWindow.MESSAGEHANDLER.error(f"Did not connect: {str(exception)}")
try:
if self.sock is not None:
@@ -184,9 +185,7 @@ class CommunicationsHandler(QtCore.QObject):
"""
Check if socket is in a working state.
"""
if self.sock is not None and self.sock.fileno() != -1:
return True
return False
return self.sock is not None and self.sock.fileno() != -1
def close(self) -> None:
global closeSoftwareLock
@@ -214,14 +213,13 @@ class CommunicationsHandler(QtCore.QObject):
def decryptTransmission(self, bytesObject) -> Union[bytes, None]:
decrypter = self.cipher.decryptor()
try:
message = decrypter.update(bytesObject) + decrypter.finalize()
return message
return decrypter.update(bytesObject) + decrypter.finalize()
except InvalidTag:
# If the ciphertext cannot be decrypted to a valid message, return None.
return None
def transmitMessage(self, messageJson: dict, showErrorOnBrokenPipe: bool = True) -> None:
self.mainWindow.MESSAGEHANDLER.debug('Sending Message: ' + str(messageJson))
self.mainWindow.MESSAGEHANDLER.debug(f'Sending Message: {messageJson}')
argEncoded = str(messageJson)
largeMessageUUID = str(uuid4())
try:
@@ -244,19 +242,15 @@ class CommunicationsHandler(QtCore.QObject):
"""
try:
self.transmitMessage({"Operation": "Close Socket", "Arguments": {}}, showErrorOnBrokenPipe=False)
except OSError:
# Typically this is due to bad file descriptor, i.e. server is closed.
pass
except AttributeError:
# This happens if no connection was established while the software was running
except (OSError, AttributeError):
# OSError thrown due to bad file descriptor, i.e. server is closed.
# AttributeError thrown if no connection was established while the software was running
pass
finally:
try:
with contextlib.suppress(OSError):
# OSError thrown if the socket is already closed.
if self.sock is not None:
self.sock.shutdown(socket.SHUT_RDWR)
except OSError:
# This would typically occur if the socket is already closed.
pass
try:
self.sock.close()
finally:
@@ -299,15 +293,12 @@ class CommunicationsHandler(QtCore.QObject):
self.inbox.put(preInbox.pop(messageID).get("message"))
except socket.error as socketError:
self.mainWindow.MESSAGEHANDLER.error('Socket Error: ' + str(socketError))
self.mainWindow.MESSAGEHANDLER.error(f'Socket Error: {str(socketError)}')
# If something happens, wait 2 seconds then try again.
closeSoftwareLock.acquire()
if not closeSoftware:
closeSoftwareLock.release()
time.sleep(2)
else:
closeSoftwareLock.release()
break
with closeSoftwareLock:
if closeSoftware:
break
time.sleep(2)
except ValueError:
# E.g.: Unpack failed: incomplete input
# In this case, we are being sent fragmented messages.
@@ -333,14 +324,12 @@ class CommunicationsHandler(QtCore.QObject):
continueTimestamp: int = 0) -> None:
collector_entities_to_send = []
for entity in collector_entities:
try:
with contextlib.suppress(KeyError):
dereferenced_entity = dict(entity)
collector_entities_to_send.append(dereferenced_entity)
# Icon is not necessary for any collector as of now: 2022/4/3.
# Cutting it out saves data.
dereferenced_entity['Icon'] = ''
except KeyError:
pass
message = {'Operation': 'Start Server Collector',
'Arguments': {
'collector_name': collector_name,
@@ -372,14 +361,12 @@ class CommunicationsHandler(QtCore.QObject):
resolution_uid: str) -> None:
resolution_entities_to_send = []
for entity in resolution_entities:
try:
with contextlib.suppress(KeyError):
dereferenced_entity = dict(entity)
resolution_entities_to_send.append(dereferenced_entity)
# Icon is not necessary for any resolution as of now: 2022/1/2.
# Cutting it out saves data.
dereferenced_entity['Icon'] = ''
except KeyError:
pass
message = {'Operation': 'Run Resolution',
'Arguments': {
'resolution_name': resolution_name,
@@ -512,17 +499,13 @@ class CommunicationsHandler(QtCore.QObject):
# Being verbose is better than prematurely optimizing for a few kbps of
# network traffic.
def receiveDatabaseUpdateEvent(self, entity_json: dict, add: int) -> None:
try:
with contextlib.suppress(KeyError):
entity_json['Icon'] = QtCore.QByteArray(b64decode(entity_json['Icon']))
except KeyError:
pass
self.receive_project_database_update.emit(entity_json, add)
def sendDatabaseUpdateEvent(self, project_name: str, entity_json: dict, add: int) -> None:
try:
with contextlib.suppress(KeyError):
entity_json['Icon'] = entity_json['Icon'].toBase64().data()
except KeyError:
pass
message = {"Operation": "Update Project Entities",
"Arguments": {
'project_name': project_name,
@@ -573,30 +556,28 @@ class CommunicationsHandler(QtCore.QObject):
"""
if not filePath.exists() or not filePath.is_file():
return
fileHandler = open(filePath, 'rb')
currThread = threading.currentThread()
while getattr(currThread, "continue_running", True):
filePart = fileHandler.read(512)
if not filePart:
messageJson = {"Operation": "File Upload Done",
with open(filePath, 'rb') as fileHandler:
currThread = threading.currentThread()
while getattr(currThread, "continue_running", True):
filePart = fileHandler.read(512)
if not filePart:
messageJson = {"Operation": "File Upload Done",
"Arguments": {
'project_name': project_name,
'file_name': file_name
}}
self.transmitMessage(messageJson)
break
messageJson = {"Operation": "File Upload",
"Arguments": {
'project_name': project_name,
'file_name': file_name
'file_name': file_name,
'file_contents': filePart
}}
self.transmitMessage(messageJson)
break
messageJson = {"Operation": "File Upload",
"Arguments": {
'project_name': project_name,
'file_name': file_name,
'file_contents': filePart
}}
self.transmitMessage(messageJson)
fileHandler.close()
def sendFileAbort(self, project_name: str, file_name: str) -> None:
try:
with contextlib.suppress(KeyError):
uploadToAbort = self.uploadingFiles.pop(file_name)
uploadToAbort.continue_running = False
messageJson = {"Operation": "File Upload Abort",
@@ -605,8 +586,6 @@ class CommunicationsHandler(QtCore.QObject):
'file_name': file_name
}}
self.transmitMessage(messageJson)
except KeyError:
pass
def scanInbox(self) -> None:
"""
@@ -619,15 +598,14 @@ class CommunicationsHandler(QtCore.QObject):
message = self.inbox.get(timeout=0.2)
except Empty:
with closeSoftwareLock:
if not closeSoftware:
time.sleep(0.2)
continue
else:
if closeSoftware:
return
time.sleep(0.2)
continue
if prevMesg == message:
# Same message, do not waste time handling.
continue
self.mainWindow.MESSAGEHANDLER.debug('Message to handle: ' + str(message))
self.mainWindow.MESSAGEHANDLER.debug(f'Message to handle: {str(message)}')
operation = message['Operation']
arguments = message['Arguments']
if operation == 'Get Server Resolutions':
@@ -667,8 +645,8 @@ class CommunicationsHandler(QtCore.QObject):
elif operation == "Start Collector":
self.receiveStartCollector(**arguments)
else:
self.mainWindow.MESSAGEHANDLER.warning('Unhandled message: ' + str(message) +
' On Operation: ' + str(operation))
self.mainWindow.MESSAGEHANDLER.warning(f'Unhandled message: {str(message)} On Operation: '
f'{str(operation)}')
prevMesg = message
def handleStatusMessage(self, operation: str, message: str, status_code: int) -> None:
@@ -686,61 +664,53 @@ class CommunicationsHandler(QtCore.QObject):
:param status_code:
:return:
"""
if status_code != 200:
if status_code == 404 and message == 'No project with the specified name exists!':
self.close_project_signal.emit()
else:
self.status_message_signal.emit('Operation ' + operation + ' failed with status code ' +
str(status_code) + ': ' + message, True)
else:
if operation == 'Create Project':
# No need to do anything here. Creating a new project also opens it.
pass
elif operation == 'Open Project':
projectName = message.split(': ', 1)[1]
self.open_project_signal.emit(projectName)
# Show the user that the server is doing something.
elif operation == 'Opening Project':
self.status_message_signal.emit(message, True)
elif operation == 'Close Project':
self.close_project_signal.emit()
elif operation == 'Create Canvas':
# No need to do anything here. Creating a new canvas also opens it.
pass
elif operation == 'Open Canvas':
canvas_name = message.split(': ', 1)[1]
self.open_project_canvas_signal.emit(canvas_name)
elif operation == 'Close Canvas':
canvas_name = message.split(': ', 1)[1]
self.close_project_canvas_signal.emit(canvas_name)
elif operation == 'Connect To Server':
server_name = message.split(': ', 1)[1]
self.connected_to_server_listener.emit(server_name)
elif operation == 'File Upload':
file_name = message.split(': ', 1)[1]
self.file_upload_finished_signal.emit(file_name)
elif operation == 'File Download Done':
file_name = message.split(': ', 1)[1]
self.receiveFileDoneListener(file_name)
elif operation == 'Abort Resolution':
if status_code == 200:
if operation == 'Abort Resolution':
# Remove resolution from resolutions list.
resolution_uid = message.split(': ', 1)[1]
self.remove_server_resolution_from_running_signal.emit(resolution_uid)
elif operation == 'Close Canvas':
canvas_name = message.split(': ', 1)[1]
self.close_project_canvas_signal.emit(canvas_name)
elif operation == 'Close Project':
self.close_project_signal.emit()
elif operation == 'Connect To Server':
server_name = message.split(': ', 1)[1]
self.connected_to_server_listener.emit(server_name)
elif operation in {'Create Project', 'Create Canvas', 'Stop Collector'}:
# No need to do anything for these.
pass
elif operation == 'Delete Project':
# Remove project from server projects list.
project_name = message.split(': ', 1)[1]
self.delete_server_project_signal.emit(project_name)
elif operation == 'File Download Done':
file_name = message.split(': ', 1)[1]
self.receiveFileDoneListener(file_name)
elif operation == 'File Upload Abort':
file_name = message.split(': ', 1)[1]
# Remove file from uploading files list.
self.file_upload_abort_signal.emit(file_name)
elif operation == 'Stop Collector':
# No need to do anything here - stopping collectors is only done by the client.
pass
elif operation == 'File Upload':
file_name = message.split(': ', 1)[1]
self.file_upload_finished_signal.emit(file_name)
elif operation == 'Open Canvas':
canvas_name = message.split(': ', 1)[1]
self.open_project_canvas_signal.emit(canvas_name)
elif operation == 'Open Project':
projectName = message.split(': ', 1)[1]
self.open_project_signal.emit(projectName)
elif operation == 'Opening Project':
self.status_message_signal.emit(message, True)
else:
self.mainWindow.MESSAGEHANDLER.warning('Unhandled status message: ' + message +
' Code: ' + str(status_code) +
' On Operation: ' + str(operation))
self.mainWindow.MESSAGEHANDLER.warning(f'Unhandled status message: {message} Code: {status_code} '
f'On Operation: {operation}')
elif status_code == 404 and message == 'No project with the specified name exists!':
self.close_project_signal.emit()
else:
self.status_message_signal.emit(f'Operation {operation} failed with status code {status_code}: {message}',
True)
def receiveFile(self, project_name: str, file_name: str, saveDir: Path) -> None:
# Do not download files already being downloaded.
@@ -761,18 +731,19 @@ class CommunicationsHandler(QtCore.QObject):
fileHandler.write(file_contents)
except Exception:
# In case something goes wrong in the middle of writing.
self.mainWindow.MESSAGEHANDLER.warning('Received data for file: ' + file_name +
' but no valid file handler exists for this file.')
self.mainWindow.MESSAGEHANDLER.warning(f'Received data for file: {file_name} but no valid file handler '
f'exists for this file.')
def receiveFileDoneListener(self, file_name: str) -> None:
fileHandler = self.downloadingFiles.pop(file_name)
if fileHandler is None:
self.mainWindow.MESSAGEHANDLER.warning('Received file: ' + file_name +
' but no file handler exists for this file.')
self.mainWindow.MESSAGEHANDLER.warning(f'Received file: {file_name} but no file handler exists for this '
f'file.')
return
fileHandler.close()
self.status_message_signal.emit('Finished downloading file from server: ' + file_name, True)
self.status_message_signal.emit(f'Finished downloading file from server: {file_name}', True)
def receiveFileAbort(self, project_name: str, file_name: str) -> None:
messageJson = {"Operation": "File Download Abort",
@@ -813,12 +784,10 @@ class CommunicationsHandler(QtCore.QObject):
:param file_name:
:return:
"""
try:
with contextlib.suppress(KeyError):
uploadToAbort = self.uploadingFiles.pop(file_name)
uploadToAbort.continue_running = False
self.file_upload_abort_signal.emit(file_name)
except KeyError:
pass
def sendFileAbortAll(self, project_name: str) -> None:
"""
@@ -828,7 +797,7 @@ class CommunicationsHandler(QtCore.QObject):
:return:
"""
for file_name in dict(self.uploadingFiles):
try:
with contextlib.suppress(KeyError):
uploadToAbort = self.uploadingFiles.pop(file_name)
uploadToAbort.continue_running = False
messageJson = {"Operation": "File Upload Abort",
@@ -837,8 +806,6 @@ class CommunicationsHandler(QtCore.QObject):
'file_name': file_name
}}
self.transmitMessage(messageJson)
except KeyError:
pass
def receiveFileAbortAll(self, project_name: str) -> None:
"""

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import contextlib
import re
import json
import sys
@@ -71,8 +72,8 @@ class WorkspaceWidget(QtWidgets.QWidget):
self.docPane.deleteLater()
def setDocTitleAndContents(self, title: Union[str, None] = None, content: Union[str, None] = None) -> None:
self.docPaneTitleText = title if title else "Document Name"
self.docPaneBodyText = content if content else "Document Summary"
self.docPaneTitleText = title or "Document Name"
self.docPaneBodyText = content or "Document Summary"
if self.docAndCanvasLayout.count() == 2:
self.docPane.documentTitleWidget.setText(title)
self.docPane.documentSummaryWidget.setPlainText(content)
@@ -89,38 +90,33 @@ class TabBar(QtWidgets.QTabBar):
self.setMovable(True)
def mouseDoubleClickEvent(self, event) -> None:
if event.button() == QtGui.Qt.LeftButton:
currIndex = self.currentIndex()
currName = self.tabText(currIndex)
currView = self.parent().getViewAtIndex(currIndex)
if event.button() != QtGui.Qt.LeftButton:
return
currIndex = self.currentIndex()
currName = self.tabText(currIndex)
currView = self.parent().getViewAtIndex(currIndex)
renameOrDeleteDialog = RenameOrDeleteTabDialog(currView.synced, currName)
dialogResult = renameOrDeleteDialog.exec()
renameOrDeleteDialog = RenameOrDeleteTabDialog(currView.synced, currName)
if not renameOrDeleteDialog.exec():
return
if dialogResult:
newTabName = renameOrDeleteDialog.newNameTextBox.text()
delete = renameOrDeleteDialog.deleteCheckbox.isChecked()
else:
return
newTabName = renameOrDeleteDialog.newNameTextBox.text()
if renameOrDeleteDialog.deleteCheckbox.isChecked():
sceneToClose = self.parent().canvasTabs[currName].scene()
groupNodes = [item for item in sceneToClose.items() if isinstance(item, Entity.GroupNode)]
for groupNode in groupNodes:
sceneToClose.removeNode(groupNode)
self.parent().closeTab(currName)
return
if delete:
sceneToClose = self.parent().canvasTabs[currName].scene()
groupNodes = [item for item in sceneToClose.items() if isinstance(item, Entity.GroupNode)]
for groupNode in groupNodes:
sceneToClose.removeNode(groupNode)
self.parent().closeTab(currName)
return
if newTabName == currName or newTabName == "":
return
if self.parent().isCanvasNameAvailable(newTabName):
self.parent().renameCanvas(currName, newTabName)
self.setTabText(currIndex, newTabName)
currView.name = newTabName
else:
self.messageHandler.info(
"Failed renaming tab: Canvas name already exists.",
popUp=True)
if newTabName in [currName, ""]:
return
if self.parent().isCanvasNameAvailable(newTabName):
self.parent().renameCanvas(currName, newTabName)
self.setTabText(currIndex, newTabName)
currView.name = newTabName
else:
self.messageHandler.info("Failed renaming tab: Canvas name already exists.", popUp=True)
class RenameOrDeleteTabDialog(QtWidgets.QDialog):
@@ -241,11 +237,8 @@ class TabbedPane(QtWidgets.QTabWidget):
def unmarkSyncedCanvasesByName(self, canvasToUnSync: str = None) -> None:
if canvasToUnSync is not None:
try:
with contextlib.suppress(ValueError):
self.syncedTabs.remove(canvasToUnSync)
except ValueError:
# In case the tab to unSync isn't actually synced.
pass
syncIndex = self.getTabIndexByName(canvasToUnSync)
syncView = self.getViewAtIndex(syncIndex)
self.setTabIcon(syncIndex, QtGui.QIcon())
@@ -262,9 +255,7 @@ class TabbedPane(QtWidgets.QTabWidget):
"""
Checks if the specified canvas name is available.
"""
if canvasName in self.canvasTabs:
return False
return True
return canvasName not in self.canvasTabs
def renameCanvas(self, currName: str, newName: str) -> None:
self.canvasTabs[newName] = self.canvasTabs.pop(currName)
@@ -294,10 +285,8 @@ class TabbedPane(QtWidgets.QTabWidget):
self.removeTab(tabIndex)
break
self.canvasTabs.pop(tabName)
try:
with contextlib.suppress(KeyError):
self.tabsNotesDict.pop(tabName)
except KeyError:
pass
def hideTab(self, index) -> None:
self.removeTab(index)
@@ -311,18 +300,11 @@ class TabbedPane(QtWidgets.QTabWidget):
self.setTabIcon(syncIndex, QtGui.QIcon(self.resourceHandler.getIcon("uploading")))
def getTabIndexByName(self, tabName):
count = 0
for tab in self.canvasTabs:
if tab == tabName:
return count
count += 1
return None
return next((count for count, tab in enumerate(self.canvasTabs) if tab == tabName), None)
def getSceneByName(self, tabName):
tabView = self.canvasTabs.get(tabName)
if tabView is not None:
return tabView.scene()
return None
return tabView.scene() if tabView is not None else None
def getNameOfScene(self, scene) -> str:
for tab in self.canvasTabs:
@@ -367,17 +349,16 @@ class TabbedPane(QtWidgets.QTabWidget):
# Having a very granular progress bar results in a massive slowdown (i.e. resolutions take 5x< the time).
steps = 3
progress = QtWidgets.QProgressDialog('Resolving new nodes for resolution: ' + resolution_name +
', please wait...', 'Abort Resolving Nodes', 0, steps, self)
progress = QtWidgets.QProgressDialog(f'Resolving new nodes for resolution: {resolution_name}, please wait...',
'Abort Resolving Nodes', 0, steps, self)
progress.setWindowModality(QtCore.Qt.WindowModal)
progress.setMinimumDuration(1500)
# Get all the entities, then split it into several lists, to make searching & iterating through them faster.
allEntities = [(entity['uid'], (entity[list(entity)[1]], entity['Entity Type']))
for entity in self.entityDB.getAllEntities()]
# In case we have no entities in the database when the resolution finishes, i.e. the user deletes the origin
# node for the resolution, or runs something that creates nodes from nothing.
if allEntities:
if allEntities := [(entity['uid'], (entity[list(entity)[1]], entity['Entity Type']))
for entity in self.entityDB.getAllEntities()]:
allEntityUIDs, allEntityPrimaryFieldsAndTypes = map(list, zip(*allEntities))
else:
allEntityUIDs = []
@@ -412,7 +393,7 @@ class TabbedPane(QtWidgets.QTabWidget):
try:
notesField = newNodeJSON.pop('Notes')
if existingEntityJSON.get('Notes'):
existingEntityJSON['Notes'] += '\n' + notesField
existingEntityJSON['Notes'] += f"\n{notesField}"
else:
existingEntityJSON['Notes'] = str(notesField)
except KeyError:
@@ -441,9 +422,8 @@ class TabbedPane(QtWidgets.QTabWidget):
nodesCreatedCount += 1
progress.setValue(1)
for resultListIndex in range(len(newNodeUIDs)):
outputEntityUID = newNodeUIDs[resultListIndex]
parentsDict = resolution_result[resultListIndex][1]
for outputEntityUID, resolutionResultElement in zip(newNodeUIDs, resolution_result):
parentsDict = resolutionResultElement[1]
for parentID in parentsDict:
parentUID = parentID
if isinstance(parentUID, int):
@@ -458,7 +438,7 @@ class TabbedPane(QtWidgets.QTabWidget):
if newLinkUID in allLinks:
linkJson = self.entityDB.getLinkIfExists(newLinkUID)
if resolutionName not in linkJson['Notes']:
linkJson['Notes'] += '\nConnection also produced by Resolution: ' + resolutionName
linkJson['Notes'] += f"\nConnection also produced by Resolution: {resolutionName}"
self.entityDB.addLink(linkJson, fromServer=True)
linksUpdatedCount += 1
else:
@@ -477,10 +457,11 @@ class TabbedPane(QtWidgets.QTabWidget):
progress.setValue(3)
self.entityDB.resetTimeline()
self.mainWindow.saveProject()
self.mainWindow.MESSAGEHANDLER.info('Resolution ' + resolution_name + ' completed successfully: ' +
str(nodesCreatedCount) + ' new nodes created, ' + str(nodesUpdatedCount) +
' existing nodes updated. New links created: ' + str(linksCreatedCount) +
', links updated: ' + str(linksUpdatedCount))
self.mainWindow.MESSAGEHANDLER.info(f'Resolution {resolution_name} completed successfully: '
f'{str(nodesCreatedCount)} new nodes created, {str(nodesUpdatedCount)} '
f'existing nodes updated. New links created: {str(linksCreatedCount)}, '
f'links updated: {str(linksUpdatedCount)}')
return newNodeUIDs
def linkAddHelper(self, links) -> None:
@@ -536,10 +517,10 @@ class TabbedPane(QtWidgets.QTabWidget):
else:
scene.addLinkProgrammatic((newLink[0], newLink[1]), newLink[2])
elif parentUID in scene.nodesDict and uid in scene.nodesDict:
elif parentUID in scene.nodesDict:
# Need to send this to server, since it won't be drawn otherwise.
scene.addLinkDragDrop(scene.nodesDict[parentUID], scene.nodesDict[uid], newLink[2])
elif parentUID not in scene.nodesDict and uid in scene.nodesDict:
elif uid in scene.nodesDict:
if parentUID not in scene.sceneGraph.nodes:
nodeJSON = self.entityDB.getEntity(parentUID)
@@ -614,16 +595,14 @@ class TabbedPane(QtWidgets.QTabWidget):
canvasDBPath = self.getCanvasDBPath()
tabsNotesPath = self.getTabsNotesPath()
canvasDBPathTmp = canvasDBPath.with_suffix(canvasDBPath.suffix + '.tmp')
canvasNotesPathTmp = tabsNotesPath.with_suffix(tabsNotesPath.suffix + '.tmp')
canvasDBPathTmp = canvasDBPath.with_suffix(f'{canvasDBPath.suffix}.tmp')
canvasNotesPathTmp = tabsNotesPath.with_suffix(f'{tabsNotesPath.suffix}.tmp')
# Save canvases
with open(canvasDBPathTmp, "wb") as canvasDBFile:
saveJson = {}
for canvasName in self.canvasTabs:
saveJson[canvasName] = [
self.resourceHandler.deconstructGraphForFileDump(self.canvasTabs[canvasName].scene().sceneGraph),
self.canvasTabs[canvasName].scene().scenePos]
saveJson = {canvasName: [self.resourceHandler.deconstructGraphForFileDump(self.canvasTabs[canvasName].scene().sceneGraph),
self.canvasTabs[canvasName].scene().scenePos] for canvasName in self.canvasTabs}
dump(saveJson, canvasDBFile)
move(canvasDBPathTmp, canvasDBPath)
@@ -665,8 +644,8 @@ class TabbedPane(QtWidgets.QTabWidget):
self.resourceHandler.reconstructGraphFullFromFile(savedJson[canvasName][0]),
savedJson[canvasName][1])
except Exception as exc:
self.messageHandler.error("Exception occurred when opening tabs: " + str(exc) +
"\nSkipping opening tabs.", popUp=True)
self.messageHandler.error(f"Exception occurred when opening tabs: {str(exc)}\nSkipping opening tabs.",
popUp=True)
def currentTabChangedListener(self, newIndex: int) -> None:
if self.previousTab in self.canvasTabs:
@@ -882,8 +861,7 @@ class CanvasView(QtWidgets.QGraphicsView):
self.centerOn(node)
def dragMoveEvent(self, event) -> None:
itemsMoved = self.scene().selectedItems()
if len(itemsMoved) != 0:
if itemsMoved := self.scene().selectedItems():
self.ensureVisible(itemsMoved[0], 50, 50)
def dragEnterEvent(self, event) -> None:
@@ -934,26 +912,11 @@ class CanvasView(QtWidgets.QGraphicsView):
entityJson = self.tabbedPane.entityDB.addEntity(nodeJson)
entityUID = entityJson['uid']
if entityUID not in self.scene().sceneGraph.nodes():
if entityJson['Entity Type'] == 'EntityGroup':
newGroup = self.tabbedPane.mainWindow.copyGroupEntity(entityUID, self.scene())
if newGroup is not None:
newNode = self.scene().addNodeProgrammatic(newGroup['uid'], newGroup['Child UIDs'])
newNode.setPos(pos.x() - 20, pos.y() - 20)
else:
self.tabbedPane.messageHandler.warning("Cannot add selected Group Node to scene: Scene "
"already contains all nodes that the Group Node "
"currently contains.", popUp=True)
else:
self.scene().addNodeDragDrop(
entityUID,
pos.x() - 20,
pos.y() - 20)
else:
if entityUID in self.scene().sceneGraph.nodes():
wasGrouped = False
for groupNode in [node for node in self.items() if isinstance(node, Entity.GroupNode)]:
wasGrouped = \
groupNode.removeSpecificItemFromGroupIfExists(entityUID)
groupNode.removeSpecificItemFromGroupIfExists(entityUID)
if wasGrouped:
self.removeGroupNodeLinksForUID(groupNode.uid, entityUID)
groupNodeJson = self.tabbedPane.entityDB.getEntity(groupNode.uid)
@@ -976,6 +939,20 @@ class CanvasView(QtWidgets.QGraphicsView):
pos.y() - 20
)
break
elif entityJson['Entity Type'] == 'EntityGroup':
newGroup = self.tabbedPane.mainWindow.copyGroupEntity(entityUID, self.scene())
if newGroup is not None:
newNode = self.scene().addNodeProgrammatic(newGroup['uid'], newGroup['Child UIDs'])
newNode.setPos(pos.x() - 20, pos.y() - 20)
else:
self.tabbedPane.messageHandler.warning("Cannot add selected Group Node to scene: Scene "
"already contains all nodes that the Group Node "
"currently contains.", popUp=True)
else:
self.scene().addNodeDragDrop(
entityUID,
pos.x() - 20,
pos.y() - 20)
if len(nodeJson) > 1:
primaryField = self.tabbedPane.resourceHandler.getPrimaryFieldForEntityType(
entityJson['Entity Type'])
@@ -1001,9 +978,7 @@ class CanvasView(QtWidgets.QGraphicsView):
# Entities are unique - only one instance exists in each canvas.
# This should search all group nodes, regardless of whether they are nested or not.
for groupNode in [node for node in self.items() if isinstance(node, Entity.GroupNode)]:
wasGrouped = \
groupNode.removeSpecificItemFromGroupIfExists(entityUID)
if wasGrouped:
if groupNode.removeSpecificItemFromGroupIfExists(entityUID):
self.removeGroupNodeLinksForUID(groupNode.uid, entityUID)
# Should not be needed.
@@ -1014,11 +989,9 @@ class CanvasView(QtWidgets.QGraphicsView):
self.tabbedPane.entityDB.addEntity(groupNodeJson)
break
try:
with contextlib.suppress(nx.exception.NetworkXError):
# Exception thrown if node was already removed.
self.scene().sceneGraph.remove_node(entityUID)
except nx.exception.NetworkXError:
# Node is already removed
pass
def removeGroupNodeLinksForUID(self, groupUID, nodeUID) -> None:
self.scene().removeGroupNodeLinksForUID(groupUID, nodeUID)
@@ -1035,19 +1008,20 @@ class CanvasView(QtWidgets.QGraphicsView):
self.adjustSceneRect()
def wheelEvent(self, event) -> None:
if len(self.scene().items()) > 0:
if event.angleDelta().y() > 0:
if self.zoom == 0:
return
factor = 1.25
self.zoom += 1
else:
# Prevent user from zooming out indefinitely.
if self.zoom < -11:
return
factor = 0.8
self.zoom -= 1
self.scale(factor, factor)
if len(self.scene().items()) == 0:
return
if event.angleDelta().y() > 0:
if self.zoom == 0:
return
factor = 1.25
self.zoom += 1
else:
# Prevent user from zooming out indefinitely.
if self.zoom < -11:
return
factor = 0.8
self.zoom -= 1
self.scale(factor, factor)
def mousePressEvent(self, event) -> None:
if event.button() == QtCore.Qt.MouseButton.RightButton and \
@@ -1058,7 +1032,7 @@ class CanvasView(QtWidgets.QGraphicsView):
items = self.scene().selectedItems()
groupItems = [groupItem for groupItem in items if isinstance(groupItem, Entity.GroupNode)]
linkItems = [linkItem for linkItem in items if isinstance(linkItem, Entity.BaseConnector)]
if len(groupItems) > 0:
if groupItems:
self.actionUngroup.setDisabled(False)
self.actionUngroup.setEnabled(True)
else:
@@ -1070,7 +1044,7 @@ class CanvasView(QtWidgets.QGraphicsView):
else:
self.actionGroup.setDisabled(True)
self.actionGroup.setEnabled(False)
if len(linkItems) > 0:
if linkItems:
self.actionLinkDelete.setDisabled(False)
self.actionLinkDelete.setEnabled(True)
else:
@@ -1112,8 +1086,7 @@ class CanvasView(QtWidgets.QGraphicsView):
prompt = SendToOtherTabCanvasSelector(otherTabs)
if prompt.exec():
otherCanvasName = prompt.canvasNameSelector.currentText()
if otherCanvasName:
if otherCanvasName := prompt.canvasNameSelector.currentText():
otherCanvas = self.tabbedPane.canvasTabs[otherCanvasName].scene()
for entityToSend in entitiesToSend:
if otherCanvas.sceneGraph.nodes.get(entityToSend) is None:
@@ -1152,8 +1125,7 @@ class CanvasView(QtWidgets.QGraphicsView):
if link[1] not in self.scene().sceneGraph.nodes])
for groupEntity in [linkedGroupEntity for linkedGroupEntity in linkedEntities
if linkedGroupEntity.endswith('@')]:
newEntityJSON = self.tabbedPane.mainWindow.copyGroupEntity(groupEntity, self.scene())
if newEntityJSON:
if newEntityJSON := self.tabbedPane.mainWindow.copyGroupEntity(groupEntity, self.scene()):
newNode = self.scene().addNodeProgrammatic(newEntityJSON['uid'], newEntityJSON['Child UIDs'])
linkedEntities.remove(groupEntity)
newNode.setSelected(True)
@@ -1168,7 +1140,7 @@ class CanvasView(QtWidgets.QGraphicsView):
# Ref: https://qtcentre.org/threads/10975-Help-Export-QGraphicsView-to-Image-File
# Rendering best optimized to rgb32 and argb32_premultiplied.
# Ref: https://doc.qt.io/qtforpython/PySide6/QtGui/QImage.html?highlight=qimage#image-formats
selectedItems = [item for item in self.scene().selectedItems()]
selectedItems = list(self.scene().selectedItems())
for item in selectedItems:
item.setSelected(False)
if justViewport:
@@ -1275,14 +1247,13 @@ class CanvasScene(QtWidgets.QGraphicsScene):
self.nodesDict[item.uid] = item
self.addItem(item)
item.setPos(QtCore.QPointF(x, y))
self.parent().mainWindow.MESSAGEHANDLER.info('Added node: ' + str(item.uid) + ' | ' +
item.labelItem.toPlainText())
self.parent().mainWindow.MESSAGEHANDLER.info(f'Added node: {str(item.uid)} | {item.labelItem.toPlainText()}')
def addLinkToScene(self, link: Entity.BaseConnector) -> None:
self.linksDict[link.startItem().uid + link.endItem().uid] = link
self.addItem(link)
self.parent().mainWindow.MESSAGEHANDLER.info('Added link: (' + link.startItem().uid + ", " +
link.endItem().uid + ') | ' + link.labelItem.text())
self.parent().mainWindow.MESSAGEHANDLER.info(f'Added link: ({link.startItem().uid}, {link.endItem().uid}) | '
f'{link.labelItem.text()}')
def appendSelectedItemsToGroupToggle(self) -> None:
if self.linking:
@@ -1311,22 +1282,22 @@ class CanvasScene(QtWidgets.QGraphicsScene):
self.parent().mainWindow.setStatus('Too many items selected to link, please only choose a total of 2.')
elif len(self.itemsToLink) == 2:
potentialUID = (self.itemsToLink[0].uid, self.itemsToLink[1].uid)
if not self.parent().entityDB.isLink(potentialUID):
if self.parent().entityDB.addLink({"uid": potentialUID}) is not None:
self.addLinkDragDrop(self.itemsToLink[0],
self.itemsToLink[1])
self.editLinkProperties(potentialUID)
if not self.parent().entityDB.isLink(potentialUID) and \
self.parent().entityDB.addLink({"uid": potentialUID}) is not None:
self.addLinkDragDrop(self.itemsToLink[0], self.itemsToLink[1])
self.editLinkProperties(potentialUID)
self.parent().mainWindow.toggleLinkingMode()
elif self.appendingToGroup:
selectedGroupItems = [item for item in self.selectedItems() if isinstance(item, Entity.GroupNode)]
if len(selectedGroupItems) < 1:
if not selectedGroupItems:
pass
elif len(selectedGroupItems) > 1:
self.parent().mainWindow.MESSAGEHANDLER.warning('Too many group items selected, '
'please only choose one.', popUp=True)
self.parent().mainWindow.setStatus('Too many group items selected, please only choose one.')
else:
# Only continue if just 1 group item is selected.
groupEntityMaybe = selectedGroupItems[0]
if not isinstance(groupEntityMaybe, Entity.GroupNode):
self.parent().mainWindow.setStatus('Selected entity is not a group entity. Aborting adding new '
@@ -1369,7 +1340,8 @@ class CanvasScene(QtWidgets.QGraphicsScene):
# Add UIDs to list. If there are both links and nodes, remove
# links from list and only keep nodes.
selectedUIDs = [item.uid for item in self.selectedItems()
if isinstance(item, Entity.BaseNode) or isinstance(item, Entity.BaseConnector)]
if isinstance(item, (Entity.BaseNode, Entity.BaseConnector))]
self.parent().mainWindow.populateDetailsWidget(selectedUIDs)
def drawGraphOnCanvasFromOpen(self, canvasName: str) -> None:
@@ -1387,8 +1359,9 @@ class CanvasScene(QtWidgets.QGraphicsScene):
if steps == 0:
return
steps += 1
progress = QtWidgets.QProgressDialog('Opening Canvas: ' + canvasName + ', please wait...',
'', 0, steps, self.parent())
progress = QtWidgets.QProgressDialog(f'Opening Canvas: {canvasName}, please wait...', '', 0, steps,
self.parent())
# Remove Cancel button from progress bar (user should not be able to stop canvas from loading).
progress.setMinimumDuration(1500)
progress.setCancelButton(None)
@@ -1440,7 +1413,7 @@ class CanvasScene(QtWidgets.QGraphicsScene):
progress.setValue(steps)
self.adjustSceneRect()
self.parent().mainWindow.MESSAGEHANDLER.info('Loaded canvas: ' + canvasName)
self.parent().mainWindow.MESSAGEHANDLER.info(f'Loaded canvas: {canvasName}')
def updatePositionInDB(self, uid, x, y) -> None:
"""
@@ -1468,8 +1441,7 @@ class CanvasScene(QtWidgets.QGraphicsScene):
# canvases will have different nodes on them.
# The 'addNode' functions assume that they've been passed whatever entity group is the correct one, i.e.
# either a new one being created or an old one that was copied.
groupItems = [uid for uid in entity['Child UIDs'] if uid not in self.sceneGraph.nodes]
if len(groupItems) > 0:
if groupItems := [uid for uid in entity['Child UIDs'] if uid not in self.sceneGraph.nodes]:
newNode = Entity.GroupNode(picture, uid, entity['Group Name'], self.entityTextFont,
self.entityTextBrush)
self.addNodeToScene(newNode, x, y)
@@ -1518,19 +1490,17 @@ class CanvasScene(QtWidgets.QGraphicsScene):
newNode = Entity.BaseNode(picture, uid, nodePrimaryAttribute, self.entityTextFont,
self.entityTextBrush)
self.addNodeToScene(newNode)
else:
groupItems = [uid for uid in groupItems if uid not in self.sceneGraph.nodes]
if len(groupItems) > 0:
newNode = Entity.GroupNode(picture, uid, entity['Group Name'], self.entityTextFont,
self.entityTextBrush)
self.addNodeToScene(newNode)
elif groupItems := [uid for uid in groupItems if uid not in self.sceneGraph.nodes]:
newNode = Entity.GroupNode(picture, uid, entity['Group Name'], self.entityTextFont,
self.entityTextBrush)
self.addNodeToScene(newNode)
newGroupList = newNode.listWidget
newGroupListGraphic = self.addWidget(newGroupList)
newGroupListGraphic.hide()
newNode.formGroup(groupItems, newGroupListGraphic)
for item in groupItems:
self.sceneGraph.add_node(item, groupID=newNode.uid)
newGroupList = newNode.listWidget
newGroupListGraphic = self.addWidget(newGroupList)
newGroupListGraphic.hide()
newNode.formGroup(groupItems, newGroupListGraphic)
for item in groupItems:
self.sceneGraph.add_node(item, groupID=newNode.uid)
if newNode is not None:
if uid in self.sceneGraph.nodes:
del self.sceneGraph.nodes[uid]['groupID']
@@ -1554,7 +1524,7 @@ class CanvasScene(QtWidgets.QGraphicsScene):
currGraphClone = self.sceneGraph.copy()
nodesToDel = set()
for edgeParentUID, edgeChild in dict(currGraphClone.edges):
for edgeParentUID, edgeChild in currGraphClone.edges:
potentialGroupIDOne = currGraphClone.nodes[edgeParentUID].get('groupID', edgeParentUID)
potentialGroupIDTwo = currGraphClone.nodes[edgeChild].get('groupID', edgeChild)
if potentialGroupIDOne != edgeParentUID:
@@ -1628,13 +1598,13 @@ class CanvasScene(QtWidgets.QGraphicsScene):
nodesOnCanvas = {}
for node in self.nodesDict:
try:
# Tiny differences in milliseconds are not considered to be significant.
# Tiny differences in seconds are not considered to be significant.
entityDate = datetime.fromisoformat(
self.parent().entityDB.getEntity(node)['Date Created']).replace(microsecond=0)
self.parent().entityDB.getEntity(node)['Date Created']).replace(microsecond=0, second=0)
except (TypeError, ValueError):
# Should never happen, but we will handle it if it does.
self.parent().mainWindow.MESSAGEHANDLER.warning('Entity without valid Date Created: ' + str(node))
entityDate = datetime.now().replace(microsecond=0)
self.parent().mainWindow.MESSAGEHANDLER.warning(f'Entity without valid Date Created: {str(node)}')
entityDate = datetime.now().replace(microsecond=0, second=0)
if entityDate not in nodesOnCanvas:
nodesOnCanvas[entityDate] = [node]
else:
@@ -1642,8 +1612,8 @@ class CanvasScene(QtWidgets.QGraphicsScene):
sortedDates = sorted(nodesOnCanvas)
xValue = 0
yValue = 0
for dateIndex in range(len(sortedDates)):
for entityUID in nodesOnCanvas[sortedDates[dateIndex]]:
for dateValue in sortedDates:
for entityUID in nodesOnCanvas[dateValue]:
self.scenePos[entityUID] = (xValue, yValue)
self.nodesDict[entityUID].setPos(QtCore.QPointF(xValue, yValue))
yValue += 150
@@ -1798,7 +1768,7 @@ class CanvasScene(QtWidgets.QGraphicsScene):
# Remove UIDs from list, and delete the link if no more UIDs are left
self.linksDict[edgeToDelete].uid = {linkToStayUID for linkToStayUID in self.linksDict[edgeToDelete].uid
if linkToStayUID not in edgesToDelete[edgeToDelete]}
if len(self.linksDict[edgeToDelete].uid) == 0:
if not self.linksDict[edgeToDelete].uid:
self.removeEdge(self.linksDict[edgeToDelete])
def removeUIDFromLink(self, linkUIDToRemove: tuple) -> None:
@@ -1869,10 +1839,8 @@ class CanvasScene(QtWidgets.QGraphicsScene):
childLinks = self.parent().entityDB.getOutgoingLinks(item)
for childLink in childLinks:
# Ask forgiveness instead of permission - set children as selected, if they are on the canvas.
try:
with contextlib.suppress(KeyError):
self.nodesDict[childLink[1]].setSelected(True)
except KeyError:
pass
def selectParentNodes(self) -> None:
items = [item.uid for item in self.selectedItems() if isinstance(item, Entity.BaseNode)]
@@ -1881,10 +1849,8 @@ class CanvasScene(QtWidgets.QGraphicsScene):
parentLinks = self.parent().entityDB.getIncomingLinks(item)
for parentLink in parentLinks:
# Ask forgiveness instead of permission - set parents as selected, if they are on the canvas.
try:
with contextlib.suppress(KeyError):
self.nodesDict[parentLink[0]].setSelected(True)
except KeyError:
pass
# Because the entities on each canvas are stored in dicts, and dicts are ordered, group nodes will always
# come after the nodes they contain.
@@ -1934,11 +1900,9 @@ class CanvasScene(QtWidgets.QGraphicsScene):
def deleteSelectedItems(self) -> None:
items = self.selectedItems()
for item in items:
if isinstance(item, Entity.BaseNode):
if isinstance(item, Entity.GroupNode):
if item.listProxyWidget is not None:
item.listProxyWidget.hide()
self.removeNode(item)
if isinstance(item, Entity.GroupNode) and item.listProxyWidget is not None:
item.listProxyWidget.hide()
self.removeNode(item)
def removeNode(self, nodeItem: Entity.BaseNode) -> None:
if not isinstance(nodeItem, Entity.BaseNode) or self.sceneGraph.nodes.get(nodeItem.uid) is None:
@@ -1953,14 +1917,10 @@ class CanvasScene(QtWidgets.QGraphicsScene):
self.clearSelection()
self.removeItem(nodeItem)
# Node already deleted
try:
with contextlib.suppress(nx.exception.NetworkXError):
self.sceneGraph.remove_node(uid)
except nx.exception.NetworkXError:
pass
try:
with contextlib.suppress(KeyError):
self.scenePos.pop(uid)
except KeyError:
pass
if isinstance(nodeItem, Entity.GroupNode):
for childUID in nodeItem.groupedNodesUid:
self.sceneGraph.remove_node(childUID)
@@ -1968,22 +1928,18 @@ class CanvasScene(QtWidgets.QGraphicsScene):
del nodeItem
def removeEdge(self, edgeItem: Entity.BaseConnector) -> None:
try:
with contextlib.suppress(KeyError):
# KeyError means that the edge was already removed from linksDict.
self.linksDict.pop(edgeItem.myStartItem.uid + edgeItem.myEndItem.uid)
except KeyError:
# Edge already removed from linksDict.
pass
uidProper = (edgeItem.myStartItem.uid, edgeItem.myEndItem.uid)
edgeItem.hide()
edgeItem.myStartItem.removeConnector(self)
edgeItem.myEndItem.removeConnector(self)
if edgeItem in self.selectedItems():
self.clearSelection()
try:
with contextlib.suppress(nx.exception.NetworkXError):
# NetworkXError is thrown when an edge is already deleted.
self.sceneGraph.remove_edge(uidProper[0], uidProper[1])
except nx.exception.NetworkXError:
# Thrown when an edge is already deleted.
pass
# Could be the case that some link graphics are duplicated or left hanging.
# This would clear them out.
if edgeItem.scene() == self:
@@ -1996,14 +1952,7 @@ class CanvasScene(QtWidgets.QGraphicsScene):
"""
newNodes = [node for node in canvas_nodes if node not in self.nodesDict and
self.parent().mainWindow.LENTDB.getEntity(node)['Entity Type'] != 'EntityGroup']
# newGroupNodes = [y for y in newNodes
# if self.parent().mainWindow.LENTDB.getEntity(y)['Entity Type'] == 'EntityGroup']
# newNodesInGroup = []
# for groupNode in newGroupNodes:
# entityJson = self.parent().mainWindow.LENTDB.getEntity(groupNode)
# self.addNodeProgrammatic(groupNode, entityJson['Child UIDs'], fromServer=True)
# newNodesInGroup += entityJson['Child UIDs']
for node in newNodes: # [z for z in newNodes if z not in newNodesInGroup]:
for node in newNodes:
self.addNodeProgrammatic(node, fromServer=True)
# Edges technically only added if the related nodes are already created,
@@ -2021,7 +1970,6 @@ class PropertiesEditor(QtWidgets.QDialog):
self.setModal(True)
self.isEditingNode = isNode
# self.setLayout(QtWidgets.QGridLayout())
self.setWindowTitle("Properties Editor")
self.setStyleSheet(Stylesheets.MAIN_WINDOW_STYLESHEET)
self.setMinimumSize(500, 300)
@@ -2089,7 +2037,7 @@ class PropertiesEditor(QtWidgets.QDialog):
if isValid is True:
super().accept()
elif isinstance(isValid, str):
self.canvas.parent().mainWindow.MESSAGEHANDLER.error('Entity fields contain invalid values: ' + isValid,
self.canvas.parent().mainWindow.MESSAGEHANDLER.error(f'Entity fields contain invalid values: {isValid}',
exc_info=False)
else:
self.canvas.parent().mainWindow.MESSAGEHANDLER.error('Error occurred when checking validity of entity '
@@ -2162,8 +2110,7 @@ class PropertiesEditorIconField(QtWidgets.QLabel):
except ValueError as ve:
# Image type is unsupported (for ImageQt)
# Supported types: 1, L, P, RGB, RGBA
self.canvas.parent().mainWindow.MESSAGEHANDLER.warning(
'Invalid Image selected: ' + str(ve), popUp=True)
self.canvas.parent().mainWindow.MESSAGEHANDLER.warning(f'Invalid Image selected: {str(ve)}', popUp=True)
super(PropertiesEditorIconField, self).mousePressEvent(event)

View File

@@ -352,7 +352,7 @@ class ResolutionList(QtWidgets.QTreeWidget):
if resolution not in self.resolutionManager.getResolutionsInCategory(category):
return
self.mainWindow.runResolution(category + '/' + resolution)
self.mainWindow.runResolution(f'{category}/{resolution}')
class ResolutionWidget(QtWidgets.QTreeWidgetItem):

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import contextlib
from PySide6 import QtWidgets, QtCore, QtCharts, QtGui
from Core.Interface import Stylesheets
from datetime import datetime
@@ -129,16 +130,12 @@ class TimeWidget(QtWidgets.QWidget):
if nodeMinute not in self.timeDetails[nodeYear][nodeMonth][nodeDay][nodeHour]:
# Sanity check. Should not be able to remove nodes that do not exist, but you never know.
if added:
self.timeDetails[nodeYear][nodeMonth][nodeDay][nodeHour][nodeMinute] = 1
else:
self.timeDetails[nodeYear][nodeMonth][nodeDay][nodeHour][nodeMinute] = 0
else:
if added:
self.timeDetails[nodeYear][nodeMonth][nodeDay][nodeHour][nodeMinute] += 1
else:
if self.timeDetails[nodeYear][nodeMonth][nodeDay][nodeHour][nodeMinute] > 0:
self.timeDetails[nodeYear][nodeMonth][nodeDay][nodeHour][nodeMinute] -= 1
self.timeDetails[nodeYear][nodeMonth][nodeDay][nodeHour][nodeMinute] = 1 if added else 0
elif added:
self.timeDetails[nodeYear][nodeMonth][nodeDay][nodeHour][nodeMinute] += 1
elif self.timeDetails[nodeYear][nodeMonth][nodeDay][nodeHour][nodeMinute] > 0:
self.timeDetails[nodeYear][nodeMonth][nodeDay][nodeHour][nodeMinute] -= 1
if updateGraph:
self.drawChart([])
@@ -177,15 +174,11 @@ class TimeWidget(QtWidgets.QWidget):
if minute is not None:
barsDict = {minute: self.timeDetails[year][month][day][hour][minute]}
self.currentTimeStep = [year, month, day, hour, minute]
self.drawChartHelper(barsDict, self.currentTimeStep)
elif hour is not None:
barsDict = {}
for minute in self.timeDetails[year][month][day][hour]:
barsDict[minute] = self.timeDetails[year][month][day][hour][minute]
self.currentTimeStep = [year, month, day, hour]
self.drawChartHelper(barsDict, self.currentTimeStep)
barsDict = {minute: self.timeDetails[year][month][day][hour][minute]
for minute in self.timeDetails[year][month][day][hour]}
self.currentTimeStep = [year, month, day, hour]
elif day is not None:
barsDict = {}
for hour in self.timeDetails[year][month][day]:
@@ -193,8 +186,6 @@ class TimeWidget(QtWidgets.QWidget):
for minute in self.timeDetails[year][month][day][hour]:
barsDict[hour] += self.timeDetails[year][month][day][hour][minute]
self.currentTimeStep = [year, month, day]
self.drawChartHelper(barsDict, self.currentTimeStep)
elif month is not None:
barsDict = {}
for day in self.timeDetails[year][month]:
@@ -203,8 +194,6 @@ class TimeWidget(QtWidgets.QWidget):
for minute in self.timeDetails[year][month][day][hour]:
barsDict[day] += self.timeDetails[year][month][day][hour][minute]
self.currentTimeStep = [year, month]
self.drawChartHelper(barsDict, self.currentTimeStep)
elif year is not None:
barsDict = {}
for month in self.timeDetails[year]:
@@ -214,8 +203,6 @@ class TimeWidget(QtWidgets.QWidget):
for minute in self.timeDetails[year][month][day][hour]:
barsDict[month] += self.timeDetails[year][month][day][hour][minute]
self.currentTimeStep = [year]
self.drawChartHelper(barsDict, self.currentTimeStep)
else:
barsDict = {}
for year in self.timeDetails:
@@ -226,7 +213,8 @@ class TimeWidget(QtWidgets.QWidget):
for minute in self.timeDetails[year][month][day][hour]:
barsDict[year] += self.timeDetails[year][month][day][hour][minute]
self.currentTimeStep = []
self.drawChartHelper(barsDict, self.currentTimeStep)
self.drawChartHelper(barsDict, self.currentTimeStep)
def drawChartHelper(self, barsDict: dict, timestep: list):
timelineSeries = QtCharts.QBarSeries(self.timelineChart)
@@ -241,14 +229,13 @@ class TimeWidget(QtWidgets.QWidget):
if barsDict[bar] > maxEntityNum:
maxEntityNum = barsDict[bar]
value = ""
for step in range(len(timestep)):
for step, stepValue in enumerate(timestep):
if step <= 2:
value += str(timestep[step]) + '/'
value += f'{str(stepValue)}/'
if step == 2:
value = value[:-1]
value += " "
value = f"{value[:-1]} "
else:
value += str(timestep[step]) + ':'
value += f'{str(stepValue)}:'
value += str(bar)
xAxisValues.append(value)
self.timelineChart.removeAllSeries()
@@ -276,10 +263,7 @@ class TimeWidget(QtWidgets.QWidget):
self.timescaleSelector.adjustLabelsToHour()
elif timesteps == 4:
self.timescaleSelector.adjustLabelsToMinute()
elif timesteps == 5:
self.timescaleSelector.adjustLabelsToSecond()
else:
# Just in case.
self.timescaleSelector.adjustLabelsToSecond()
yAxis.applyNiceNumbers()
@@ -501,7 +485,7 @@ class ChatBox(QtWidgets.QWidget):
self.setMinimumWidth(500)
self.setMaximumWidth(500)
self.chatName = getuser() + ": "
self.chatName = f"{getuser()}: "
chatLayout = QtWidgets.QGridLayout()
self.setLayout(chatLayout)
@@ -536,14 +520,10 @@ class LoggingUpdateThread(QtCore.QThread):
self.messageHandler = messageHandler
def run(self):
while True:
if self.endLogging:
break
while not self.endLogging:
if not self.messageHandler.logQueue.empty():
try:
with contextlib.suppress(queue.Empty):
logMsg = self.messageHandler.logQueue.get().getMessage()
self.loggingSignal.emit(logMsg)
except queue.Empty:
pass
else:
self.msleep(100)

View File

@@ -50,13 +50,10 @@ class DockBarTwo(QtWidgets.QDockWidget):
self.initialiseLayout()
def setNotesText(self, newText: str) -> None:
if self.tabNotes.textEditor.isReadOnly():
self.tabNotes.textEditor.contents = newText
self.tabNotes.textEditor.setMarkdown(newText)
else:
if not self.tabNotes.textEditor.isReadOnly():
self.tabNotes.textEditor.setReadOnly(True)
self.tabNotes.textEditor.contents = newText
self.tabNotes.textEditor.setMarkdown(newText)
self.tabNotes.textEditor.contents = newText
self.tabNotes.textEditor.setMarkdown(newText)
def getNotesText(self) -> str:
"""
@@ -248,15 +245,15 @@ class EntityDetails(QtWidgets.QWidget):
else:
self.populateMultiRelationshipHelperLink(item)
linksNumber += 1
self.multiNodesTableLabelOne.setText('Selected Nodes: ' + str(nodesNumber))
self.multiNodesTableLabelTwo.setText('Selected Links: ' + str(linksNumber))
self.multiNodesTableLabelOne.setText(f'Selected Nodes: {str(nodesNumber)}')
self.multiNodesTableLabelTwo.setText(f'Selected Links: {str(linksNumber)}')
self.switchLayoutHelper(numberOfItems, isNode)
except Exception as exc:
# If an error is thrown at some point during the process, show the default nothing selected screen.
self.detailsLayout.setCurrentIndex(0)
self.mainWindow.MESSAGEHANDLER.error('Error occurred while trying to display the details of the selected '
'nodes: ' + str(exc), popUp=False, exc_info=False)
self.mainWindow.MESSAGEHANDLER.error(f'Error occurred while trying to display the details of the selected '
f'nodes: {str(exc)}', popUp=False, exc_info=False)
# Display helper functions
def clearDetailsHelper(self) -> None:
@@ -279,7 +276,7 @@ class EntityDetails(QtWidgets.QWidget):
return
rowCount = 0
for key in jsonDict:
if key == "uid" or key == "Child UIDs" or key == "Icon":
if key in ["uid", "Child UIDs", "Icon"]:
continue
elif key == "Notes":
notesTextArea = RichNotesEditor(self, jsonDict[key], False)
@@ -339,7 +336,7 @@ class EntityDetails(QtWidgets.QWidget):
nodePixmap,
edgeJson[list(edgeJson)[1]],
uid)
self.relationshipsIncomingTable.setHeaderLabel('Incoming Links: ' + str(len(inc)))
self.relationshipsIncomingTable.setHeaderLabel(f'Incoming Links: {len(inc)}')
for edge in out:
uid = edge[1]
edgeJson = self.entityDB.getEntity(uid)
@@ -349,7 +346,7 @@ class EntityDetails(QtWidgets.QWidget):
nodePixmap,
edgeJson[list(edgeJson)[1]],
uid)
self.relationshipsOutgoingTable.setHeaderLabel('Outgoing Links: ' + str(len(out)))
self.relationshipsOutgoingTable.setHeaderLabel(f'Outgoing Links: {len(out)}')
def populateMultiRelationshipHelperNode(self, nodeJson) -> None:
inc = len(self.entityDB.getIncomingLinks(nodeJson['uid']))

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import contextlib
from json import dumps
import math
from typing import Any, Optional
@@ -69,18 +70,16 @@ class BaseNode(QGraphicsItemGroup):
def updateLabel(self, newText: str = '') -> None:
if not isinstance(newText, str):
newText = str(newText)
newText = newText
if newText != '':
if len(newText) > 50:
newText = newText[:47] + "..."
newText = f"{newText[:47]}..."
self.labelItem.setPlainText(newText)
def removeConnector(self, connector) -> None:
# Exception could be thrown if the connector is already deleted.
try:
with contextlib.suppress(ValueError):
self.connectors.remove(connector)
except ValueError:
pass
def addConnector(self, connector) -> None:
self.connectors.append(connector)
@@ -235,10 +234,7 @@ class BaseConnector(QGraphicsItemGroup):
self.updateLabel(name)
if uid is not None:
if isinstance(uid, list) or isinstance(uid, set):
self.uid = set(uid)
else:
self.uid = {uid}
self.uid = set(uid) if isinstance(uid, (list, set)) else {uid}
else:
self.uid = {(origin.uid, destination.uid)}
@@ -262,7 +258,7 @@ class BaseConnector(QGraphicsItemGroup):
def updateLabel(self, newText: str = '') -> None:
if len(newText) > 50:
newText = newText[:47] + "..."
newText = f"{newText[:47]}..."
self.labelItem.setText(newText)
self.update()

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import contextlib
import hashlib
import re
import json
@@ -508,11 +509,9 @@ class MenuBar(QtWidgets.QMenuBar):
fileContents = []
with open(fileDirectory, 'r') as importFile:
# Read a maximum of 3 lines from the file:
count = 0
for line in importFile:
for index, line in enumerate(importFile):
fileContents.append(line.strip())
count += 1
if count >= 3:
if index > 2:
break
importTextFileDialog = ImportFromTextFileDialog(self, fileContents)
@@ -561,10 +560,10 @@ class MenuBar(QtWidgets.QMenuBar):
importEntityCSVDialog = ImportEntityFromCSVFile(self, csvDF)
if importEntityCSVDialog.exec_():
attributeRows = [comboBox.currentText()
for comboBox in importEntityCSVDialog.fieldMappingComboBoxes]
for attribute in range(len(attributeRows)):
if attributeRows[attribute] == '':
attributeRows[attribute] = csvDF.columns[attribute]
if comboBox.currentText() else
csvDF.columns[index]
for index, comboBox in
enumerate(importEntityCSVDialog.fieldMappingComboBoxes)]
if importEntityCSVDialog.importToCanvasCheckbox.isChecked():
sceneToAddTo = self.parent().centralWidget().tabbedPane.getSceneByName(
@@ -572,8 +571,8 @@ class MenuBar(QtWidgets.QMenuBar):
entityTypeToImportAs = importEntityCSVDialog.entityTypeChoiceDropdown.currentText()
for row in csvDF.itertuples(index=False):
newEntityJSON = {str(attributeRows[key]).strip(): str(row[key]).strip()
for key in range(len(attributeRows))}
newEntityJSON = {str(value).strip(): str(row[index]).strip()
for index, value in enumerate(attributeRows)}
newEntityJSON['Entity Type'] = entityTypeToImportAs
if newEntityJSON not in newNodes:
primaryAttr = newEntityJSON[
@@ -608,9 +607,9 @@ class MenuBar(QtWidgets.QMenuBar):
importLinksCSVDialog = ImportLinksFromCSVFile(self, csvDF)
if importLinksCSVDialog.exec_():
unmapped = []
for columnIndex in range(len(importLinksCSVDialog.fieldMappingComboBoxes)):
columnMapping = importLinksCSVDialog.fieldMappingComboBoxes[columnIndex].currentText()
if columnMapping != '':
for columnIndex, columnValue in enumerate(importLinksCSVDialog.fieldMappingComboBoxes):
columnMapping = columnValue.currentText()
if columnMapping:
csvDF.rename(columns={csvDF.columns[columnIndex]: columnMapping}, inplace=True)
elif not importLinksCSVDialog.fieldIncludeCheckBoxes[columnIndex].isChecked():
unmapped.append(csvDF.columns[columnIndex])
@@ -628,10 +627,10 @@ class MenuBar(QtWidgets.QMenuBar):
entityTwoType = importLinksCSVDialog.entityTwoTypeChoiceDropdown.currentText()
attributeRows = [comboBox.currentText()
for comboBox in createLinkEntitiesDialog.fieldMappingComboBoxes]
for attribute in range(len(attributeRows)):
if attributeRows[attribute] == '':
attributeRows[attribute] = fieldsRemainingDF.columns[attribute]
if comboBox.currentText()
else fieldsRemainingDF.columns[index]
for index, comboBox
in enumerate(createLinkEntitiesDialog.fieldMappingComboBoxes)]
entityTypeToImportAs = \
createLinkEntitiesDialog.entityTypeChoiceDropdown.currentText()
@@ -642,14 +641,13 @@ class MenuBar(QtWidgets.QMenuBar):
for entityRow, linkRow in zip(fieldsRemainingDF.itertuples(index=False),
csvDF.itertuples(index=False)):
count = 0
linkJSON = {}
entityOneJSON = {}
entityTwoJSON = {}
resolutionID = ""
notes = ""
for column in linkRow:
for count, column in enumerate(linkRow):
column = str(column)
mapping = csvDF.columns[count]
if mapping == 'Entity One':
@@ -674,7 +672,6 @@ class MenuBar(QtWidgets.QMenuBar):
resolutionID = column
else:
linkJSON[mapping] = column
count += 1
# We still need to check if both nodes exist, since errors may have occurred
# during their creation.
@@ -696,12 +693,12 @@ class MenuBar(QtWidgets.QMenuBar):
linkJSONTwo = dict(linkJSON)
linkJSONTwo['Resolution'] += ' IN'
newEntityJSON = {str(attributeRows[key]): str(entityRow[key]).strip()
for key in range(len(attributeRows))}
newEntityJSON = {str(value): str(entityRow[index]).strip()
for index, value in enumerate(attributeRows)}
newEntityJSON['Entity Type'] = entityTypeToImportAs
if randomizePrimary:
newEntityJSON[newEntityPrimaryAttribute] += ' | ' + str(uuid4())
newEntityJSON[newEntityPrimaryAttribute] += f' | {str(uuid4())}'
newNode = self.parent().LENTDB.getEntityOfType(
newEntityJSON[newEntityPrimaryAttribute], entityTypeToImportAs)
@@ -734,14 +731,13 @@ class MenuBar(QtWidgets.QMenuBar):
entityTwoType = importLinksCSVDialog.entityTwoTypeChoiceDropdown.currentText()
for row in csvDF.itertuples(index=False):
count = 0
linkJSON = {}
entityOneJSON = {}
entityTwoJSON = {}
resolutionID = ""
notes = ""
for column in row:
for count, column in enumerate(row):
column = str(column)
mapping = csvDF.columns[count]
if mapping == 'Entity One':
@@ -756,7 +752,6 @@ class MenuBar(QtWidgets.QMenuBar):
resolutionID = column
else:
linkJSON[mapping] = column
count += 1
if (entityOneJSON is not None) and (entityTwoJSON is not None):
linkJSON['uid'] = (entityOneJSON['uid'], entityTwoJSON['uid'])
@@ -815,14 +810,8 @@ class MenuBar(QtWidgets.QMenuBar):
if canvasSaveDialogAccept and fileDirectory != '':
canvas = canvasSaveDialog.chosenCanvasDropdown.currentText()
if canvasSaveDialog.justViewportChoice.isChecked():
justViewport = True
else:
justViewport = False
if canvasSaveDialog.transparentChoice.isChecked():
transparentBackground = True
else:
transparentBackground = False
justViewport = canvasSaveDialog.justViewportChoice.isChecked()
transparentBackground = canvasSaveDialog.transparentChoice.isChecked()
picture = self.parent().getPictureOfCanvas(canvas, justViewport, transparentBackground)
picture.save(fileDirectory, "PNG")
@@ -957,10 +946,7 @@ class MenuBar(QtWidgets.QMenuBar):
self.parent().FCOM.askServerForFileList(project_name)
def forceDatabaseSync(self) -> None:
if self.parent().FCOM.isConnected():
project_name = self.parent().SETTINGS.value("Project/Server/Project")
with self.parent().LENTDB.dbLock:
self.parent().FCOM.syncDatabase(project_name, self.parent().LENTDB.database)
self.parent().syncDatabase()
def uploadFiles(self) -> None:
self.parent().uploadFiles()
@@ -1036,10 +1022,8 @@ class MenuBar(QtWidgets.QMenuBar):
selectedURLs = SearchEngineDialog(self)
if selectedURLs.exec_():
selectedEngineURLs = []
for engineCheckbox in selectedURLs.searchEngineWidgets:
if engineCheckbox.isChecked():
selectedEngineURLs.append(engineCheckbox.searchAttr)
selectedEngineURLs = [engineCheckbox.searchAttr for engineCheckbox in selectedURLs.searchEngineWidgets
if engineCheckbox.isChecked()]
searchTerms = []
for item in currentScene.selectedItems():
if isinstance(item, BaseNode):
@@ -1057,10 +1041,8 @@ class MenuBar(QtWidgets.QMenuBar):
selectedURLs = SearchImageEngineDialog(self)
if selectedURLs.exec_():
selectedEngineURLs = []
for engineCheckbox in selectedURLs.searchEngineWidgets:
if engineCheckbox.isChecked():
selectedEngineURLs.append(engineCheckbox.searchAttr)
selectedEngineURLs = [engineCheckbox.searchAttr for engineCheckbox in selectedURLs.searchEngineWidgets
if engineCheckbox.isChecked()]
for searchEngineURL in selectedEngineURLs:
try:
QtGui.QDesktopServices.openUrl(searchEngineURL)
@@ -1092,7 +1074,7 @@ class MenuBar(QtWidgets.QMenuBar):
except KeyError:
continue
if len(websiteEntities) == 0:
if not websiteEntities:
self.parent().MESSAGEHANDLER.warning('Please select the "Website" nodes that correspond to the sites that '
'you wish to download, and re-run the Download Selected Websites '
'operation.',
@@ -1121,7 +1103,7 @@ class MenuBar(QtWidgets.QMenuBar):
except KeyError:
continue
if len(websiteEntities) == 0:
if not websiteEntities:
self.parent().MESSAGEHANDLER.warning('Please select the "Website" nodes that correspond to the sites that '
'you wish to download, and re-run the Download Selected Websites '
'operation.',
@@ -1153,9 +1135,8 @@ class MenuBar(QtWidgets.QMenuBar):
fileName = itemPrimaryField + ' | ' + itemJSON.get('Date Last Edited', str(time.time_ns())) + '.txt'
fileName = fileName.replace('/', '+')
fileName = fileName.replace('\\', '+')
f = open(baseFilesPath / fileName, "w")
f.write(itemJSONNotes)
f.close()
with open(baseFilesPath / fileName, "w") as f:
f.write(itemJSONNotes)
newNodes.append([{'Document Name': fileName,
'File Path': fileName,
'Entity Type': 'Document'},
@@ -1172,7 +1153,7 @@ class MenuBar(QtWidgets.QMenuBar):
:return:
"""
if platform.system() != 'Linux' and platform.system() != 'Windows':
if platform.system() not in ['Linux', 'Windows']:
self.parent().setStatus('Importing tabs not supported on platforms other than Linux and Windows.')
self.parent().MESSAGEHANDLER.warning('Importing tabs not supported on platforms '
'other than Linux and Windows.', popUp=True)
@@ -1279,7 +1260,7 @@ class MenuBar(QtWidgets.QMenuBar):
del newNodeJSON[newNodePrimaryFieldKey]
try:
notesField = newNodeJSON.pop('Notes')
existingEntityJSON['Notes'] += '\n' + notesField
existingEntityJSON['Notes'] += f'\n{notesField}'
except KeyError:
# If no new field was actually added to the entity, don't re-add to the database
if len(newNodeJSON) == 0:
@@ -1303,10 +1284,9 @@ class MenuBar(QtWidgets.QMenuBar):
allEntityUIDs.append(entityJson['uid'])
allEntityPrimaryFieldsAndTypes.append((newNodePrimaryField, newNodeEntityType))
for resultListIndex in range(len(resolution_result)):
if len(resolution_result[resultListIndex]) > 1:
outputEntityUID = newNodeUIDs[resultListIndex]
parentsDict = resolution_result[resultListIndex][1]
for parentsDictHolder, outputEntityUID in zip(resolution_result, newNodeUIDs):
if len(parentsDictHolder) > 1:
parentsDict = parentsDictHolder[1]
for parentID in parentsDict:
parentUID = parentID
if isinstance(parentUID, int):
@@ -1319,7 +1299,7 @@ class MenuBar(QtWidgets.QMenuBar):
if newLinkUID in allLinks:
linkJson = self.parent().LENTDB.getLinkIfExists(newLinkUID)
if resolutionName not in linkJson['Notes']:
linkJson['Notes'] += '\nConnection also produced by Resolution: ' + resolutionName
linkJson['Notes'] += f'\nConnection also produced by Resolution: {resolutionName}'
self.parent().LENTDB.addLink(linkJson, fromServer=True)
else:
self.parent().LENTDB.addLink({'uid': newLinkUID, 'Resolution': resolutionName,
@@ -1351,7 +1331,7 @@ class DeleteProjectConfirmationDialog(QtWidgets.QDialog):
self.mainWindowObject = mainWindowObject
self.setWindowTitle('Delete Server Project')
resolutionsLabel = QtWidgets.QLabel('Delete Project: "' + currentServerProject + '" ?')
resolutionsLabel = QtWidgets.QLabel(f'Delete Project: "{currentServerProject}" ?')
resolutionsLabel.setWordWrap(True)
resolutionsLabel.setAlignment(QtCore.Qt.AlignCenter)
@@ -1626,7 +1606,7 @@ class CollectorsDialog(QtWidgets.QDialog):
self.connectedToServerFormWidgetLayout = QtWidgets.QVBoxLayout()
for category in collectorsDict:
categoryLabel = QtWidgets.QLabel("Category: " + str(category))
categoryLabel = QtWidgets.QLabel(f"Category: {str(category)}")
self.connectedToServerFormWidgetLayout.addWidget(categoryLabel)
for collector in collectorsDict[category]:
newCollectorWidget = QtWidgets.QWidget()
@@ -1651,8 +1631,8 @@ class CollectorsDialog(QtWidgets.QDialog):
newCollectorWidgetLayout.addWidget(newCollectorInstanceTree, 1, 0, 2, 2)
if runningCollectors is not None:
for runningCollectorCategory in runningCollectors:
for runningCollector in runningCollectors[runningCollectorCategory][collector]:
for value in runningCollectors.values():
for runningCollector in value[collector]:
newTreeItem = QtWidgets.QTreeWidgetItem(newCollectorInstanceTree)
newTreeItem.setText(0, runningCollector['uid'])
stopButton = QtWidgets.QPushButton("Stop")
@@ -1683,10 +1663,10 @@ class CollectorsDialog(QtWidgets.QDialog):
newCollector.chosenParameters)
self.mainWindow.MESSAGEHANDLER.info('Starting server collector: ' + collector_name)
except Exception as e:
self.mainWindow.MESSAGEHANDLER.error('Error starting server collector: ' + str(e))
self.mainWindow.MESSAGEHANDLER.error(f'Error starting server collector: {str(e)}')
def stopSelectedCollector(self, collectorToStop: str):
self.mainWindow.MESSAGEHANDLER.info('Stopping server collector with UID: ' + collectorToStop)
self.mainWindow.MESSAGEHANDLER.info(f'Stopping server collector with UID: {collectorToStop}')
self.mainWindow.FCOM.stopServerCollector(collectorToStop)
self.runningCollectorTreeItems[collectorToStop][1].takeTopLevelItem(
self.runningCollectorTreeItems[collectorToStop][1].indexOfTopLevelItem(
@@ -1890,7 +1870,7 @@ class ImportLinksFromCSVFile(QtWidgets.QDialog):
tableFieldCheckBoxesWidget = QtWidgets.QWidget()
tableFieldCheckBoxesWidgetLayout = QtWidgets.QHBoxLayout()
tableFieldCheckBoxesWidget.setLayout(tableFieldCheckBoxesWidgetLayout)
for fieldIndex in range(columnNumber):
for _ in range(columnNumber):
checkBoxWidget = QtWidgets.QCheckBox('Include Field? ')
checkBoxWidget.setToolTip('Check the box to include this column as an attribute for the resolution.')
self.fieldIncludeCheckBoxes.append(checkBoxWidget)
@@ -1955,9 +1935,7 @@ class ImportLinksFromCSVFile(QtWidgets.QDialog):
importLayout.addWidget(buttonsWidget)
def changeMappingForField(self, newIndex):
for comboBoxIndex in range(len(self.fieldMappingComboBoxes)):
comboBox = self.fieldMappingComboBoxes[comboBoxIndex]
checkBox = self.fieldIncludeCheckBoxes[comboBoxIndex]
for comboBox, checkBox in zip(self.fieldMappingComboBoxes, self.fieldIncludeCheckBoxes):
if comboBox.currentIndex() == newIndex:
if newIndex == 0:
checkBox.setEnabled(True)
@@ -1967,9 +1945,8 @@ class ImportLinksFromCSVFile(QtWidgets.QDialog):
checkBox.setChecked(True)
else:
comboBox.setCurrentIndex(0)
if newIndex != 0:
checkBox.setEnabled(True)
checkBox.setChecked(False)
checkBox.setEnabled(True)
checkBox.setChecked(False)
def checkIfEntitiesAreMapped(self):
# Check if Entity One and Entity Two labels are assigned, do not proceed if not.
@@ -2037,7 +2014,7 @@ class ImportLinkEntitiesFromCSVFile(QtWidgets.QDialog):
tableFieldAttributeMapping = QtWidgets.QWidget()
tableFieldAttributeMappingLayout = QtWidgets.QHBoxLayout()
tableFieldAttributeMapping.setLayout(tableFieldAttributeMappingLayout)
for fieldIndex in range(columnNumber):
for _ in range(columnNumber):
fieldMappingWidget = QtWidgets.QComboBox()
fieldMappingWidget.setEditable(False)
fieldMappingWidget.currentIndexChanged.connect(self.changeMappingForField)
@@ -2090,12 +2067,7 @@ class ImportLinkEntitiesFromCSVFile(QtWidgets.QDialog):
def confirmThatPrimaryFieldIsMapped(self):
primaryField = self.parent().parent().RESOURCEHANDLER.getPrimaryFieldForEntityType(
self.entityTypeChoiceDropdown.currentText())
primaryFieldMapped = False
for comboBox in self.fieldMappingComboBoxes:
if comboBox.currentText() == primaryField:
# Just doing self.accept() here will not work.
primaryFieldMapped = True
break
primaryFieldMapped = any(comboBox.currentText() == primaryField for comboBox in self.fieldMappingComboBoxes)
if primaryFieldMapped:
self.accept()
else:
@@ -2143,7 +2115,7 @@ class ImportEntityFromCSVFile(QtWidgets.QDialog):
tableFieldAttributeMapping = QtWidgets.QWidget()
tableFieldAttributeMappingLayout = QtWidgets.QHBoxLayout()
tableFieldAttributeMapping.setLayout(tableFieldAttributeMappingLayout)
for fieldIndex in range(columnNumber):
for _ in range(columnNumber):
fieldMappingWidget = QtWidgets.QComboBox()
fieldMappingWidget.setEditable(False)
fieldMappingWidget.currentIndexChanged.connect(self.changeMappingForField)
@@ -2321,10 +2293,10 @@ class ImportFromTextFileDialog(QtWidgets.QDialog):
textTable = QtWidgets.QTableWidget(3, 1, self)
textTable.setWordWrap(True)
textTable.setFixedWidth(450)
for line in range(len(fileContents)):
columnItem = QtWidgets.QTableWidgetItem(fileContents[line])
for lineIndex, lineValue in enumerate(fileContents):
columnItem = QtWidgets.QTableWidgetItem(lineValue)
columnItem.setFlags(columnItem.flags() & ~QtCore.Qt.ItemIsEditable)
textTable.setItem(line, 0, columnItem)
textTable.setItem(lineIndex, 0, columnItem)
textTable.setColumnWidth(0, 450)
textTable.setHorizontalHeaderLabels(['File Entities Preview'])
@@ -2561,8 +2533,7 @@ class ScreenshotWebsiteThread(QtCore.QThread):
)
urlPath = Path(os.environ['APPDATA']) / 'Mozilla' / 'Firefox' / 'Profiles'
tabsFilePath = list(urlPath.glob('*default*/sessionstore-backups/recovery.jsonlz4'))
if len(tabsFilePath) != 0:
if tabsFilePath := list(urlPath.glob('*default*/sessionstore-backups/recovery.jsonlz4')):
tabsFilePath = tabsFilePath[0]
cookiesDatabasePath = tabsFilePath.parent.parent / 'cookies.sqlite'
browserCookies = self.menuBar.firefoxCookiesHelper(cookiesDatabasePath)
@@ -2623,7 +2594,7 @@ class SaveWebsiteThread(QtCore.QThread):
responseURL = response.url
responseURLFragments = responseURL.split('/')[3:]
savePath = currTempDir
try:
with contextlib.suppress(Exception):
if response.ok:
for fragment in responseURLFragments[:-1]:
savePath /= fragment
@@ -2632,12 +2603,10 @@ class SaveWebsiteThread(QtCore.QThread):
filename = responseURLFragments[-1]
except IndexError:
filename = ''
if filename == '':
if not filename:
filename = 'index.html'
with open(savePath / filename, "wb") as fileToWrite:
fileToWrite.write(response.body())
except Exception:
pass
with sync_playwright() as p:
browser = p.firefox.launch()
@@ -2655,8 +2624,7 @@ class SaveWebsiteThread(QtCore.QThread):
)
urlPath = Path(os.environ['APPDATA']) / 'Mozilla' / 'Firefox' / 'Profiles'
tabsFilePath = list(urlPath.glob('*default*/sessionstore-backups/recovery.jsonlz4'))
if len(tabsFilePath) != 0:
if tabsFilePath := list(urlPath.glob('*default*/sessionstore-backups/recovery.jsonlz4')):
tabsFilePath = tabsFilePath[0]
cookiesDatabasePath = tabsFilePath.parent.parent / 'cookies.sqlite'
browserCookies = self.menuBar.firefoxCookiesHelper(cookiesDatabasePath)
@@ -2673,13 +2641,11 @@ class SaveWebsiteThread(QtCore.QThread):
archiveDir = baseFilesPath / (tldextract.extract(website).fqdn + ' Snapshot ' + str(time.time_ns()))
for _ in range(3):
try:
with contextlib.suppress(TimeoutError):
page.goto(website)
page.keyboard.press("End")
page.wait_for_load_state("networkidle")
break
except TimeoutError:
pass
progressValue += 1
self.progressSignal.emit(progressValue)
@@ -2734,11 +2700,7 @@ class ImportBrowserTabsThread(QtCore.QThread):
)
urlPath = Path(os.environ['APPDATA']) / 'Mozilla' / 'Firefox' / 'Profiles'
tabsFilePath = list(urlPath.glob('*default*/sessionstore-backups/recovery.jsonlz4'))
if len(tabsFilePath) == 0:
self.mainWindow.warningSignalListener.emit('No Firefox session detected. Skipping importing '
'from Firefox.', True)
else:
if tabsFilePath := list(urlPath.glob('*default*/sessionstore-backups/recovery.jsonlz4')):
tabsFilePath = tabsFilePath[0]
if recordSession:
tabsToOpen = []
@@ -2837,23 +2799,20 @@ class ImportBrowserTabsThread(QtCore.QThread):
if urlSaveDir is not None:
timeNow = str(datetime.now().timestamp() * 1000000).split('.')[0]
screenshotSavePath = str(urlSaveDir / (
actualURL.replace('/', '+') +
' ' + timeNow + ' screenshot.png'))
actualURL.replace('/', '+') + ' ' + timeNow + ' screenshot.png'))
screenshotSavePath = screenshotSavePath.replace('\\', '+')
screenshotEntity = {'Image Name': decodedPath + ' Screenshot ' +
timeNow,
screenshotEntity = {'Image Name': f"{decodedPath} Screenshot {timeNow}",
'File Path': screenshotSavePath,
'Entity Type': 'Image'}
try:
page.screenshot(path=screenshotSavePath, full_page=True)
except Error:
try:
page.screenshot(path=screenshotSavePath, full_page=False)
except Error:
screenshotEntity = {'Phrase': 'Could not take screenshot of ' +
decodedPath,
screenshotEntity = {'Phrase': f'Could not take screenshot of '
f'{decodedPath}',
'Entity Type': 'Phrase'}
returnResults.append(
@@ -2866,10 +2825,12 @@ class ImportBrowserTabsThread(QtCore.QThread):
newEntity.append({historyMark: {'Resolution': 'Next Page'}})
historyMark = len(returnResults)
returnResults.append(newEntity)
else:
self.mainWindow.warningSignalListener.emit('No Firefox session detected. Skipping importing '
'from Firefox.', True)
browser.close()
except Error as e:
self.mainWindow.warningSignalListener.emit('Cannot import tabs from Firefox: ' + str(repr(e)), True)
self.mainWindow.warningSignalListener.emit(f'Cannot import tabs from Firefox: {str(repr(e))}', True)
if self.importDialog.chromeChoice.isChecked() and not self.cancelled:
progressValue = 2
@@ -3017,7 +2978,7 @@ class ImportBrowserTabsThread(QtCore.QThread):
page.screenshot(path=screenshotSavePath, full_page=True)
returnResults.append(
[{'Image Name': decodedPath + ' Screenshot ' + timeNow,
[{'Image Name': f"{decodedPath} Screenshot {timeNow}",
'File Path': screenshotSavePath,
'Entity Type': 'Image'},
{len(returnResults) - 1: {'Resolution': 'Screenshot of Tab',

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import contextlib
from typing import Union, Optional, Any
from uuid import uuid4
import re
@@ -27,10 +28,9 @@ class LQLQueryBuilder:
self.mainWindow = mainWindow
def takeSnapshot(self):
self.mainWindow.LENTDB.dbLock.acquire()
# Create a copy
self.databaseSnapshot = self.mainWindow.LENTDB.database.copy()
self.mainWindow.LENTDB.dbLock.release()
with self.mainWindow.LENTDB.dbLock:
# Create a copy
self.databaseSnapshot = self.mainWindow.LENTDB.database.copy()
self.databaseEntities = set(self.databaseSnapshot.nodes)
@@ -82,16 +82,14 @@ class LQLQueryBuilder:
def parseSelect(self, selectClause: str, selectValue: Union[str, list]):
if selectClause == 'SELECT':
if '*' in selectValue:
# No need to remove the '*'. Could cause errors if that's a field name (even though it is bad practice).
return self.allEntityFields
return set([entityField for entityField in selectValue if entityField in self.allEntityFields])
else:
try:
clauseValue = re.compile(selectValue)
return set([entityField for entityField in self.allEntityFields if clauseValue.match(entityField)])
except re.error:
return set()
return self.allEntityFields if '*' in selectValue else \
{entityField for entityField in selectValue if entityField in self.allEntityFields}
try:
clauseValue = re.compile(selectValue)
return {entityField for entityField in self.allEntityFields if clauseValue.match(entityField)}
except re.error:
return set()
def parseSource(self, sourceClause: str, sourceValues: Union[None, list], fieldsToSelect: set) -> set:
"""
@@ -118,32 +116,24 @@ class LQLQueryBuilder:
except (ValueError, re.error):
continue
# Not the most efficient way of phrasing this, but by far the most compact and legible.
for matchingCanvas in matchingCanvases:
if sourceValue[0] == 'AND':
if sourceValue[2] is True:
resultEntitySet = self.canvasAndNot(resultEntitySet,
self.canvasesEntitiesDict[matchingCanvas])
else:
resultEntitySet = self.canvasAnd(resultEntitySet,
self.canvasesEntitiesDict[matchingCanvas])
resultEntitySet = self.canvasAndNot(resultEntitySet, self.canvasesEntitiesDict[matchingCanvas])\
if sourceValue[2] is True else\
self.canvasAnd(resultEntitySet, self.canvasesEntitiesDict[matchingCanvas])
elif sourceValue[2] is True:
resultEntitySet = self.canvasOrNot(resultEntitySet,
self.canvasesEntitiesDict[matchingCanvas],
self.databaseEntities)
else:
# If this is the first clause, or'ing the empty initial resultEntitySet is what we want.
if sourceValue[2] is True:
resultEntitySet = self.canvasOrNot(resultEntitySet,
self.canvasesEntitiesDict[matchingCanvas],
self.databaseEntities)
else:
resultEntitySet = self.canvasOr(resultEntitySet,
self.canvasesEntitiesDict[matchingCanvas])
resultEntitySet = self.canvasOr(resultEntitySet,
self.canvasesEntitiesDict[matchingCanvas])
# Filter out all entities that do not contain at least one of the selected fields.
for entity in list(resultEntitySet):
validEntity = False
for field in fieldsToSelect:
if field in self.allEntities[entity].keys():
validEntity = True
break
validEntity = any(field in self.allEntities[entity].keys() for field in fieldsToSelect)
if not validEntity:
resultEntitySet.remove(entity)
self.allEntities.pop(entity)
@@ -190,30 +180,26 @@ class LQLQueryBuilder:
attributeRegex = re.compile(userInput1)
except re.error:
continue
for field in self.allEntityFields:
if attributeRegex.match(field):
matchingFields.append(field)
matchingFields.extend(field for field in self.allEntityFields if attributeRegex.match(field))
for matchingField in matchingFields:
entitiesToRemove = []
for entity in self.allEntities:
attributeKeyValue = str(self.allEntities[entity].get(matchingField))
if not self.checkVCHelper(conditionValue[2], isNot, attributeKeyValue, userInput2):
if conditionClause[0] == "AND":
entitiesToRemove.append(entity)
else:
if self.checkVCHelper(conditionValue[2], isNot, attributeKeyValue, userInput2):
uidsToSelect.add(entity)
elif conditionClause[0] == "AND":
entitiesToRemove.append(entity)
for entityToRemove in entitiesToRemove:
uidsToSelect.remove(entityToRemove)
elif conditionClause[1] == "Graph Condition":
entitiesToRemove = []
for entity in self.allEntities:
if not self.checkGCHelper(firstArgument, isNot, [entity] + conditionValue[1:]):
if conditionClause[0] == "AND":
entitiesToRemove.append(entity)
else:
if self.checkGCHelper(firstArgument, isNot, [entity] + conditionValue[1:]):
uidsToSelect.add(entity)
elif conditionClause[0] == "AND":
entitiesToRemove.append(entity)
for entityToRemove in entitiesToRemove:
uidsToSelect.remove(entityToRemove)
@@ -236,169 +222,119 @@ class LQLQueryBuilder:
return canvasSetA.union(allEntitiesSet.difference(canvasSetB))
def checkEQ(self, valueA: str, valueB: str):
if valueA == valueB:
return True
return False
return valueA == valueB
def checkContains(self, valueA: str, valueB: str):
if valueB in valueA:
return True
return False
return valueB in valueA
def checkStartsWith(self, valueA: str, valueB: str):
if valueA.startswith(valueB):
return True
return False
return valueA.startswith(valueB)
def checkEndsWith(self, valueA: str, valueB: str):
if valueA.endswith(valueB):
return True
return False
return valueA.endswith(valueB)
def checkRMatch(self, valueA: str, valueB: str):
try:
with contextlib.suppress(re.error):
valueMatch = re.compile(valueB)
if valueMatch.match(valueA):
return True
except re.error:
pass
return False
def checkVCHelper(self, checkType: str, isNot: bool, valueA: str, valueB: str):
returnVal = False
if checkType == "EQ":
returnVal = self.checkEQ(valueA, valueB)
elif checkType == "CONTAINS":
if checkType == "CONTAINS":
returnVal = self.checkContains(valueA, valueB)
elif checkType == "STARTSWITH":
returnVal = self.checkStartsWith(valueA, valueB)
elif checkType == "ENDSWITH":
returnVal = self.checkEndsWith(valueA, valueB)
elif checkType == "EQ":
returnVal = self.checkEQ(valueA, valueB)
elif checkType == "RMATCH":
returnVal = self.checkRMatch(valueA, valueB)
if isNot:
return not returnVal
return returnVal
elif checkType == "STARTSWITH":
returnVal = self.checkStartsWith(valueA, valueB)
return not returnVal if isNot else returnVal
def checkParentOf(self, valueA: str, valueB: str):
return self.databaseSnapshot.has_successor(valueA, valueB)
def checkAncestorOf(self, valueA: str, valueB: str):
try:
with contextlib.suppress(nx.NetworkXError):
if valueB in nx.descendants(self.databaseSnapshot, valueA):
return True
except nx.NetworkXError:
pass
return False
def checkChildOf(self, valueA: str, valueB: str):
return self.databaseSnapshot.has_predecessor(valueA, valueB)
def checkDescendantOf(self, valueA: str, valueB: str):
try:
with contextlib.suppress(nx.NetworkXError):
if valueB in nx.ancestors(self.databaseSnapshot, valueA):
return True
except nx.NetworkXError:
pass
return False
def checkNumChildren(self, valueA: str, valueB: str, valueC: int):
numChildren = len(list(self.databaseSnapshot.successors(valueA)))
returnValue = False
if valueB == "<":
if numChildren < valueC:
returnValue = True
elif valueB == "<=":
if numChildren <= valueC:
returnValue = True
elif valueB == ">":
if numChildren > valueC:
returnValue = True
elif valueB == ">=":
if numChildren >= valueC:
returnValue = True
elif valueB == "==":
if numChildren == valueC:
returnValue = True
return returnValue
return (valueB == "<" and numChildren < valueC) or \
(valueB == "<=" and numChildren <= valueC) or \
(valueB == ">" and numChildren > valueC) or \
(valueB == ">=" and numChildren >= valueC) or \
(valueB == "==" and numChildren == valueC)
def checkNumParents(self, valueA: str, valueB: str, valueC: int):
numParents = len(list(self.databaseSnapshot.predecessors(valueA)))
returnValue = False
if valueB == "<":
if numParents < valueC:
returnValue = True
elif valueB == "<=":
if numParents <= valueC:
returnValue = True
elif valueB == ">":
if numParents > valueC:
returnValue = True
elif valueB == ">=":
if numParents >= valueC:
returnValue = True
elif valueB == "==":
if numParents == valueC:
returnValue = True
return returnValue
return (valueB == "<" and numParents < valueC) or \
(valueB == "<=" and numParents <= valueC) or \
(valueB == ">" and numParents > valueC) or \
(valueB == ">=" and numParents >= valueC) or \
(valueB == "==" and numParents == valueC)
def checkConnectedTo(self, valueA: str, valueB: str):
try:
with contextlib.suppress(nx.NetworkXError):
if nx.has_path(self.databaseSnapshot, valueA, valueB):
return True
except nx.NetworkXError:
pass
return False
def checkIsolated(self, valueA: str):
try:
with contextlib.suppress(nx.NetworkXError):
if valueA in self.databaseSnapshot.nodes and nx.is_isolate(self.databaseSnapshot, valueA):
return True
except nx.NetworkXError:
pass
return False
def checkIsRoot(self, valueA: str):
try:
with contextlib.suppress(nx.NetworkXError):
if len(self.databaseSnapshot.in_edges(valueA)) == 0:
return True
except nx.NetworkXError:
pass
return False
def checkIsLeaf(self, valueA: str):
try:
with contextlib.suppress(nx.NetworkXError):
if len(self.databaseSnapshot.out_edges(valueA)) == 0:
return True
except nx.NetworkXError:
pass
return False
def checkGCHelper(self, checkType: str, isNot: bool, args: list):
returnVal = False
if checkType == "CHILDOF":
returnVal = self.checkChildOf(*args)
elif checkType == "DESCENDANTOF":
returnVal = self.checkDescendantOf(*args)
elif checkType == "PARENTOF":
returnVal = self.checkParentOf(*args)
elif checkType == "ANCESTOROF":
if checkType == "ANCESTOROF":
returnVal = self.checkAncestorOf(*args)
elif checkType == "NUMCHILDREN":
returnVal = self.checkNumChildren(*args)
elif checkType == "NUMPARENTS":
returnVal = self.checkNumParents(*args)
elif checkType == "CHILDOF":
returnVal = self.checkChildOf(*args)
elif checkType == "CONNECTEDTO":
returnVal = self.checkConnectedTo(*args)
elif checkType == "DESCENDANTOF":
returnVal = self.checkDescendantOf(*args)
elif checkType == "ISLEAF":
returnVal = self.checkIsLeaf(*args)
elif checkType == "ISOLATED":
returnVal = self.checkIsolated(*args)
elif checkType == "ISROOT":
returnVal = self.checkIsRoot(*args)
elif checkType == "ISLEAF":
returnVal = self.checkIsLeaf(*args)
if isNot:
return not returnVal
return returnVal
elif checkType == "NUMCHILDREN":
returnVal = self.checkNumChildren(*args)
elif checkType == "NUMPARENTS":
returnVal = self.checkNumParents(*args)
elif checkType == "PARENTOF":
returnVal = self.checkParentOf(*args)
return not returnVal if isNot else returnVal
def modifyNumify(self, valueA: str):
# Get the first number that shows up.
@@ -457,17 +393,15 @@ class LQLQueryBuilder:
for entity in self.allEntities:
for modifyField in modifyFields:
entityFieldValue = self.allEntities[entity].get(modifyField)
if entityFieldValue is None:
if entityFieldValue is None or modificationType not in ["UPPERCASE", "LOWERCASE", "NUMIFY"]:
newFieldValue = None
elif modificationType == "UPPERCASE":
newFieldValue = self.modifyUpperCase(entityFieldValue)
elif modificationType == "LOWERCASE":
newFieldValue = self.modifyLowerCase(entityFieldValue)
elif modificationType == "NUMIFY":
else:
newFieldValue = self.modifyNumify(entityFieldValue)
numifiedFields.add(modifyField)
else:
newFieldValue = None
if newFieldValue is not None:
modifiedUIDs.add(entity)
self.allEntities[entity][modifyField] = newFieldValue
@@ -476,19 +410,16 @@ class LQLQueryBuilder:
def parseQuery(self, selectClause: str, selectValue: Union[str, list], sourceClause: str,
sourceValues: Union[None, list], conditionClauses: Union[None, list],
modifyQueries: Union[list, None] = None) -> \
Optional[tuple[Optional[tuple[set, Union[set[Any], set[Union[str, Any]]]]],
Optional[tuple[set[Any], set[Any]]]]]:
modifyQueries: Union[list, None] = None) -> Optional[tuple[Optional[tuple[set, Union[set[Any], set[Union[str, Any]]]]],
Optional[tuple[set[Any], set[Any]]]]]:
if self.databaseSnapshot is None:
return None
returnValue = None
modifications = None
fieldsToSelect = self.parseSelect(selectClause, selectValue)
if fieldsToSelect:
entitiesToConsider = self.parseSource(sourceClause, sourceValues, fieldsToSelect)
if entitiesToConsider:
if fieldsToSelect := self.parseSelect(selectClause, selectValue):
if entitiesToConsider := self.parseSource(sourceClause, sourceValues, fieldsToSelect):
if conditionClauses:
entitiesToConsider = self.parseConditions(conditionClauses, entitiesToConsider)
returnValue = (entitiesToConsider, fieldsToSelect)

View File

@@ -63,7 +63,7 @@ class MessageHandler:
return message
# Set the severity level
def setSeverityLevel(self, level: int):
def setSeverityLevel(self, level):
currentLogLevel = self.linkScopeLogger.level
try:
level = int(level)

View File

@@ -10,7 +10,7 @@ from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.lib.units import cm
from reportlab.pdfgen import canvas
from reportlab.platypus import Paragraph, PageBreak, Image, Spacer, Table, LongTable, ParagraphAndImage
from reportlab.platypus import Paragraph, PageBreak, Image, Spacer, Table, ParagraphAndImage
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.pagesizes import LETTER, inch
@@ -33,7 +33,7 @@ class MyDocTemplate(BaseDocTemplate):
style = flowable.style.name
if style == 'Heading1':
self.notify('TOCEntry', (0, text, self.page))
if style == 'Heading2':
elif style == 'Heading2':
self.notify('TOCEntry', (1, text, self.page))
@@ -58,7 +58,7 @@ class ReportBuilder(canvas.Canvas):
canvas.Canvas.save(self)
def drawHeaderAndFooter(self, pageCount):
pageCountString = "Page %s of %s" % (self._pageNumber, pageCount)
pageCountString = f"Page {self._pageNumber} of {pageCount}"
self.saveState()
self.setStrokeColorRGB(0, 0, 0)
self.setLineWidth(0.5)
@@ -96,7 +96,7 @@ class PDFReport:
self.elements.append(spacer)
self.elements.append(report_subtitle)
spacer = Spacer(100, 200)
spacer = Spacer(100, 425)
self.elements.append(spacer)
date = datetime.now().strftime('%Y-%m-%d %H:%m:%S')
@@ -165,14 +165,16 @@ class PDFReport:
"""
text = Paragraph(notes, notesParagraph)
if type(entityImagePath) == Drawing:
if isinstance(entityImagePath, Drawing):
tbl = ReportImageAndParagraph(text, entityImagePath, side='left', xpad=10, ypad=0)
elif entityImagePath.endswith('.png') or entityImagePath.endswith('.jpg'):
entityImage = Image(entityImagePath, kind='proportional')
entityImage = Image(entityImagePath)
entityImage.preserveAspectRatio = True
entityImage.drawHeight = 2 * inch
entityImage.drawWidth = 2 * inch
tbl = ReportImageAndParagraph(text, entityImage, side='left', xpad=10, ypad=0)
else:
raise ValueError('Invalid Image Type')
links_subHeader = Paragraph("Entity Links", psSubHeaderText)
@@ -188,20 +190,20 @@ class PDFReport:
outgoing_data = [
['Outgoing Links'],
['Resolution Name', 'Child Entity', 'Date Created', 'Notes']]
index = 0
for link in outgoingLinks:
for index, link in enumerate(outgoingLinks):
resolutionText = link['Resolution']
childNodeText = outgoingNames[index]
linkNotesText = link['Notes']
linkName = "".join([resolutionText[counter:counter+24] + "\n"
dateCreatedText = link['Date Created']
linkName = "".join([resolutionText[counter:counter + 24] + "\n"
for counter in range(0, len(resolutionText), 24)])
childNode = "".join([childNodeText[counter:counter+24] + "\n"
for counter in range(0, len(childNodeText), 24)])
linkNotes = "".join([linkNotesText[counter:counter+24] + "\n"
for counter in range(0, len(linkNotesText), 24)])
dateCreated = link['Date Created']
childNode = "".join([childNodeText[counter:counter + 24] + "\n"
for counter in range(0, len(childNodeText), 24)])
linkNotes = "".join([linkNotesText[counter:counter + 24] + "\n"
for counter in range(0, len(linkNotesText), 24)])
dateCreated = "".join([dateCreatedText[counter:counter + 24] + "\n"
for counter in range(0, len(dateCreatedText), 24)])
outgoing_data.append([linkName, childNode, dateCreated, Paragraph(linkNotes)])
index += 1
outgoing_table = Table(data=outgoing_data, style=links_table_style, hAlign="CENTER",
colWidths=[140, 140, 140, 140])
spacer = Spacer(10, 10)
@@ -218,20 +220,20 @@ class PDFReport:
incoming_data = [
['Incoming Links'],
['Resolution Name', 'Parent Entity', 'Date Created', 'Notes']]
index = 0
for link in incomingLinks:
for index, link in enumerate(incomingLinks):
resolutionText = link['Resolution']
parentNodeText = incomingNames[index]
linkNotesText = link['Notes']
linkName = "".join([resolutionText[counter:counter+24] + "\n"
dateCreatedText = link['Date Created']
linkName = "".join([resolutionText[counter:counter + 24] + "\n"
for counter in range(0, len(resolutionText), 24)])
parentNode = "".join([parentNodeText[counter:counter+24] + "\n"
parentNode = "".join([parentNodeText[counter:counter + 24] + "\n"
for counter in range(0, len(parentNodeText), 24)])
linkNotes = "".join([linkNotesText[counter:counter+24] + "\n"
for counter in range(0, len(linkNotesText), 24)])
dateCreated = link['Date Created']
linkNotes = "".join([linkNotesText[counter:counter + 24] + "\n"
for counter in range(0, len(linkNotesText), 24)])
dateCreated = "".join([dateCreatedText[counter:counter + 24] + "\n"
for counter in range(0, len(dateCreatedText), 24)])
incoming_data.append([linkName, parentNode, dateCreated, Paragraph(linkNotes)])
index += 1
incoming_table = Table(data=incoming_data, style=links_table_style, hAlign="CENTER",
colWidths=[140, 140, 140, 140])
spacer = Spacer(10, 10)
@@ -260,7 +262,7 @@ class PDFReport:
for key in list(entity):
if key not in avoid_parsing_fields and key != 'Notes':
valueText = entity[key]
value = "".join([valueText[counter:counter+80] + "\n"
value = "".join([valueText[counter:counter + 80] + "\n"
for counter in range(0, len(valueText), 80)])
entity_data.append([Paragraph(key, tableParagraph), Paragraph(value, tableParagraph)])
elif key == 'Notes':
@@ -268,28 +270,6 @@ class PDFReport:
entity_notes = Paragraph(entity[key], notesParagraph)
entity_table = Table(data=entity_data, style=entity_table_style, hAlign="CENTER")
appendix_header = Paragraph(f"Appendix {appendixNumber}", notesHeader)
text = []
images = []
imangeNParagraph = []
for appendixDict in appendixDicts:
if appendixDict['AppendixEntityImage'] == '':
text.append(Paragraph(appendixDict['AppendixEntityNotes'], notesParagraph))
elif appendixDict['AppendixEntityNotes'] == '' and appendixDict['AppendixEntityImage'] != '':
img = Image(Path(appendixDict['AppendixEntityImage']), kind='proportional')
# img.preserveAspectRatio=True
img.drawHeight = 2 * inch
img.drawWidth = 2 * inch
img.hAlign = 'LEFT'
images.append(img)
elif appendixDict['AppendixEntityNotes'] != '' and appendixDict['AppendixEntityImage'] != '':
paragraph = appendixDict['AppendixEntityNotes']
img = Image(Path(appendixDict['AppendixEntityImage']), kind='proportional')
img.drawHeight = 2 * inch
img.drawWidth = 2 * inch
imangeNParagraph.append(
ReportImageAndParagraph(Paragraph(paragraph), img, side='left', xpad=10, ypad=0))
self.elements.append(entityTitle)
spacer = Spacer(20, 20)
self.elements.append(spacer)
@@ -314,11 +294,11 @@ class PDFReport:
pie = Pie()
pie.x = 150
pie.y = 65
pie.data = [int(len(incomingLinks)), int(len(outgoingLinks))]
pie.data = [len(incomingLinks), len(outgoingLinks)]
pie.sideLabels = 1
pie.labels = ['Incoming: ' + str(len(incomingLinks)), 'Outgoing: ' + str(len(outgoingLinks))]
pie.slices.strokeWidth = 1
if int(len(incomingLinks)) > int(len(outgoingLinks)):
if len(incomingLinks) > len(outgoingLinks):
pie.slices[0].popout = 5
else:
pie.slices[1].popout = 5
@@ -333,17 +313,29 @@ class PDFReport:
self.elements.append(entity_notes_header)
self.elements.append(entity_notes)
self.elements.append(spacer)
self.elements.append(appendix_header)
spacer = Spacer(20, 20)
self.elements.append(spacer)
for elementText in text:
self.elements.append(elementText)
for appendixIndex, appendixDict in enumerate(appendixDicts):
appendix_header = Paragraph(f"Entity Appendix {appendixIndex}", notesHeader)
self.elements.append(appendix_header)
self.elements.append(spacer)
for elementImage in images:
self.elements.append(elementImage)
self.elements.append(spacer)
for elementBoth in imangeNParagraph:
self.elements.append(elementBoth)
appendixImage = appendixDict['AppendixEntityImage']
appendixNotes = appendixDict['AppendixEntityNotes']
if appendixImage == '':
self.elements.append(Paragraph(appendixNotes, notesParagraph))
elif appendixNotes == '' and appendixImage != '':
img = Image(Path(appendixImage))
# img.preserveAspectRatio = True
img.drawHeight = 3 * inch
img.drawWidth = 3 * inch
self.elements.append(img)
elif appendixNotes != '' and appendixImage != '':
paragraph = appendixNotes
img = Image(Path(appendixImage))
img.drawHeight = 3 * inch
img.drawWidth = 3 * inch
self.elements.append(ReportImageAndParagraph(Paragraph(paragraph), img, side='left', xpad=10, ypad=0))
self.elements.append(spacer)
self.elements.append(PageBreak())
@@ -356,7 +348,7 @@ class PDFReport:
ParagraphStyle('Report', fontSize=9, justifyBreaks=1, alignment=TA_LEFT,
justifyLastLine=1)
img = Image(timeLineImage, kind='proportional')
img = Image(timeLineImage)
img.drawHeight = 1.3 * inch
img.drawWidth = 6 * inch
img.hAlign = 'LEFT'
@@ -429,11 +421,15 @@ class PDFReport:
head = f'Entity Report: {entityPrimaryField[i - 3]}'
self.nextPagesHeader(True, head)
self.entityPage(title=entityPrimaryField[i - 3], userNotes=entityListData[i][0].get('EntityNotes'),
self.entityPage(title=entityPrimaryField[i - 3],
userNotes=entityListData[i][0].get('EntityNotes'),
entityImagePath=imagePath,
appendixDicts=entityListData[i][1], outgoingLinks=outgoingLinks[i - 3],
appendixDicts=entityListData[i][1],
outgoingLinks=outgoingLinks[i - 3],
incomingLinks=incomingLinks[i - 3],
entity=entity[i - 3], incomingNames=incomingNames[i - 3], outgoingNames=outgoingNames[i - 3],
entity=entity[i - 3],
incomingNames=incomingNames[i - 3],
outgoingNames=outgoingNames[i - 3],
appendixNumber=i - 3)
# Graph report stuff disabled, at least for now.

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import contextlib
import importlib.util
import sys
from os import listdir
@@ -40,12 +41,10 @@ class ResolutionManager:
originTypes = resClassInst.originTypes
resultTypes = resClassInst.resultTypes
resolutionParameters = resClassInst.parameters
try:
with contextlib.suppress(AttributeError):
resolutionCategory = resClassInst.category
if not isinstance(resolutionCategory, str):
raise AttributeError()
except AttributeError:
pass
if self.resolutions.get(resolutionCategory) is None:
self.resolutions[resolutionCategory] = {}
self.resolutions[resolutionCategory][resNameString] = {'name': resNameString,
@@ -56,9 +55,9 @@ class ResolutionManager:
'category': resolutionCategory,
'resolution': resClass
}
self.messageHandler.info("Loaded Resolution: " + resNameString)
self.messageHandler.info(f"Loaded Resolution: {resNameString}")
except Exception as e:
self.messageHandler.error("Cannot load resolutions from " + str(directory) + "\n Info: " + repr(e))
self.messageHandler.error(f"Cannot load resolutions from {str(directory)}" + "\n Info: " + repr(e))
exceptionsCount += 1
if exceptionsCount > 3:
# Will not occur when loading modules with 3 or fewer resolutions, but that should be fine.
@@ -68,30 +67,24 @@ class ResolutionManager:
def getResolutionParameters(self, resolutionCategory, resolutionNameString):
resolutionsList = self.resolutions.get(resolutionCategory)
if resolutionsList is not None and resolutionNameString in resolutionsList:
parameters = self.resolutions[resolutionCategory][resolutionNameString]['parameters']
return parameters
return self.resolutions[resolutionCategory][resolutionNameString]['parameters']
return None
def getResolutionOriginTypes(self, resolutionCategoryNameString: str) -> Union[list, None]:
resolutionCategory, resolutionName = resolutionCategoryNameString.split('/', 1)
try:
with contextlib.suppress(TypeError):
if resolutionName in self.resolutions.get(resolutionCategory):
originTypes = self.resolutions[resolutionCategory][resolutionName]['originTypes']
if '*' in originTypes:
originTypes = self.mainWindow.RESOURCEHANDLER.getAllEntities()
return originTypes
except TypeError:
pass
return None
def getResolutionDescription(self, resolutionCategoryNameString: str) -> Union[str, None]:
resolutionCategory, resolutionName = resolutionCategoryNameString.split('/', 1)
try:
with contextlib.suppress(TypeError):
if resolutionName in self.resolutions.get(resolutionCategory):
resolutionDescription = self.resolutions[resolutionCategory][resolutionName].get('description', '')
return resolutionDescription
except TypeError:
pass
return self.resolutions[resolutionCategory][resolutionName].get('description', '')
return None
def loadResolutionsFromServer(self, serverRes) -> None:
@@ -130,9 +123,7 @@ class ResolutionManager:
return result
def getResolutionsInCategory(self, category) -> list:
if category in self.resolutions:
return list(self.resolutions[category])
return []
return list(self.resolutions[category]) if category in self.resolutions else []
def getAllResolutions(self) -> list:
categories = self.getResolutionCategories()
@@ -144,7 +135,7 @@ class ResolutionManager:
def executeResolution(self, resolutionCategoryNameString: str, resolutionEntitiesInput: list, parameters: dict,
resolutionUID: str):
resolutionCategory, resolutionName = resolutionCategoryNameString.split('/', 1)
try:
with contextlib.suppress(TypeError):
if resolutionName in self.resolutions.get(resolutionCategory):
if self.resolutions[resolutionCategory][resolutionName].get('resolution') == '':
# If resolution class does not exist locally, then assume it exists on the server.
@@ -153,10 +144,7 @@ class ResolutionManager:
# Returning a bool, so we know that the resolution is running on the server.
return True
resolutionClass = self.resolutions[resolutionCategory][resolutionName]['resolution']()
result = resolutionClass.resolution(resolutionEntitiesInput, parameters)
return result
except TypeError:
pass
return resolutionClass.resolution(resolutionEntitiesInput, parameters)
return None
def createMacro(self, resolutionList: list) -> str:
@@ -198,7 +186,7 @@ class ResolutionManager:
def save(self):
macroFilePath = self.getMacroFilePath()
macroFilePathTmp = macroFilePath.with_suffix(macroFilePath.suffix + '.tmp')
macroFilePathTmp = macroFilePath.with_suffix(f'{macroFilePath.suffix}.tmp')
with open(macroFilePathTmp, "wb") as macroFile:
dump(self.macros, macroFile)

View File

@@ -61,13 +61,12 @@ class ContainsPhrase:
counter += 1
if counter > 0:
returnResults.append([{'Phrase': primaryField + ' Contains Phrase: "' + searchPhrase +
f'" {counter} times',
returnResults.append([{'Phrase': f'{primaryField} Contains Phrase: "{searchPhrase}" {counter} times',
'Entity Type': 'Phrase',
'Notes': f'"{searchPhrase}" was found {counter} time(s)\n'
f'Offsets: Matches at character indices: '
f'{(", ".join(map(str, offsets)))}'},
{uid: {'Resolution': 'Contains Phrase ' + searchPhrase,
{uid: {'Resolution': f'Contains Phrase {searchPhrase}',
'Notes': ''}}])
return returnResults

View File

@@ -1,10 +1,12 @@
#!/usr/bin/env python3
import contextlib
from typing import Union
import networkx as nx
import re
from datetime import timezone
from defusedxml.ElementTree import parse
from datetime import datetime
from os import listdir
@@ -89,10 +91,7 @@ class ResourceHandler:
self.loadCoreEntities()
def getEntityCategories(self) -> list:
eList = []
for category in self.entityCategoryList:
eList.append(category)
return eList
return list(self.entityCategoryList)
def getAllEntityDetailsWithIconsInCategory(self, category) -> list:
eList = []
@@ -108,8 +107,7 @@ class ResourceHandler:
try:
for category in self.entityCategoryList:
if entityType in self.entityCategoryList[category]:
for attribute in self.entityCategoryList[category][entityType]['Attributes']:
aList.append(attribute)
aList.extend(iter(self.entityCategoryList[category][entityType]['Attributes']))
break
except KeyError:
self.messageHandler.error("Attempted to get attributes for "
@@ -121,16 +119,12 @@ class ResourceHandler:
"""
Get all Entity Types in the specified category.
"""
eList = []
for entity in self.entityCategoryList[category]:
eList.append(entity)
return eList
return list(self.entityCategoryList[category])
def getCategoryOfEntityType(self, entityType: Union[str, None]):
for category in self.entityCategoryList:
if entityType in self.entityCategoryList[category]:
return category
return None
return next((category for category in self.entityCategoryList
if entityType in self.entityCategoryList[category]),
None)
def getAllEntities(self) -> list:
"""
@@ -138,8 +132,7 @@ class ResourceHandler:
"""
eList = []
for category in self.getEntityCategories():
for entity in self.getAllEntitiesInCategory(category):
eList.append(entity)
eList.extend(iter(self.getAllEntitiesInCategory(category)))
return eList
def validateAttributesOfEntity(self, entityJSON: dict) -> (bool, str):
@@ -156,7 +149,7 @@ class ResourceHandler:
if attrValue is None or not self.runCheckOnAttribute(
attrValue,
self.entityCategoryList[entityCategory][entityType]['Attributes'][attribute][1]):
return 'Bad value: ' + str(attrValue)
return f'Bad value: {str(attrValue)}'
except Exception:
return False
return True
@@ -172,9 +165,7 @@ class ResourceHandler:
if attrCheck is None:
return False
result = attrCheck.findall(attribute)
if len(result) == 1:
return True
return False
return len(result) == 1
def addRecognisedEntityTypes(self, entityFile: Path) -> bool:
try:
@@ -191,32 +182,28 @@ class ResourceHandler:
try:
entityName = entity.tag.replace('_', ' ')
attributes = entity.find('Attributes')
attributesDict = {}
primaryCount = 0
attributesDict = {}
for attribute in list(attributes):
attributeName = attribute.text
defaultValue = attribute.attrib['default']
valueCheck = attribute.attrib['check']
isPrimary = True if attribute.attrib['primary'] == 'True' else False
isPrimary = attribute.attrib['primary'] == 'True'
if isPrimary:
if primaryCount > 0:
raise AttributeError('Malformed Entity: ' + entityName + ' - too many primary fields')
raise AttributeError(f'Malformed Entity: {entityName} - too many primary fields')
else:
primaryCount += 1
if self.runCheckOnAttribute(defaultValue, valueCheck):
attributesDict[attributeName] = [attribute.attrib['default'], attribute.attrib['check'],
isPrimary]
else:
raise AttributeError('Malformed Entity: ' + entityName + ' - default values do not conform to '
'their corresponding checks.')
if not self.runCheckOnAttribute(defaultValue, valueCheck):
raise AttributeError(f'Malformed Entity: {entityName} - default values do not pass their '
f'corresponding checks.')
attributeName = attribute.text
attributesDict[attributeName] = [attribute.attrib['default'], attribute.attrib['check'], isPrimary]
if primaryCount != 1:
raise AttributeError('Malformed Entity: ' + entityName + ' - invalid number of primary fields '
'specified.')
raise AttributeError(f'Malformed Entity: {entityName} - invalid number of primary fields '
f'specified.')
icon = entity.find('Icon')
if icon is not None:
icon = icon.text.strip()
elif icon is None or icon == '':
icon = 'Default.svg'
icon = icon.text.strip() if icon is not None else 'Default.svg'
if self.entityCategoryList.get(category) is None:
self.entityCategoryList[category] = {}
self.entityCategoryList[category][entityName] = {
@@ -224,7 +211,7 @@ class ResourceHandler:
'Icon': str(Path(self.mainWindow.SETTINGS.value("Program/BaseDir")) / "Resources" / "Icons" / icon)}
except (KeyError, AttributeError) as err:
# Ignore malformed entities
self.messageHandler.error('Error: ' + str(err), popUp=False)
self.messageHandler.error(f'Error: {str(err)}', popUp=False)
continue
return True
@@ -253,8 +240,7 @@ class ResourceHandler:
eJson[attribute] = self.entityCategoryList[category][entityType]['Attributes'][attribute][0]
break
except KeyError:
self.messageHandler.error("Attempted to get attributes for "
"malformed entity type: " + str(entityType), True)
self.messageHandler.error(f"Attempted to get attributes for malformed entity type: {entityType}", True)
return None
eJson['Entity Type'] = entityType
eJson['Date Created'] = None
@@ -269,7 +255,7 @@ class ResourceHandler:
if value is not None and value != '':
eJson[key] = value
utcNow = datetime.isoformat(datetime.utcnow())
utcNow = datetime.isoformat(datetime.now(timezone.utc))
if eJson['Date Created'] is None:
eJson['Date Created'] = utcNow
else:
@@ -291,8 +277,8 @@ class ResourceHandler:
if self.entityCategoryList[category][entityType]['Attributes'][attribute][2]:
return attribute
except KeyError:
self.messageHandler.error("Attempted to get primary attribute for "
"malformed entity type: " + str(entityType), True)
self.messageHandler.error(f"Attempted to get primary attribute for malformed entity type: {entityType}",
True)
return None
def getBareBonesEntityJson(self, entityType: str) -> Union[dict, None]:
@@ -304,8 +290,7 @@ class ResourceHandler:
eJson[attribute] = self.entityCategoryList[category][entityType]['Attributes'][attribute][0]
break
except KeyError:
self.messageHandler.error("Attempted to get attributes for "
"malformed entity type: " + str(entityType), True)
self.messageHandler.error(f"Attempted to get attributes for malformed entity type: {entityType}", True)
return None
eJson['Entity Type'] = entityType
@@ -318,7 +303,7 @@ class ResourceHandler:
except KeyError:
return None
utcNow = datetime.isoformat(datetime.utcnow())
utcNow = datetime.isoformat(datetime.now(timezone.utc))
linkJson['Resolution'] = str(jsonData.get('Resolution')) # This way, if it is None, it is cast to a string.
linkJson['Date Created'] = jsonData.get('Date Created')
# Make sure that dates are always in ISO format.
@@ -334,8 +319,8 @@ class ResourceHandler:
# Transfer all values from jsonData to linkJson, but preserve the values and order of linkJson for existing
# keys.
jsonData.update(linkJson)
linkJson.update(jsonData)
jsonData |= linkJson
linkJson |= jsonData
return linkJson
@@ -354,8 +339,7 @@ class ResourceHandler:
finally:
with open(picture, 'rb') as pictureFile:
pictureContents = pictureFile.read()
pictureByteArray = QByteArray(pictureContents)
return pictureByteArray
return QByteArray(pictureContents)
def getLinkPicture(self):
picture = Path(self.mainWindow.SETTINGS.value("Program/BaseDir")) / "Resources" / "Icons" / "Resolution.png"
@@ -370,11 +354,8 @@ class ResourceHandler:
for nodeKey in graph.nodes:
# Dereference the original dict, so we don't actually convert its icon to data.
nodes[nodeKey] = dict(graph.nodes.get(nodeKey))
try:
with contextlib.suppress(KeyError):
nodes[nodeKey]['Icon'] = nodes[nodeKey]['Icon'].toBase64().data()
except KeyError:
pass
edges = {edgeKey: graph.edges.get(edgeKey) for edgeKey in graph.edges}
return nodes, edges
@@ -383,22 +364,16 @@ class ResourceHandler:
for nodeKey in graph.nodes:
# Dereference the original dict, so we don't actually convert its icon to data.
nodes[nodeKey] = dict(graph.nodes.get(nodeKey))
try:
with contextlib.suppress(KeyError):
nodes[nodeKey]['Icon'] = nodes[nodeKey]['Icon'].toBase64().data()
except KeyError:
pass
edges = {str(edgeKey): graph.edges.get(edgeKey) for edgeKey in graph.edges}
return nodes, edges
def reconstructGraphFromString(self, graphString: str) -> tuple:
nodes, edges = literal_eval(graphString)
for node in nodes:
try:
with contextlib.suppress(KeyError):
nodes[node]['Icon'] = QByteArray(b64decode(nodes[node]['Icon']))
except KeyError:
pass
return nodes, edges
def reconstructGraphFullFromFile(self, graphNodesAndEdges: Union[tuple, list]) -> nx.DiGraph:
@@ -406,10 +381,8 @@ class ResourceHandler:
graphNodes = graphNodesAndEdges[0]
graphEdges = graphNodesAndEdges[1]
for node in graphNodes:
try:
with contextlib.suppress(KeyError):
graphNodes[node]['Icon'] = QByteArray(b64decode(graphNodes[node]['Icon']))
except KeyError:
pass
returnGraph.add_node(node, **graphNodes[node])
for edge in graphEdges:
@@ -476,11 +449,7 @@ class SingleChoicePropertyInput(QtWidgets.QGroupBox):
vboxLayout.addWidget(radioButton)
def getValue(self):
for option in self.options:
if option.isChecked():
return option.text()
return ''
return next((option.text() for option in self.options if option.isChecked()), '')
class MultiChoicePropertyInput(QtWidgets.QGroupBox):
@@ -507,12 +476,7 @@ class MultiChoicePropertyInput(QtWidgets.QGroupBox):
vboxLayout.addWidget(checkBox)
def getValue(self):
valuesSelected = []
for option in self.options:
if option.isChecked():
valuesSelected.append(option.text())
return valuesSelected
return [option.text() for option in self.options if option.isChecked()]
class MinSizeStackedLayout(QtWidgets.QStackedLayout):
@@ -550,12 +514,11 @@ class RichNotesEditor(QtWidgets.QTextBrowser):
self.textFormat = self.currentCharFormat()
def startEditing(self) -> None:
if self.allowEditing:
if self.isReadOnly():
# Reset char format to plain text.
self.setCurrentCharFormat(self.textFormat)
self.setPlainText(self.contents)
self.setReadOnly(False)
if self.allowEditing and self.isReadOnly():
# Reset char format to plain text.
self.setCurrentCharFormat(self.textFormat)
self.setPlainText(self.contents)
self.setReadOnly(False)
def stopEditing(self) -> None:
if not self.isReadOnly():
@@ -573,15 +536,13 @@ class RichNotesEditor(QtWidgets.QTextBrowser):
def mousePressEvent(self, ev: QtGui.QMouseEvent) -> None:
potentialLink = self.anchorAt(ev.pos())
if not potentialLink:
if ev.button() == QtGui.Qt.LeftButton:
self.startEditing()
if not potentialLink and ev.button() == QtGui.Qt.LeftButton:
self.startEditing()
super(RichNotesEditor, self).mousePressEvent(ev)
def focusOutEvent(self, ev: QtGui.QFocusEvent) -> None:
if not self.underMouse():
if self.isActiveWindow():
self.stopEditing()
if not self.underMouse() and self.isActiveWindow():
self.stopEditing()
super(RichNotesEditor, self).focusOutEvent(ev)
def doSetSource(self, name: Union[QUrl, str], resourceType: QtGui.QTextDocument.ResourceType = ...) -> None:

View File

@@ -63,10 +63,9 @@ class SettingsObject(dict):
# Save and then move to prevent corruption if the application closes unexpectedly.
actualSavePath = str(Path(self.value("Project/BaseDir")).joinpath(self.value("Project/Name") + ".linkscope"))
if is_path_exists_or_creatable_portable(actualSavePath):
tempSavePath = actualSavePath + '.tmp'
projectFile = open(tempSavePath, "wb")
dump(self, projectFile)
projectFile.close()
tempSavePath = f'{actualSavePath}.tmp'
with open(tempSavePath, "wb") as projectFile:
dump(self, projectFile)
move(tempSavePath, actualSavePath)
def load(self, savedDict: dict):

View File

@@ -31,11 +31,7 @@ class URLManager:
Takes a list of QUrls and returns a list of entities that correspond
to them.
"""
returnValue = []
for url in urls:
returnValue.append(self.handleURL(url))
return returnValue
return [self.handleURL(url) for url in urls]
def handleURL(self, url):
parsedURL = urlparse(url.toString())
@@ -66,10 +62,8 @@ class URLManager:
fileType = magic.from_file(urlPathString, mime=True).split('/')[0]
if fileType == "video":
entityJson = {"Video Name": urlName, "File Path": savePathString, "Entity Type": "Video"}
pass
elif fileType == "image":
entityJson = {"Image Name": urlName, "File Path": savePathString, "Entity Type": "Image"}
pass
else:
entityJson = {"Document Name": urlName, "File Path": savePathString, "Entity Type": "Document"}
return entityJson
@@ -85,12 +79,12 @@ class URLManager:
savePath = valuePath.relative_to(projectFilesPath)
except ValueError:
# The file selected is not in Project Files
createSymlink = True if self.mainWindow.SETTINGS.value("Project/Symlink or Copy Materials") == "Symlink" \
else False
createSymlink = self.mainWindow.SETTINGS.value("Project/Symlink or Copy Materials") == "Symlink"
projectFilesPath = Path(self.mainWindow.SETTINGS.value("Project/FilesDir"))
# Create a unique path in Project Files
saveHash = hexlify(sha3_512(str(urlPath).encode()).digest()).decode()[:16] # nosec
savePath = projectFilesPath / (saveHash + '|' + urlPath.name)
savePath = projectFilesPath / f'{saveHash}|{urlPath.name}'
if createSymlink:
symlink(urlPath, savePath)
@@ -103,8 +97,6 @@ class URLManager:
def handleRemoteURL(self, url):
stringURL = url.toString()
if self.mainWindow.RESOURCEHANDLER.runCheckOnAttribute(stringURL, 'Onion'):
entity = {'Entity Type': 'Onion Website', 'Onion URL': stringURL}
else:
entity = {'Entity Type': 'Website', 'URL': stringURL}
return entity
return {'Entity Type': 'Onion Website', 'Onion URL': stringURL} \
if self.mainWindow.RESOURCEHANDLER.runCheckOnAttribute(stringURL, 'Onion') \
else {'Entity Type': 'Website', 'URL': stringURL}

View File

@@ -747,16 +747,11 @@ class InstallWizard(QtWidgets.QWizard):
if self.currentId() == 3:
return 6
if self.currentId() == 4:
if self.currentOS == 'Windows' and not self.graphvizExists:
return 2
else:
return 5
return 2 if self.currentOS == 'Windows' and not self.graphvizExists else 5
if self.currentId() == 5:
return 6
if self.currentId() == 6:
return -1
# If we lose the user somehow, return them to the last page.
return 6
return -1 if self.currentId() == 6 else 6
def __init__(self):
super(InstallWizard, self).__init__()
@@ -775,11 +770,10 @@ class InstallWizard(QtWidgets.QWizard):
try:
if ctypes.windll.shell32.IsUserAnAdmin() == 0:
raise ValueError('Not an admin')
else:
QtWidgets.QMessageBox.critical(self, 'Elevated Privileges Detected',
'The Installer must be ran as a normal user, not as an '
'Administrator. Please run the Installer normally.')
sys.exit(-2)
QtWidgets.QMessageBox.critical(self, 'Elevated Privileges Detected',
'The Installer must be ran as a normal user, not as an '
'Administrator. Please run the Installer normally.')
sys.exit(-2)
except Exception:
self.desktopShortcutPath = Path.home() / 'Desktop' / 'LinkScope.lnk'
@@ -790,7 +784,7 @@ class InstallWizard(QtWidgets.QWizard):
for textPart in releasesParts:
if 'Windows10-x64.7z' in textPart:
urlPart = textPart.split('"')[1].strip()
self.downloadURL = 'https://github.com' + urlPart
self.downloadURL = f'https://github.com{urlPart}'
break
newArgs = ['"' + str(self.desktopShortcutPath) + '"', str(self.graphvizExists),
@@ -815,7 +809,7 @@ class InstallWizard(QtWidgets.QWizard):
for textPart in releasesParts:
if 'Ubuntu-x64.7z' in textPart:
urlPart = textPart.split('"')[1].strip()
self.downloadURL = 'https://github.com' + urlPart
self.downloadURL = f'https://github.com{urlPart}'
break
# No need to wrap these in quotes
@@ -841,10 +835,6 @@ class InstallWizard(QtWidgets.QWizard):
subprocess.run(
['dbus-launch', 'gio', 'set', str(self.desktopShortcutPath), "metadata::trusted",
'true'])
# subprocess.run(
# ["dbus-send --type=method_call --dest=org.gnome.Shell /org/gnome/Shell "
# "org.gnome.Shell.Eval string:'global.reexec_self()'"],
# shell=True)
sys.exit(0)
else:
sys.exit(-1)
@@ -855,7 +845,7 @@ class InstallWizard(QtWidgets.QWizard):
sys.exit(-5)
else:
self.desktopShortcutPath = Path(sys.argv[1])
self.graphvizExists = True if sys.argv[2] == 'True' else False
self.graphvizExists = sys.argv[2] == 'True'
self.baseSoftwarePath = Path(sys.argv[3])
self.executablePath = Path(sys.argv[4])
self.downloadURL = sys.argv[5]
@@ -910,18 +900,19 @@ class InstallWizard(QtWidgets.QWizard):
self.show()
def removeFileHelper(self, pathToRemove: Path):
if pathToRemove.exists():
pathToRemove.chmod(0o777)
if pathToRemove.is_dir():
for dirpath, dirnames, filenames in os.walk(pathToRemove):
Path(dirpath).chmod(0o777)
filenames.extend(dirnames)
for filename in filenames:
filePath = Path(dirpath) / filename
filePath.chmod(0o777)
shutil.rmtree(pathToRemove)
else:
pathToRemove.unlink(missing_ok=True)
if not pathToRemove.exists():
return
pathToRemove.chmod(0o777)
if pathToRemove.is_dir():
for dirpath, dirnames, filenames in os.walk(pathToRemove):
Path(dirpath).chmod(0o777)
filenames.extend(dirnames)
for filename in filenames:
filePath = Path(dirpath) / filename
filePath.chmod(0o777)
shutil.rmtree(pathToRemove)
else:
pathToRemove.unlink(missing_ok=True)
def createShortcut(self):
self.removeFileHelper(self.desktopShortcutPath)
@@ -955,11 +946,9 @@ class InstallWizard(QtWidgets.QWizard):
def downloadGraphviz(self):
graphVizPage = requests.get('https://graphviz.org/download/')
graphVizParts = graphVizPage.text.split('\n')
graphVizDownloadLink = ""
for chunk in graphVizParts:
if '(64-bit) EXE installer' in chunk:
graphVizDownloadLink = chunk.split('"')[1]
break
graphVizDownloadLink = next((chunk.split('"')[1] for chunk in graphVizParts
if '(64-bit) EXE installer' in chunk), "")
if not isinstance(graphVizDownloadLink, str) or graphVizDownloadLink == "":
raise ValueError('Cannot install GraphViz: Failed to locate the latest version of the GraphViz installer.')
graphVizInstallerTemp = tempfile.mkstemp()
@@ -975,25 +964,24 @@ class InstallWizard(QtWidgets.QWizard):
def install(self):
# Assumes we have superuser privileges.
if self.currentOS == 'Linux':
# Redundant since we always try to update and install, but it's good coding practice.
if not self.graphvizExists:
# No need to check if this succeeds - if there are any issues with installation, we will throw
# an error on the install command.
subprocess.run(['apt', 'update'])
command = subprocess.run(['apt', 'install', 'p7zip-full', 'libopengl0', 'graphviz', 'libmagic1', '-y'])
if command.returncode != 0:
raise ValueError('Installing new packages failed, cannot continue installation.')
self.removeFileHelper(self.appPath)
with open(self.appPath, 'w') as desktopApplicationFile:
desktopApplicationFile.write(LINUX_DESKTOP_FILE_ENTRY)
# Mark desktop file as executable
self.appPath.chmod(self.appPath.stat().st_mode | 0o111)
elif self.currentOS == 'Windows':
if self.currentOS != 'Linux':
# Assume the user has installed / will install Graphviz.
# We don't actually need to do anything here. Maybe in the future, register application in registry?
pass
return
# Redundant since we always try to update and install, but it's good coding practice.
if not self.graphvizExists:
# No need to check if this succeeds - if there are any issues with installation, we will throw
# an error on the install command.
subprocess.run(['apt', 'update'])
command = subprocess.run(['apt', 'install', 'p7zip-full', 'libopengl0', 'graphviz', 'libmagic1', '-y'])
if command.returncode != 0:
raise ValueError('Installing new packages failed, cannot continue installation.')
self.removeFileHelper(self.appPath)
with open(self.appPath, 'w') as desktopApplicationFile:
desktopApplicationFile.write(LINUX_DESKTOP_FILE_ENTRY)
# Mark desktop file as executable
self.appPath.chmod(self.appPath.stat().st_mode | 0o111)
class IntroInstallUninstallPage(QtWidgets.QWizardPage):
@@ -1077,11 +1065,10 @@ class LinkScopeInstallLatestPage(QtWidgets.QWizardPage):
self.installLabel.setText('Installation failed, click "Commit" to proceed.')
def validatePage(self) -> bool:
if self.progressBar.value() != 10:
if not self.processStarted:
self.doStuff()
else:
if self.progressBar.value() == 10:
return True
if not self.processStarted:
self.doStuff()
return False
def __init__(self):

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3
# Load modules
import contextlib
import re
import sys
import tempfile
@@ -11,6 +12,7 @@ import itertools
import threading
import networkx as nx
from svglib.svglib import svg2rlg
from ast import literal_eval
from uuid import uuid4
from shutil import move
@@ -66,7 +68,7 @@ class MainWindow(QtWidgets.QMainWindow):
newChildren = [childUID for childUID in list(entityJSON['Child UIDs'])
if childUID not in targetCanvas.sceneGraph.nodes]
# Don't create the entity if all the nodes in it already exist on the target canvas.
if len(newChildren) == 0:
if not newChildren:
return None
newEntity = self.LENTDB.addEntity(
{'Group Name': entityJSON['Group Name'] + ' Copy',
@@ -127,7 +129,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.setStatus("Project Saved.", 3000)
self.MESSAGEHANDLER.info('Project Saved')
except Exception as e:
errorMessage = "Could not Save Project: " + str(repr(e))
errorMessage = f"Could not Save Project: {repr(e)}"
self.MESSAGEHANDLER.error(errorMessage, exc_info=True)
self.setStatus("Failed Saving Project.", 3000)
self.MESSAGEHANDLER.info("Failed Saving Project " + self.SETTINGS.value("Project/Name", 'Untitled'))
@@ -208,7 +210,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.setWindowTitle("LinkScope - " + self.SETTINGS.get('Project/Name', 'Untitled'))
self.saveProject()
self.setStatus('Project Saved As: ' + newProjectPath.name)
self.setStatus(f'Project Saved As: {newProjectPath.name}')
# https://networkx.org/documentation/stable/reference/readwrite/graphml.html
def exportCanvasToGraphML(self):
@@ -229,7 +231,7 @@ class MainWindow(QtWidgets.QMainWindow):
nx.write_graphml(currentCanvasGraph, filePath)
self.setStatus('Canvas exported successfully.')
except Exception as exc:
self.MESSAGEHANDLER.error("Could not export canvas to file: " + str(exc), popUp=True)
self.MESSAGEHANDLER.error(f"Could not export canvas to file: {str(exc)}", popUp=True)
self.setStatus('Canvas export failed.')
def importCanvasFromGraphML(self):
@@ -252,7 +254,7 @@ class MainWindow(QtWidgets.QMainWindow):
nodesToReadFirst = [entity for entity in nodesToReadFirst if entity is not None and
entity['Entity Type'] == 'EntityGroup']
for entity in nodesToReadFirst:
# Create new group entity so we don't mess with the contents of the original.
# Create new group entity, so we don't mess with the contents of the original.
if entity['uid'] not in currentScene.sceneGraph.nodes:
newGroupEntity = self.copyGroupEntity(entity['uid'], currentScene)
if newGroupEntity is not None:
@@ -271,14 +273,13 @@ class MainWindow(QtWidgets.QMainWindow):
"do not exist in the database.", popUp=True)
self.setStatus('Canvas import aborted.')
except Exception as exc:
self.MESSAGEHANDLER.error("Cannot import canvas: " + str(exc), popUp=True)
self.MESSAGEHANDLER.error(f"Cannot import canvas: {str(exc)}", popUp=True)
self.setStatus('Canvas import failed.')
def exportDatabaseToGraphML(self):
# Need to create a new database to remove the icons
self.LENTDB.dbLock.acquire()
currentDatabase = self.LENTDB.database.copy()
self.LENTDB.dbLock.release()
with self.LENTDB.dbLock:
currentDatabase = self.LENTDB.database.copy()
for node in currentDatabase.nodes:
# Remove icons. Will reset custom icons to default, but saves space.
@@ -305,7 +306,7 @@ class MainWindow(QtWidgets.QMainWindow):
nx.write_graphml(currentDatabase, filePath)
self.setStatus('Database exported successfully.')
except Exception as exc:
self.MESSAGEHANDLER.error("Could not export database to file: " + str(exc), popUp=True)
self.MESSAGEHANDLER.error(f"Could not export database to file: {str(exc)}", popUp=True)
self.setStatus('Database export failed.')
def importDatabaseFromGraphML(self):
@@ -323,18 +324,22 @@ class MainWindow(QtWidgets.QMainWindow):
read_graphml_nodes = {key: read_graphml.nodes[key] for key in read_graphml.nodes}
read_graphml_edges = {key: read_graphml.edges[key] for key in read_graphml.edges}
for node in read_graphml_nodes:
read_graphml_nodes[node]['Icon'] = self.RESOURCEHANDLER.getEntityDefaultPicture(
read_graphml_nodes[node]['Entity Type'])
# Make sure that all the necessary values are assigned.
# This will throw an exception if the user imports an entity without a type.
read_graphml_nodes[node] = self.RESOURCEHANDLER.getEntityJson(
read_graphml_nodes[node]['Entity Type'], read_graphml_nodes[node])
if read_graphml_nodes[node].get('Child UIDs'):
read_graphml_nodes[node]['Child UIDs'] = literal_eval(read_graphml_nodes[node]['Child UIDs'])
for edge in read_graphml_edges:
read_graphml_edges[edge]['uid'] = literal_eval(read_graphml_edges[edge]['uid'])
# Make sure that all the necessary values are assigned.
read_graphml_edges[edge] = self.RESOURCEHANDLER.getLinkJson(read_graphml_edges[edge])
self.LENTDB.mergeDatabases(read_graphml_nodes, read_graphml_edges, fromServer=False)
self.dockbarOne.existingEntitiesPalette.loadEntities()
self.LENTDB.resetTimeline()
self.setStatus('Database imported successfully.')
except Exception as exc:
self.MESSAGEHANDLER.error("Could not import database from file: " + str(exc), popUp=True)
self.MESSAGEHANDLER.error(f"Could not import database from file: {str(exc)}", popUp=True)
self.setStatus('Database import failed.')
def generateReport(self):
@@ -382,26 +387,23 @@ class MainWindow(QtWidgets.QMainWindow):
self.SETTINGS.setValue("Project/Name", newName)
move(oldBaseDir, self.SETTINGS.value("Project/BaseDir"))
oldProjectFile = newBaseDir.joinpath(oldName + '.linkscope')
oldProjectFile = newBaseDir.joinpath(f'{oldName}.linkscope')
oldProjectFile.unlink(missing_ok=True)
self.setWindowTitle("LinkScope - " + self.SETTINGS.get('Project/Name', 'Untitled'))
self.saveProject()
statusMessage = 'Project Renamed to: ' + newName
statusMessage = f'Project Renamed to: {newName}'
self.setStatus(statusMessage)
self.MESSAGEHANDLER.info(statusMessage)
def addCanvas(self) -> None:
# Create or open canvas
connected = False
if self.FCOM.isConnected():
connected = True
connected = self.FCOM.isConnected()
with self.syncedCanvasesLock:
availableSyncedCanvases = self.syncedCanvases
newCanvasPopup = CreateOrOpenCanvas(self, connected, availableSyncedCanvases)
if newCanvasPopup.exec():
self.MESSAGEHANDLER.info("New Canvas added: " + newCanvasPopup.canvasName)
self.MESSAGEHANDLER.info(f"New Canvas added: {newCanvasPopup.canvasName}")
def toggleWorldDoc(self) -> None:
if self.centralWidget() is not None:
@@ -430,7 +432,7 @@ class MainWindow(QtWidgets.QMainWindow):
def deleteSpecificEntity(self, itemUID: str) -> None:
self.centralWidget().tabbedPane.nodeRemoveAllHelper(itemUID)
self.LENTDB.removeEntity(itemUID)
self.MESSAGEHANDLER.info("Deleted node: " + itemUID)
self.MESSAGEHANDLER.info(f"Deleted node: {itemUID}")
def deleteSpecificLink(self, linkUIDs: set) -> None:
"""
@@ -446,7 +448,7 @@ class MainWindow(QtWidgets.QMainWindow):
scene.removeUIDFromLink(linkUID)
for linkUID in linkUIDs:
self.LENTDB.removeLink(linkUID)
self.MESSAGEHANDLER.info("Deleted link: " + str(linkUID))
self.MESSAGEHANDLER.info(f"Deleted link: {str(linkUID)}")
def setGroupAppendMode(self, enable: bool) -> None:
if self.centralWidget().tabbedPane.getCurrentScene().linking:
@@ -536,7 +538,7 @@ class MainWindow(QtWidgets.QMainWindow):
shortestPath = None
if shortestPath is None:
messagePathNotFound = 'No path found connecting the selected nodes: ' + str(endPoints)
messagePathNotFound = f'No path found connecting the selected nodes: {endPoints}'
self.setStatus(messagePathNotFound)
self.MESSAGEHANDLER.info(messagePathNotFound, popUp=True)
else:
@@ -546,7 +548,7 @@ class MainWindow(QtWidgets.QMainWindow):
if itemUID in shortestPath:
currentScene.nodesDict[itemUID].setSelected(True)
linksToSelect = [(a, b) for a, b in zip(shortestPath, shortestPath[1:])]
linksToSelect = list(zip(shortestPath, shortestPath[1:]))
for linkItem in [link for link in currentScene.items() if isinstance(link, BaseConnector)]:
if linkItem.uid.intersection(linksToSelect):
linkItem.setSelected(True)
@@ -568,17 +570,17 @@ class MainWindow(QtWidgets.QMainWindow):
newCyclesThread = ExtractCyclesThread(tempGraph, endPoints, canvasName)
newCyclesThread.cyclesSignal.connect(self.extractCyclesResultHandler)
self.MESSAGEHANDLER.info('Extracting Cycles from Canvas: ' + canvasName)
self.MESSAGEHANDLER.info(f'Extracting Cycles from Canvas: {canvasName}')
newCyclesThread.start()
self.cycleExtractionThreads.append(newCyclesThread)
def extractCyclesResultHandler(self, results: list, canvasName: str) -> None:
if not results:
self.MESSAGEHANDLER.info('No Cycles in Canvas: ' + canvasName)
self.MESSAGEHANDLER.info(f'No Cycles in Canvas: {canvasName}')
else:
count = 0
while True:
newCanvasName = canvasName + ' Cycles #' + str(count)
newCanvasName = f'{canvasName} Cycles #{str(count)}'
if self.centralWidget().tabbedPane.addCanvas(newCanvasName):
break
else:
@@ -591,16 +593,14 @@ class MainWindow(QtWidgets.QMainWindow):
for node in nodesToAdd:
newCanvas.addNodeProgrammatic(node)
count = 1
entitiesAlreadyInGroups = set()
for group in groupsToMake:
for count, group in enumerate(groupsToMake, start=1):
newGroup = set()
for groupEntity in group:
if groupEntity not in entitiesAlreadyInGroups:
newGroup.add(groupEntity)
entitiesAlreadyInGroups.add(groupEntity)
newCanvas.groupItemsProgrammatic(newGroup, 'Group ' + str(count))
count += 1
newCanvas.groupItemsProgrammatic(newGroup, f'Group {str(count)}')
newCanvas.rearrangeGraph('circular')
for cycleThread in list(self.cycleExtractionThreads):
@@ -610,8 +610,7 @@ class MainWindow(QtWidgets.QMainWindow):
def findEntityOrLinkOnCanvas(self, regex: bool = False) -> None:
currentScene = self.centralWidget().tabbedPane.getCurrentScene()
currentUIDs = [item.uid for item in currentScene.items() if isinstance(item, BaseNode)
or isinstance(item, BaseConnector)]
currentUIDs = [item.uid for item in currentScene.items() if isinstance(item, (BaseNode, BaseConnector))]
entityPrimaryFields = {}
for uid in currentUIDs:
if isinstance(uid, str):
@@ -631,9 +630,9 @@ class MainWindow(QtWidgets.QMainWindow):
findPrompt = FindEntityOnCanvasDialog(list(entityPrimaryFields), regex)
if findPrompt.exec():
uidsToSelect = []
findText = findPrompt.findInput.text()
if findText != "":
uidsToSelect = []
try:
if regex:
expression = re.compile(findText)
@@ -650,7 +649,7 @@ class MainWindow(QtWidgets.QMainWindow):
currentScene.clearSelection()
for item in [linkOrEntity for linkOrEntity in currentScene.items()
if isinstance(linkOrEntity, BaseNode) or isinstance(linkOrEntity, BaseConnector)]:
if isinstance(linkOrEntity, (BaseNode, BaseConnector))]:
if str(item.uid) in uidsToSelect:
item.setSelected(True)
if len(uidsToSelect) == 1 and ',' not in uidsToSelect[0]:
@@ -677,10 +676,10 @@ class MainWindow(QtWidgets.QMainWindow):
findPrompt = FindEntityOfTypeOnCanvasDialog(entityTypesOnCanvas, regex)
if findPrompt.exec():
uidsToSelect = []
findText = findPrompt.findInput.text()
findType = findPrompt.typeInput.currentText()
if findText != "":
uidsToSelect = []
try:
if regex:
expression = re.compile(findText)
@@ -771,7 +770,7 @@ class MainWindow(QtWidgets.QMainWindow):
if isinstance(item, BaseNode)]
validEntityToSplit = [entity for entity in entityToSplit
if entity['Entity Type'] != 'EntityGroup']
if len(validEntityToSplit) == 0:
if not validEntityToSplit:
self.MESSAGEHANDLER.info('No valid entities to split selected! Please choose at least one non-Meta entity.',
popUp=True)
return
@@ -780,6 +779,7 @@ class MainWindow(QtWidgets.QMainWindow):
popUp=True)
return
# Continue only if a single entity is selected.
entityToSplit = validEntityToSplit[0]
entityToSplitPrimaryFieldKey = list(entityToSplit)[1]
entityToSplitUID = entityToSplit['uid']
@@ -792,14 +792,12 @@ class MainWindow(QtWidgets.QMainWindow):
for newEntityWithLinks in splitDialog.splitEntitiesWithLinks:
newEntity = {entityToSplitPrimaryFieldKey: newEntityWithLinks[0]}
for field in entityToSplit:
if field != 'uid' and field != entityToSplitPrimaryFieldKey:
if field not in ['uid', entityToSplitPrimaryFieldKey]:
newEntity[field] = entityToSplit[field]
newEntity = self.LENTDB.addEntity(newEntity)
for link in newEntityWithLinks[1]:
newLink = {}
for field in link:
newLink[field] = link[field]
newLink = {field: link[field] for field in link}
if newLink['uid'][0] == entityToSplitUID:
newLink['uid'] = (newEntity['uid'], newLink['uid'][1])
else:
@@ -821,9 +819,7 @@ class MainWindow(QtWidgets.QMainWindow):
def editProjectSettings(self) -> None:
settingsDialog = ProjectEditDialog(self.SETTINGS)
settingsConfirm = settingsDialog.exec()
if settingsConfirm:
if settingsDialog.exec():
# Save new settings
newSettings = settingsDialog.newSettings
for key in newSettings:
@@ -833,25 +829,19 @@ class MainWindow(QtWidgets.QMainWindow):
self.SETTINGS.pop(key)
elif newSettingValue[0] != '':
# Do not allow blank settings.
if key == 'Project/Resolution Result Grouping Threshold' or \
key == 'Project/Number of Answers Returned' or \
key == 'Project/Question Answering Retriever Value' or \
key == 'Project/Question Answering Reader Value':
try:
if key in ['Project/Resolution Result Grouping Threshold', 'Project/Number of Answers Returned',
'Project/Question Answering Retriever Value', 'Project/Question Answering Reader Value']:
with contextlib.suppress(ValueError):
int(newSettingValue[1])
self.SETTINGS.setValue(key, newSettingValue[0])
except ValueError:
pass
elif newSettingValue[0] == 'Copy' or newSettingValue[0] == 'Symlink':
elif newSettingValue[0] in ['Copy', 'Symlink']:
self.SETTINGS.setValue(key, newSettingValue[0])
self.saveProject()
def editResolutionsSettings(self) -> None:
settingsDialog = ResolutionsEditDialog(self.SETTINGS)
settingsConfirm = settingsDialog.exec()
if settingsConfirm:
if settingsDialog.exec():
# Save new settings
newSettings = settingsDialog.newSettings
for key in newSettings:
@@ -867,9 +857,7 @@ class MainWindow(QtWidgets.QMainWindow):
def editLogSettings(self) -> None:
settingsDialog = LoggingSettingsDialog(self.SETTINGS)
settingsConfirm = settingsDialog.exec()
if settingsConfirm:
if settingsDialog.exec():
# Save new settings
newSettings = settingsDialog.newSettings
for key in newSettings:
@@ -887,9 +875,7 @@ class MainWindow(QtWidgets.QMainWindow):
def editProgramSettings(self) -> None:
settingsDialog = ProgramEditDialog(self.SETTINGS)
settingsConfirm = settingsDialog.exec()
if settingsConfirm:
if settingsDialog.exec():
# Save new settings
newSettings = settingsDialog.newSettings
for key in newSettings:
@@ -905,30 +891,22 @@ class MainWindow(QtWidgets.QMainWindow):
def changeGraphics(self) -> None:
settingsDialog = GraphicsEditDialog(self.SETTINGS, self.RESOURCEHANDLER)
settingsConfirm = settingsDialog.exec()
if settingsConfirm:
if settingsDialog.exec():
newSettings = settingsDialog.newSettings
try:
with contextlib.suppress(ValueError):
etfVal = int(newSettings["ETF"])
self.centralWidget().tabbedPane.entityTextFont.setPointSize(etfVal)
self.SETTINGS.setValue("Program/Graphics/EntityTextFontSize", str(newSettings["ETF"]))
except ValueError:
pass
try:
with contextlib.suppress(ValueError):
ltfVal = int(newSettings["LTF"])
self.centralWidget().tabbedPane.linkTextFont.setPointSize(ltfVal)
self.SETTINGS.setValue("Program/Graphics/LinkTextFontSize", str(newSettings["LTF"]))
except ValueError:
pass
try:
with contextlib.suppress(ValueError):
lfVal = int(newSettings["LF"])
self.centralWidget().tabbedPane.hideZoom = - int(lfVal)
self.centralWidget().tabbedPane.hideZoom = -lfVal
self.centralWidget().tabbedPane.updateCanvasHideZoom()
self.SETTINGS.setValue("Program/Graphics/LabelFade", str(newSettings["LF"]))
except ValueError:
pass
etcVal = newSettings["ETC"]
newEtcColor = QtGui.QColor(etcVal)
if newEtcColor.isValid():
@@ -1072,8 +1050,7 @@ class MainWindow(QtWidgets.QMainWindow):
for uid in uids:
# Connectors give the list if edge UIDs they represent
if isinstance(uid, set):
for linkUID in uid:
eJson.append(self.LENTDB.getLink(linkUID))
eJson.extend(self.LENTDB.getLink(linkUID) for linkUID in uid)
else:
eJson.append(self.LENTDB.getEntity(uid))
@@ -1090,10 +1067,8 @@ class MainWindow(QtWidgets.QMainWindow):
"""
for tab in self.centralWidget().tabbedPane.canvasTabs:
scene = self.centralWidget().tabbedPane.canvasTabs[tab].scene()
try:
with contextlib.suppress(KeyError):
scene.nodesDict[uid].updateLabel(label)
except KeyError:
pass
def updateLinkLabelsOnCanvases(self, uid: str, label: str) -> None:
"""
@@ -1106,10 +1081,8 @@ class MainWindow(QtWidgets.QMainWindow):
"""
for tab in self.centralWidget().tabbedPane.canvasTabs:
scene = self.centralWidget().tabbedPane.canvasTabs[tab].scene()
try:
with contextlib.suppress(KeyError):
scene.linksDict[uid].updateLabel(label)
except KeyError:
pass
def populateEntitiesWidget(self, eJson: dict, add: bool) -> None:
if add:
@@ -1140,9 +1113,9 @@ class MainWindow(QtWidgets.QMainWindow):
elif parameters[parameter].get('global') is True:
# Extra slash in the middle to ensure that resolutions cannot overwrite these accidentally (or not),
# since slashes are not allowed by default on Linux or Windows.
savedParameterValue = self.SETTINGS.value('Resolutions/Global/Parameters/' + parameter)
savedParameterValue = self.SETTINGS.value(f'Resolutions/Global/Parameters/{parameter}')
else:
savedParameterValue = self.SETTINGS.value('Resolutions/' + resolutionName + '/' + parameter)
savedParameterValue = self.SETTINGS.value(f'Resolutions/{resolutionName}/{parameter}')
if savedParameterValue is not None:
specifiedParameterValues[parameter] = savedParameterValue
parameters.pop(parameter)
@@ -1275,7 +1248,7 @@ class MainWindow(QtWidgets.QMainWindow):
macroValid = False
for macroIndex, runningMacro in enumerate(self.runningMacros):
currentResolutionForMacro = runningMacro[0]
if '/' + resolution_name in currentResolutionForMacro[0] and \
if resolution_name == currentResolutionForMacro[0] and \
resolution_uid == currentResolutionForMacro[1]:
macroValid = True
break
@@ -1283,21 +1256,21 @@ class MainWindow(QtWidgets.QMainWindow):
if macroValid:
runNext = False
runningMacro.pop(0)
if len(runningMacro) > 0:
if runningMacro:
nextResolutionForMacro = runningMacro[0]
acceptableOriginTypes = self.RESOLUTIONMANAGER.getResolutionOriginTypes(nextResolutionForMacro[0])
fullEntityJsonList = [self.LENTDB.getEntity(entityUID) for entityUID in affectedEntityUIDs]
fullEntityJsonList = [self.LENTDB.getEntity(entityUID) for entityUID in set(affectedEntityUIDs)]
filteredEntityJsonList = [entity for entity in fullEntityJsonList
if entity['Entity Type'] in acceptableOriginTypes]
if len(filteredEntityJsonList) > 0:
if filteredEntityJsonList:
preparedResolutionArguments = [nextResolutionForMacro[0], nextResolutionForMacro[1],
filteredEntityJsonList, nextResolutionForMacro[2]]
runNext = True
if not runNext:
self.setStatus('Macro execution finished.')
self.runningMacros.remove(runningMacro)
if runNext:
if macroValid and runNext:
self.runResolution(*preparedResolutionArguments)
def showMacrosDialog(self) -> None:
@@ -1869,12 +1842,11 @@ class MainWindow(QtWidgets.QMainWindow):
for answerIndex in range(answerCount):
answer = response[answerIndex]
if answer['answer']:
textAns += "Answer " + str(answerIndex + 1) + ": " + answer['answer'] + "\n\n"
textAns += "Context: ..." + answer['context'] + "...\n\n"
textAns += "Document Used: " + answer['doc']
textAns += "\n\n"
textAns += f"Answer {str(answerIndex + 1)}: {answer['answer']}\n\n" \
f"Context: ...{answer['context']}...\n\n" \
f"Document Used: {answer['doc']}\n\n"
else:
textAns += "Answer " + str(answerIndex + 1) + ": No Answer\n\n"
textAns += f"Answer {str(answerIndex + 1)}: No Answer\n\n"
self.dockbarTwo.oracle.answerSection.setPlainText(textAns)
self.setStatus("Answered Question")
@@ -2204,7 +2176,10 @@ class InitialConfigPage(QtWidgets.QWizardPage):
options=QtWidgets.QFileDialog.DontUseNativeDialog)
selectedPath = selectedPath[0]
if selectedPath != '':
self.savePathEdit.setText(str(Path(selectedPath).absolute()))
savePath = Path(selectedPath).absolute()
if savePath.suffix != '.pdf':
savePath = savePath.with_suffix(f"{savePath.suffix}.pdf")
self.savePathEdit.setText(str(savePath))
def getData(self):
data = {'SavePath': self.savePathEdit.text()}
@@ -2276,24 +2251,22 @@ class SummaryPage(QtWidgets.QWizardPage):
class EntityPage(QtWidgets.QWizardPage):
def __init__(self, parent: ReportWizard):
super(EntityPage, self).__init__(parent=parent.parent())
self.inputAppendixImageEdit = QtWidgets.QLineEdit()
self.appendixWidget = QtWidgets.QWidget()
self.appendixLayout = QtWidgets.QVBoxLayout()
self.setTitle(self.tr(f"Entity Page Wizard"))
self.setMinimumSize(300, 700)
self.entityName = parent.primaryField
self.uidPicture = parent.uid
self.entityUID = parent.uid
self.inputNotesEdit = QtWidgets.QPlainTextEdit()
self.inputImageEdit = QtWidgets.QLineEdit()
self.button = QtWidgets.QPushButton("Add...")
self.addAppendixButton = QtWidgets.QPushButton("Add New Appendix Section")
self.removeAppendixButton = QtWidgets.QPushButton("Remove Last Appendix Section")
self.scrolllayout = QtWidgets.QVBoxLayout()
self.scrollwidget = QtWidgets.QWidget()
self.defaultpic = self.parent().LENTDB.getEntity(self.uidPicture).get('Icon')
self.defaultpic = self.parent().LENTDB.getEntity(self.entityUID).get('Icon')
summaryLabel = QtWidgets.QLabel(f"Entity {self.entityName} Notes: ")
@@ -2305,13 +2278,8 @@ class EntityPage(QtWidgets.QWizardPage):
imageCheckBox.setChecked(False)
imageCheckBox.toggled.connect(pDirButton.setEnabled)
self.button.clicked.connect(self.addSection)
self.button.setDisabled(True)
appendixCheckBox = QtWidgets.QCheckBox('Add Appendix')
appendixCheckBox.setChecked(False)
appendixCheckBox.toggled.connect(self.button.setEnabled)
self.addAppendixButton.clicked.connect(self.addSection)
self.removeAppendixButton.clicked.connect(self.removeSection)
self.scrollwidget.setLayout(self.scrolllayout)
@@ -2328,8 +2296,9 @@ class EntityPage(QtWidgets.QWizardPage):
hLayout.addWidget(self.inputImageEdit)
hLayout.addWidget(pDirButton)
hLayout.addWidget(appendixCheckBox)
hLayout.addWidget(self.button)
hLayout.addItem(QtWidgets.QSpacerItem(10, 30))
hLayout.addWidget(self.addAppendixButton)
hLayout.addWidget(self.removeAppendixButton)
layout = QtWidgets.QVBoxLayout()
layout.addLayout(hLayout)
@@ -2337,7 +2306,7 @@ class EntityPage(QtWidgets.QWizardPage):
self.setLayout(layout)
def editPath(self):
def editPath(self) -> None:
selectedPath = QtWidgets.QFileDialog().getOpenFileName(parent=self, caption='Select New Icon',
dir=str(Path.home()),
options=QtWidgets.QFileDialog.DontUseNativeDialog,
@@ -2345,56 +2314,22 @@ class EntityPage(QtWidgets.QWizardPage):
if selectedPath != '':
self.inputImageEdit.setText(str(Path(selectedPath).absolute()))
def editAppendixPath(self):
selectedPath = QtWidgets.QFileDialog().getOpenFileName(parent=self, caption='Select New Icon',
dir=str(Path.home()),
options=QtWidgets.QFileDialog.DontUseNativeDialog,
filter="Image Files (*.png *.jpg)")[0]
if selectedPath != '':
self.inputAppendixImageEdit.setText(str(Path(selectedPath).absolute()))
def addSection(self) -> None:
appendixWidget = AppendixWidget()
self.scrolllayout.addWidget(appendixWidget)
def addSection(self):
appendixLabelNotes = QtWidgets.QLabel("Entity Notes: ")
inputAppendixNotesEdit = QtWidgets.QPlainTextEdit()
imageAppendixLabel = QtWidgets.QLabel("Image Path: ")
appendixButton = QtWidgets.QPushButton("Select Image...")
appendixButton.clicked.connect(self.editAppendixPath)
self.appendixLayout.addWidget(appendixLabelNotes)
self.appendixLayout.addWidget(inputAppendixNotesEdit)
self.appendixLayout.addWidget(imageAppendixLabel)
self.appendixLayout.addWidget(self.inputAppendixImageEdit)
self.appendixLayout.addWidget(appendixButton)
self.appendixWidget.setLayout(self.appendixLayout)
self.scrolllayout.addWidget(self.appendixWidget)
self.button.setDisabled(True)
def removeSection(self) -> None:
numChildren = self.scrolllayout.count()
if numChildren:
appendixItem = self.scrolllayout.takeAt(numChildren - 1)
appendixItem.widget().deleteLater()
def getData(self):
import re
from svglib.svglib import svg2rlg
appendixNotes = []
if self.inputImageEdit.text() != '':
data = {'EntityNotes': self.inputNotesEdit.toPlainText(), 'EntityImage': self.inputImageEdit.text()}
else:
if 'svg' in str(self.defaultpic):
contents = bytearray(self.defaultpic)
widthRegex = re.compile(b' width="\d*" ')
fileContents = ''
for widthMatches in widthRegex.findall(self.defaultpic):
fileContents = contents.replace(widthMatches, b' ')
heightRegex = re.compile(b' height="\d*" ')
for heightMatches in heightRegex.findall(self.defaultpic):
fileContents = contents.replace(heightMatches, b' ')
fileContents = fileContents.replace(b'<svg ', b'<svg height="150" width="150" ')
temp_dir = tempfile.TemporaryDirectory()
imagePath = Path(temp_dir.name) / 'entity.svg'
with open(imagePath, 'wb') as tempFile:
tempFile.write(bytearray(fileContents))
image = svg2rlg(imagePath)
data = {'EntityNotes': self.inputNotesEdit.toPlainText(), 'EntityImage': image}
elif 'PNG' in str(self.defaultpic):
if 'PNG' in str(self.defaultpic):
temp_dir = tempfile.TemporaryDirectory()
imagePath = Path(temp_dir.name) / 'entity.png'
with open(imagePath, 'wb') as tempFile:
@@ -2402,9 +2337,10 @@ class EntityPage(QtWidgets.QWizardPage):
data = {'EntityNotes': self.inputNotesEdit.toPlainText(), 'EntityImage': str(imagePath)}
else:
self.defaultpic = self.parent().RESOURCEHANDLER.getEntityDefaultPicture(
self.parent().LENTDB.getEntity(self.uidPicture)['Entity Type'])
# Default picture is an SVG.
if 'svg' not in str(self.defaultpic):
# Default picture is an SVG.
self.defaultpic = self.parent().RESOURCEHANDLER.getEntityDefaultPicture(
self.parent().LENTDB.getEntity(self.entityUID)['Entity Type'])
contents = bytearray(self.defaultpic)
widthRegex = re.compile(b' width="\d*" ')
fileContents = ''
@@ -2423,16 +2359,43 @@ class EntityPage(QtWidgets.QWizardPage):
image = svg2rlg(imagePath)
data = {'EntityNotes': self.inputNotesEdit.toPlainText(), 'EntityImage': image}
qPlainTextNote = self.appendixWidget.findChildren(QtWidgets.QPlainTextEdit)
qlineEdits = self.appendixWidget.findChildren(QtWidgets.QLineEdit)
for i in range(len(qPlainTextNote)):
appendixDict = {'AppendixEntityNotes': qPlainTextNote[i].toPlainText(),
'AppendixEntityImage': qlineEdits[i].text()}
for index in range(self.scrolllayout.count()):
childWidget = self.scrolllayout.itemAt(index).widget()
appendixDict = {'AppendixEntityNotes': childWidget.inputAppendixNotesEdit.toPlainText(),
'AppendixEntityImage': childWidget.inputAppendixImageEdit.text()}
appendixNotes.append(appendixDict)
return data, appendixNotes
class AppendixWidget(QtWidgets.QWidget):
def __init__(self) -> None:
super(AppendixWidget, self).__init__()
appendixWidgetLayout = QtWidgets.QGridLayout()
appendixLabelNotes = QtWidgets.QLabel("Entity Notes: ")
self.inputAppendixNotesEdit = QtWidgets.QPlainTextEdit()
imageAppendixLabel = QtWidgets.QLabel("Image Path: ")
appendixButton = QtWidgets.QPushButton("Select Image...")
appendixButton.clicked.connect(self.editAppendixPath)
self.inputAppendixImageEdit = QtWidgets.QLineEdit()
appendixWidgetLayout.addWidget(appendixLabelNotes, 0, 0, 1, 1)
appendixWidgetLayout.addWidget(self.inputAppendixNotesEdit, 2, 0, 4, 1)
appendixWidgetLayout.addWidget(imageAppendixLabel, 7, 0, 1, 1)
appendixWidgetLayout.addWidget(self.inputAppendixImageEdit, 9, 0, 1, 1)
appendixWidgetLayout.addWidget(appendixButton, 11, 0, 1, 1)
self.setLayout(appendixWidgetLayout)
self.inputAppendixNotesEdit.setFixedHeight(100)
def editAppendixPath(self) -> None:
selectedPath = QtWidgets.QFileDialog().getOpenFileName(parent=self, caption='Select New Icon',
dir=str(Path.home()),
options=QtWidgets.QFileDialog.DontUseNativeDialog,
filter="Image Files (*.png *.jpg)")[0]
if selectedPath != '':
self.inputAppendixImageEdit.setText(str(Path(selectedPath).absolute()))
class CreateOrOpenCanvas(QtWidgets.QDialog):
def __init__(self, parent, isConnectedToNetwork=False, syncedCanvases=None):
@@ -3826,13 +3789,11 @@ class SplitEntitiesDialog(QtWidgets.QDialog):
self.entitiesTable.setItem(newRowIndex, 0, QtWidgets.QTableWidgetItem(entityPrimaryField))
self.entitiesTable.setFocus()
count = 1
for link in self.allLinks:
for count, link in enumerate(self.allLinks, start=1):
selectResolution = QtWidgets.QCheckBox(link['Resolution'])
selectResolution.setStyleSheet(Stylesheets.CHECK_BOX_STYLESHEET)
selectResolution.linkUID = link['uid']
self.entitiesTable.setCellWidget(newRowIndex, count, selectResolution)
count += 1
def removeRow(self) -> None:
self.entitiesTable.removeRow(self.entitiesTable.rowCount() - 1)
@@ -4355,13 +4316,11 @@ class QueryResultsViewer(QtWidgets.QDialog):
for index in range(1, len(self.headerFields)):
self.resultsTable.horizontalHeader().setSectionResizeMode(index, QtWidgets.QHeaderView.Stretch)
count = 0
for uid in selectedUIDs:
for count, uid in enumerate(selectedUIDs):
self.resultsTable.insertRow(count)
for index, field in enumerate(self.headerFields):
self.resultsTable.setItem(count, index, QtWidgets.QTableWidgetItem(
str(entitiesDict[uid].get(field, 'None'))))
count += 1
self.resultsTabbedPane.addTab(self.resultsTable, 'Table')
@@ -4728,8 +4687,8 @@ class MacroDialog(QtWidgets.QDialog):
if 0 < len(rParameters):
parameterSelector = ResolutionParametersSelector(
self.mainWindowObject, resolutionName, rParameters,
windowTitle='[' + str(itemIndex) + '/' + str(numberOfResolutionsSelected) + '] ' +
'Select Parameter values for Resolution: ' + resolutionName)
windowTitle=f'[{str(itemIndex + 1)}/{str(numberOfResolutionsSelected)}] Select Parameter '
f'values for Resolution: {resolutionName}')
if parameterSelector.exec():
resolutionParameterValues.update(parameterSelector.chosenParameters)
else:

View File

@@ -33,14 +33,7 @@ class HaveIBeenPwnedBreachDomains:
primaryField = entity['Domain Name']
breachInfoRequest = requests.get(baseURL + primaryField, headers=requestHeaders)
statusCode = breachInfoRequest.status_code
if statusCode == 401:
return "The HIBP API Key provided is invalid."
elif statusCode == 429:
sleep(2)
continue
elif statusCode == 503:
return "The HIBP Service is unavailable."
elif statusCode == 200:
if statusCode == 200:
breachContent = json.loads(breachInfoRequest.content)
for breach in breachContent:
@@ -80,6 +73,13 @@ class HaveIBeenPwnedBreachDomains:
'Date Created': breach['BreachDate']},
{entity['uid']: {'Resolution': 'Contained in Breach',
'Notes': ''}}])
elif statusCode == 401:
return "The HIBP API Key provided is invalid."
elif statusCode == 429:
sleep(2)
continue
elif statusCode == 503:
return "The HIBP Service is unavailable."
sleep(1.7)
count += 1
return returnResults

View File

@@ -36,14 +36,7 @@ class HaveIBeenPwnedBreaches:
breachInfoRequest = requests.get(baseURL + quote_plus(primaryField) + '?truncateResponse=false',
headers=requestHeaders)
statusCode = breachInfoRequest.status_code
if statusCode == 401:
return "The HIBP API Key provided is invalid."
elif statusCode == 429:
sleep(2)
continue
elif statusCode == 503:
return "The HIBP Service is unavailable."
elif statusCode == 200:
if statusCode == 200:
breachContent = json.loads(breachInfoRequest.content)
for breach in breachContent:
@@ -83,6 +76,13 @@ class HaveIBeenPwnedBreaches:
'Date Created': breach['BreachDate']},
{entity['uid']: {'Resolution': 'Contained in Breach',
'Notes': ''}}])
elif statusCode == 401:
return "The HIBP API Key provided is invalid."
elif statusCode == 429:
sleep(2)
continue
elif statusCode == 503:
return "The HIBP Service is unavailable."
sleep(1.7)
count += 1
return returnResults

View File

@@ -34,24 +34,24 @@ class HaveIBeenPwnedPassword:
breachInfoRequest = requests.get(baseURL + hashPrefix, headers=requestHeaders)
statusCode = breachInfoRequest.status_code
if statusCode == 401:
if statusCode == 200:
pwnedPasswordContent = breachInfoRequest.content.decode('utf-8').split('\r\n')
for password in pwnedPasswordContent:
if hashSuffix in password:
returnResults.append([{'Phrase': f"Password Hash Found {password.split(':')[1]} times "
f"in breach data.",
'Entity Type': 'Phrase'},
{entity['uid']: {'Resolution': 'Pwned Password',
'Notes': ''}}])
break
elif statusCode == 401:
return "The HIBP API Key provided is invalid."
elif statusCode == 429:
sleep(2)
continue
elif statusCode == 503:
return "The HIBP Service is unavailable."
elif statusCode == 200:
pwnedPasswordContent = breachInfoRequest.content.decode('utf-8').split('\r\n')
for password in pwnedPasswordContent:
if hashSuffix in password:
returnResults.append([{'Phrase': 'Password Hash Found ' + password.split(':')[1] +
' times in breach data.',
'Entity Type': 'Phrase'},
{entity['uid']: {'Resolution': 'Pwned Password',
'Notes': ''}}])
break
sleep(1.7)
count += 1
return returnResults

View File

@@ -35,24 +35,24 @@ class HaveIBeenPwnedPasswordHash:
breachInfoRequest = requests.get(baseURL + hashPrefix, headers=requestHeaders)
statusCode = breachInfoRequest.status_code
if statusCode == 401:
if statusCode == 200:
pwnedPasswordContent = breachInfoRequest.content.decode('utf-8').split('\r\n')
for password in pwnedPasswordContent:
if hashSuffix in password:
returnResults.append([{'Phrase': f"Password Hash Found {password.split(':')[1]} times "
f"in breach data.",
'Entity Type': 'Phrase'},
{entity['uid']: {'Resolution': 'Pwned Password',
'Notes': ''}}])
break
elif statusCode == 401:
return "The HIBP API Key provided is invalid."
elif statusCode == 429:
sleep(2)
continue
elif statusCode == 503:
return "The HIBP Service is unavailable."
elif statusCode == 200:
pwnedPasswordContent = breachInfoRequest.content.decode('utf-8').split('\r\n')
for password in pwnedPasswordContent:
if hashSuffix in password:
returnResults.append([{'Phrase': 'Password Hash Found ' + password.split(':')[1] +
' times in breach data.',
'Entity Type': 'Phrase'},
{entity['uid']: {'Resolution': 'Pwned Password',
'Notes': ''}}])
break
sleep(1.7)
count += 1
return returnResults

View File

@@ -31,14 +31,7 @@ class HaveIBeenPwnedPastes:
emailAddress = entity['Email Address']
pasteInfoRequest = requests.get(baseURL + quote_plus(emailAddress), headers=requestHeaders)
statusCode = pasteInfoRequest.status_code
if statusCode == 401:
return "The HIBP API Key provided is invalid."
elif statusCode == 429:
sleep(2)
continue
elif statusCode == 503:
return "The HIBP Service is unavailable."
elif statusCode == 200:
if statusCode == 200:
pasteContent = json.loads(pasteInfoRequest.content)
for paste in pasteContent:
@@ -46,7 +39,7 @@ class HaveIBeenPwnedPastes:
pasteSource = paste['Source']
# If Paste Date is None, then default to entity creation date.
returnResults.append([{'Paste Identifier': pasteSource + ' | ' + pasteID,
returnResults.append([{'Paste Identifier': f'{pasteSource} | {pasteID}',
'Paste Title': paste['Title'],
'Paste Source': pasteSource,
'Paste ID': pasteID,
@@ -55,6 +48,14 @@ class HaveIBeenPwnedPastes:
'Date Created': paste['Date']},
{entity['uid']: {'Resolution': 'Contained in Paste',
'Notes': ''}}])
elif statusCode == 401:
return "The HIBP API Key provided is invalid."
elif statusCode == 429:
sleep(2)
continue
elif statusCode == 503:
return "The HIBP Service is unavailable."
sleep(1.7)
count += 1
return returnResults

View File

@@ -38,8 +38,8 @@ class InterpolRedNotices:
noticeNotes = ""
for warrant in noticeContents['arrest_warrants']:
noticeNotes += 'CHARGE: ' + warrant.get('charge') + '\nFROM COUNTRY: ' + \
warrant.get('issuing_country_id') + '\n\n'
noticeNotes += f"CHARGE: {warrant.get('charge')}\nFROM COUNTRY: " \
f"{warrant.get('issuing_country_id')}\n\n"
try:
thumbnailPictureURL = noticeContents['_links']['thumbnail']['href']
@@ -84,7 +84,7 @@ class InterpolRedNotices:
for entity in entityJsonList:
uid = entity['uid']
entityType = entity['Entity Type']
if entityType == 'Person' or entityType == 'Politically Exposed Person':
if entityType in ['Person', 'Politically Exposed Person']:
primaryField = entity['Full Name'].strip()
elif entityType == 'Phrase':
primaryField = entity['Phrase'].strip()
@@ -95,17 +95,13 @@ class InterpolRedNotices:
# This ensures that we will not miss any matches.
nameFragments = primaryField.split(' ')
firstName = nameFragments[0].upper()
if len(nameFragments) == 1:
lastName = None
else:
lastName = nameFragments[-1].upper()
lastName = None if len(nameFragments) == 1 else nameFragments[-1].upper()
firstRequestURL = firstRequestPart1 + firstName
if lastName is not None:
firstRequestURL += "&name=" + lastName
firstRequestURL += f"&name={lastName}"
firstRequestURL += firstRequestPart2
firstRequest = requests.get(firstRequestURL + "1" + firstRequestPart3)
firstRequest = requests.get(f"{firstRequestURL}1{firstRequestPart3}")
pageContents = firstRequest.json()
lastPage = int(pageContents['_links']['last']['href'].split('&page=')[1].split('&')[0])

View File

@@ -84,7 +84,7 @@ class InterpolYellowNotices:
for entity in entityJsonList:
uid = entity['uid']
entityType = entity['Entity Type']
if entityType == 'Person' or entityType == 'Politically Exposed Person':
if entityType in ['Person', 'Politically Exposed Person']:
primaryField = entity['Full Name'].strip()
elif entityType == 'Phrase':
primaryField = entity['Phrase'].strip()
@@ -95,17 +95,13 @@ class InterpolYellowNotices:
# This ensures that we will not miss any matches.
nameFragments = primaryField.split(' ')
firstName = nameFragments[0].upper()
if len(nameFragments) == 1:
lastName = None
else:
lastName = nameFragments[-1].upper()
lastName = None if len(nameFragments) == 1 else nameFragments[-1].upper()
firstRequestURL = firstRequestPart1 + firstName
if lastName is not None:
firstRequestURL += "&name=" + lastName
firstRequestURL += f"&name={lastName}"
firstRequestURL += firstRequestPart2
firstRequest = requests.get(firstRequestURL + "1" + firstRequestPart3)
firstRequest = requests.get(f"{firstRequestURL}1{firstRequestPart3}")
pageContents = firstRequest.json()
lastPage = int(pageContents['_links']['last']['href'].split('&page=')[1].split('&')[0])