Moved NWTree to core/tree.py
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
novelWriter – Project Item Class
|
||||
==================================
|
||||
Class holding the data off a project tree item
|
||||
Class holding the data of a project tree item
|
||||
|
||||
File History:
|
||||
Created: 2018-10-27 [0.0.1] NWItem
|
||||
|
||||
+2
-385
@@ -3,14 +3,13 @@
|
||||
|
||||
novelWriter – Project Wrapper
|
||||
===============================
|
||||
Class wrapping the data if a novelWriter project
|
||||
Class wrapping the data of a novelWriter project
|
||||
|
||||
File History:
|
||||
Created: 2018-09-29 [0.0.1] NWProject
|
||||
Created: 2019-05-19 [0.1.3] NWStatus
|
||||
Created: 2019-10-21 [0.3.1] OptionState
|
||||
Merged: 2020-05-07 [0.4.5] Moved NWStatus class to this file
|
||||
Added: 2020-05-07 [0.4.5] NWTree
|
||||
Rewritten: 2020-02-19 [0.4.5] OptionState
|
||||
|
||||
This file is a part of novelWriter
|
||||
@@ -42,6 +41,7 @@ from shutil import make_archive
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from nw.core.tree import NWTree
|
||||
from nw.core.item import NWItem
|
||||
from nw.core.document import NWDoc
|
||||
from nw.common import checkString, checkBool, checkInt, formatTimeStamp
|
||||
@@ -1224,389 +1224,6 @@ class NWProject():
|
||||
|
||||
# END Class NWProject
|
||||
|
||||
# =============================================================================================== #
|
||||
# NWTree
|
||||
# Class holding the project tree for the NWProject
|
||||
# =============================================================================================== #
|
||||
|
||||
class NWTree():
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
self.theProject = theProject
|
||||
|
||||
self._projTree = {} # Holds all the items of the project
|
||||
self._treeOrder = [] # The order of the tree items on the tree view
|
||||
self._treeRoots = [] # The root items of the tree
|
||||
self._trashRoot = None # The handle of the trash root folder
|
||||
self._theLength = 0 # Always the length of _treeOrder
|
||||
self._theIndex = 0 # The current iterator index
|
||||
self._treeChanged = False # True if tree structure has changed
|
||||
self._handleSeed = None # Used for generating handles for testing
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def clear(self):
|
||||
"""Clear the item tree entirely.
|
||||
"""
|
||||
self._projTree = {}
|
||||
self._treeOrder = []
|
||||
self._treeRoots = []
|
||||
self._trashRoot = None
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
self._treeChanged = False
|
||||
return
|
||||
|
||||
def handles(self):
|
||||
"""Returns a copy of the list of all the active handles.
|
||||
"""
|
||||
return self._treeOrder.copy()
|
||||
|
||||
def append(self, tHandle, pHandle, nwItem):
|
||||
"""Add a new item to the end of the tree.
|
||||
"""
|
||||
tHandle = checkString(tHandle, None, True)
|
||||
pHandle = checkString(pHandle, None, True)
|
||||
if tHandle is None:
|
||||
tHandle = self._makeHandle()
|
||||
|
||||
logger.verbose("Adding entry %s with parent %s" % (str(tHandle), str(pHandle)))
|
||||
|
||||
nwItem.setHandle(tHandle)
|
||||
nwItem.setParent(pHandle)
|
||||
|
||||
self._projTree[tHandle] = nwItem
|
||||
self._treeOrder.append(tHandle)
|
||||
|
||||
if nwItem.itemType == nwItemType.ROOT:
|
||||
logger.verbose("Entry %s is a root item" % str(tHandle))
|
||||
self._treeRoots.append(tHandle)
|
||||
|
||||
if nwItem.itemType == nwItemType.TRASH:
|
||||
if self._trashRoot is None:
|
||||
logger.verbose("Entry %s is the trash folder" % str(tHandle))
|
||||
self._trashRoot = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
|
||||
return
|
||||
|
||||
def packXML(self, xParent):
|
||||
"""Pack the content of the tree into an XML object.
|
||||
"""
|
||||
xContent = etree.SubElement(xParent, "content", attrib={
|
||||
"count":str(self._theLength)}
|
||||
)
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
tItem.packXML(xContent)
|
||||
return
|
||||
|
||||
def unpackXML(self, xContent):
|
||||
"""Iterate through all items of a content XML object and add
|
||||
them to the project tree.
|
||||
"""
|
||||
if xContent.tag != "content":
|
||||
logger.error("XML entry is not a NWTree")
|
||||
return False
|
||||
|
||||
self.clear()
|
||||
for xItem in xContent:
|
||||
nwItem = NWItem(self.theProject)
|
||||
if nwItem.unpackXML(xItem):
|
||||
self.append(nwItem.itemHandle, nwItem.parHandle, nwItem)
|
||||
|
||||
return True
|
||||
|
||||
def writeToCFiles(self):
|
||||
"""Write the convenience table of contents files in the root of
|
||||
the project directory. These files are there to assist the user
|
||||
if they wish to browse the stored files.
|
||||
"""
|
||||
tocText = path.join(self.theProject.projPath, nwFiles.TOC_TXT)
|
||||
tocJson = path.join(self.theProject.projPath, nwFiles.TOC_JSON)
|
||||
|
||||
jsonData = []
|
||||
try:
|
||||
# Dump the text
|
||||
with open(tocText, mode="w", encoding="utf8") as outFile:
|
||||
outFile.write("\n")
|
||||
outFile.write(" Table of Contents\n")
|
||||
outFile.write("===================\n")
|
||||
outFile.write("\n")
|
||||
outFile.write(" %-25s %-9s %s\n" %("File Name","Class","Document Label"))
|
||||
outFile.write("-"*80+"\n")
|
||||
for tHandle in sorted(self._treeOrder):
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
continue
|
||||
tFile = tHandle+".nwd"
|
||||
if path.isfile(path.join(self.theProject.projContent, tFile)):
|
||||
outFile.write(" %-25s %-9s %s\n" %(
|
||||
path.join("content", tFile),
|
||||
tItem.itemClass.name,
|
||||
tItem.itemName,
|
||||
))
|
||||
jsonData.append([
|
||||
path.join("content", tFile),
|
||||
tItem.itemClass.name,
|
||||
tItem.itemName,
|
||||
])
|
||||
outFile.write("\n")
|
||||
|
||||
# Dump the JSON
|
||||
with open(tocJson, mode="w+", encoding="utf8") as outFile:
|
||||
outFile.write(json.dumps(jsonData, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Tree Structure Methods
|
||||
##
|
||||
|
||||
def trashRoot(self):
|
||||
"""Returns the handle of the trash folder, or None if there
|
||||
isn't one.
|
||||
"""
|
||||
if self._trashRoot:
|
||||
return self._trashRoot
|
||||
return None
|
||||
|
||||
def findRoot(self, theClass):
|
||||
"""Find the root item for a given class.
|
||||
Note: This returns the first item for class CUSTOM.
|
||||
"""
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
if theClass == tItem.itemClass:
|
||||
return tItem.itemHandle
|
||||
return None
|
||||
|
||||
def checkRootUnique(self, theClass):
|
||||
"""Checks if there already is a root entry of class 'theClass'
|
||||
in the root of the project tree. CUSTOM class is skipped as it
|
||||
is not required to be unique.
|
||||
"""
|
||||
if theClass == nwItemClass.CUSTOM:
|
||||
return True
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if theClass == tItem.itemClass:
|
||||
return False
|
||||
return True
|
||||
|
||||
def getRootItem(self, tHandle):
|
||||
"""Iterate upwards in the tree until we find the item with
|
||||
parent None, the root item. We do this with a for loop with a
|
||||
maximum depth of 200 to make infinite loops impossible.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is not None:
|
||||
for i in range(200):
|
||||
if tItem.parHandle is None:
|
||||
return tHandle
|
||||
else:
|
||||
tHandle = tItem.parHandle
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return tHandle
|
||||
return None
|
||||
|
||||
def getItemPath(self, tHandle):
|
||||
"""Iterate upwards in the tree until we find the item with
|
||||
parent None, the root item, and return the list of handles.
|
||||
We do this with a for loop with a maximum depth of 200 to make
|
||||
infinite loops impossible.
|
||||
"""
|
||||
tTree = []
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is not None:
|
||||
tTree.append(tHandle)
|
||||
for i in range(200):
|
||||
if tItem.parHandle is None:
|
||||
return tTree
|
||||
else:
|
||||
tHandle = tItem.parHandle
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return tTree
|
||||
else:
|
||||
tTree.append(tHandle)
|
||||
return tTree
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setOrder(self, newOrder):
|
||||
"""Reorders the tree based on a list of items.
|
||||
"""
|
||||
tmpOrder = []
|
||||
|
||||
# Add all known elements to a new temp list
|
||||
for tHandle in newOrder:
|
||||
if tHandle in self._projTree:
|
||||
tmpOrder.append(tHandle)
|
||||
else:
|
||||
logger.error("Handle %s in new tree order is not in project tree" % tHandle)
|
||||
|
||||
# Do a reverse lookup to check for items that will be lost
|
||||
# This is mainly for debugging purposes
|
||||
for tHandle in self._treeOrder:
|
||||
if tHandle not in tmpOrder:
|
||||
logger.warning("Handle %s in old tree order is not in new tree order" % tHandle)
|
||||
|
||||
# Save the temp list
|
||||
self._treeOrder = tmpOrder
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
logger.verbose("Project tree order updated")
|
||||
|
||||
return
|
||||
|
||||
def setSeed(self, theSeed):
|
||||
"""Used for debugging!
|
||||
Sets a seed for generating handles so that they always come out
|
||||
in a predictable order.
|
||||
"""
|
||||
self._handleSeed = theSeed
|
||||
return
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
|
||||
def countTypes(self):
|
||||
"""Count the number of files, folders and roots in the project.
|
||||
"""
|
||||
nRoot = 0
|
||||
nFolder = 0
|
||||
nFile = 0
|
||||
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
continue
|
||||
elif tItem.itemType == nwItemType.ROOT:
|
||||
nRoot += 1
|
||||
elif tItem.itemType == nwItemType.FOLDER:
|
||||
nFolder += 1
|
||||
elif tItem.itemType == nwItemType.FILE:
|
||||
nFile += 1
|
||||
|
||||
return nRoot, nFolder, nFile
|
||||
|
||||
##
|
||||
# Meta Methods
|
||||
##
|
||||
|
||||
def __len__(self):
|
||||
"""Return the length counter. Does not check that it is correct!
|
||||
"""
|
||||
return self._theLength
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns True if the tree has any entries.
|
||||
"""
|
||||
return self._theLength > 0
|
||||
|
||||
##
|
||||
# Item Access Methods
|
||||
##
|
||||
|
||||
def __getitem__(self, tHandle):
|
||||
"""Return a project item based on its handle. Returns None if
|
||||
the handle doesn't exist in the project.
|
||||
"""
|
||||
if tHandle in self._projTree:
|
||||
return self._projTree[tHandle]
|
||||
logger.error("No tree item with handle %s" % str(tHandle))
|
||||
return None
|
||||
|
||||
def __delitem__(self, tHandle):
|
||||
"""This only removes the item from the order list, but not from
|
||||
the project tree.
|
||||
"""
|
||||
if tHandle not in self._treeOrder:
|
||||
logger.warning(
|
||||
"Could not remove item %s from project tree as it does not exist" % tHandle
|
||||
)
|
||||
return False
|
||||
self._treeOrder.remove(tHandle)
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
return True
|
||||
|
||||
def __contains__(self, tHandle):
|
||||
"""Checks if a handle exists in the tree.
|
||||
"""
|
||||
return tHandle in self._treeOrder
|
||||
|
||||
##
|
||||
# Iterator Methods
|
||||
##
|
||||
|
||||
def __iter__(self):
|
||||
"""Initiates the iterator.
|
||||
"""
|
||||
self._theIndex = 0
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
"""Returns the item from the next entry in the _treeOrder list.
|
||||
"""
|
||||
if self._theIndex < self._theLength:
|
||||
theItem = self.__getitem__(self._treeOrder[self._theIndex])
|
||||
self._theIndex += 1
|
||||
return theItem
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _setTreeChanged(self, theState):
|
||||
"""Set the changed flag to theState, and if being set to True,
|
||||
propagate that state change to the parent NWProject class.
|
||||
"""
|
||||
self._treeChanged = theState
|
||||
if theState:
|
||||
self.theProject.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def _makeHandle(self, addSeed=""):
|
||||
"""Generate a unique item handle. In the unlikely event that the
|
||||
key already exists, salt the seed and generate a new handle.
|
||||
"""
|
||||
if self._handleSeed is None:
|
||||
newSeed = str(time()) + addSeed
|
||||
else:
|
||||
# This is used for debugging
|
||||
newSeed = str(self._handleSeed)
|
||||
self._handleSeed += 1
|
||||
logger.verbose("Generating handle with seed '%s'" % newSeed)
|
||||
itemHandle = sha256(newSeed.encode()).hexdigest()[0:13]
|
||||
if itemHandle in self._projTree:
|
||||
logger.warning("Duplicate handle encountered! Retrying ...")
|
||||
itemHandle = self._makeHandle(addSeed+"!")
|
||||
return itemHandle
|
||||
|
||||
# END Class NWTree
|
||||
|
||||
# =============================================================================================== #
|
||||
# NWStatus
|
||||
# Class holding the item status values stored in the NWProject
|
||||
|
||||
+421
@@ -0,0 +1,421 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter Project Tree Class
|
||||
|
||||
novelWriter – Project Tree Class
|
||||
==================================
|
||||
Class holding the data of the project tree
|
||||
|
||||
File History:
|
||||
Created: 2020-05-07 [0.4.5] NWTree
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2020, Veronica Berglyd Olsen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import json
|
||||
import nw
|
||||
|
||||
from os import path
|
||||
from lxml import etree
|
||||
from hashlib import sha256
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from nw.core.item import NWItem
|
||||
from nw.common import checkString
|
||||
from nw.constants import nwFiles, nwItemType, nwItemClass
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class NWTree():
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
self.theProject = theProject
|
||||
|
||||
self._projTree = {} # Holds all the items of the project
|
||||
self._treeOrder = [] # The order of the tree items on the tree view
|
||||
self._treeRoots = [] # The root items of the tree
|
||||
self._trashRoot = None # The handle of the trash root folder
|
||||
self._theLength = 0 # Always the length of _treeOrder
|
||||
self._theIndex = 0 # The current iterator index
|
||||
self._treeChanged = False # True if tree structure has changed
|
||||
self._handleSeed = None # Used for generating handles for testing
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def clear(self):
|
||||
"""Clear the item tree entirely.
|
||||
"""
|
||||
self._projTree = {}
|
||||
self._treeOrder = []
|
||||
self._treeRoots = []
|
||||
self._trashRoot = None
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
self._treeChanged = False
|
||||
return
|
||||
|
||||
def handles(self):
|
||||
"""Returns a copy of the list of all the active handles.
|
||||
"""
|
||||
return self._treeOrder.copy()
|
||||
|
||||
def append(self, tHandle, pHandle, nwItem):
|
||||
"""Add a new item to the end of the tree.
|
||||
"""
|
||||
tHandle = checkString(tHandle, None, True)
|
||||
pHandle = checkString(pHandle, None, True)
|
||||
if tHandle is None:
|
||||
tHandle = self._makeHandle()
|
||||
|
||||
logger.verbose("Adding entry %s with parent %s" % (str(tHandle), str(pHandle)))
|
||||
|
||||
nwItem.setHandle(tHandle)
|
||||
nwItem.setParent(pHandle)
|
||||
|
||||
self._projTree[tHandle] = nwItem
|
||||
self._treeOrder.append(tHandle)
|
||||
|
||||
if nwItem.itemType == nwItemType.ROOT:
|
||||
logger.verbose("Entry %s is a root item" % str(tHandle))
|
||||
self._treeRoots.append(tHandle)
|
||||
|
||||
if nwItem.itemType == nwItemType.TRASH:
|
||||
if self._trashRoot is None:
|
||||
logger.verbose("Entry %s is the trash folder" % str(tHandle))
|
||||
self._trashRoot = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
|
||||
return
|
||||
|
||||
def packXML(self, xParent):
|
||||
"""Pack the content of the tree into an XML object.
|
||||
"""
|
||||
xContent = etree.SubElement(xParent, "content", attrib={
|
||||
"count":str(self._theLength)}
|
||||
)
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
tItem.packXML(xContent)
|
||||
return
|
||||
|
||||
def unpackXML(self, xContent):
|
||||
"""Iterate through all items of a content XML object and add
|
||||
them to the project tree.
|
||||
"""
|
||||
if xContent.tag != "content":
|
||||
logger.error("XML entry is not a NWTree")
|
||||
return False
|
||||
|
||||
self.clear()
|
||||
for xItem in xContent:
|
||||
nwItem = NWItem(self.theProject)
|
||||
if nwItem.unpackXML(xItem):
|
||||
self.append(nwItem.itemHandle, nwItem.parHandle, nwItem)
|
||||
|
||||
return True
|
||||
|
||||
def writeToCFiles(self):
|
||||
"""Write the convenience table of contents files in the root of
|
||||
the project directory. These files are there to assist the user
|
||||
if they wish to browse the stored files.
|
||||
"""
|
||||
tocText = path.join(self.theProject.projPath, nwFiles.TOC_TXT)
|
||||
tocJson = path.join(self.theProject.projPath, nwFiles.TOC_JSON)
|
||||
|
||||
jsonData = []
|
||||
try:
|
||||
# Dump the text
|
||||
with open(tocText, mode="w", encoding="utf8") as outFile:
|
||||
outFile.write("\n")
|
||||
outFile.write(" Table of Contents\n")
|
||||
outFile.write("===================\n")
|
||||
outFile.write("\n")
|
||||
outFile.write(" %-25s %-9s %s\n" %("File Name","Class","Document Label"))
|
||||
outFile.write("-"*80+"\n")
|
||||
for tHandle in sorted(self._treeOrder):
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
continue
|
||||
tFile = tHandle+".nwd"
|
||||
if path.isfile(path.join(self.theProject.projContent, tFile)):
|
||||
outFile.write(" %-25s %-9s %s\n" %(
|
||||
path.join("content", tFile),
|
||||
tItem.itemClass.name,
|
||||
tItem.itemName,
|
||||
))
|
||||
jsonData.append([
|
||||
path.join("content", tFile),
|
||||
tItem.itemClass.name,
|
||||
tItem.itemName,
|
||||
])
|
||||
outFile.write("\n")
|
||||
|
||||
# Dump the JSON
|
||||
with open(tocJson, mode="w+", encoding="utf8") as outFile:
|
||||
outFile.write(json.dumps(jsonData, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Tree Structure Methods
|
||||
##
|
||||
|
||||
def trashRoot(self):
|
||||
"""Returns the handle of the trash folder, or None if there
|
||||
isn't one.
|
||||
"""
|
||||
if self._trashRoot:
|
||||
return self._trashRoot
|
||||
return None
|
||||
|
||||
def findRoot(self, theClass):
|
||||
"""Find the root item for a given class.
|
||||
Note: This returns the first item for class CUSTOM.
|
||||
"""
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
if theClass == tItem.itemClass:
|
||||
return tItem.itemHandle
|
||||
return None
|
||||
|
||||
def checkRootUnique(self, theClass):
|
||||
"""Checks if there already is a root entry of class 'theClass'
|
||||
in the root of the project tree. CUSTOM class is skipped as it
|
||||
is not required to be unique.
|
||||
"""
|
||||
if theClass == nwItemClass.CUSTOM:
|
||||
return True
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if theClass == tItem.itemClass:
|
||||
return False
|
||||
return True
|
||||
|
||||
def getRootItem(self, tHandle):
|
||||
"""Iterate upwards in the tree until we find the item with
|
||||
parent None, the root item. We do this with a for loop with a
|
||||
maximum depth of 200 to make infinite loops impossible.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is not None:
|
||||
for i in range(200):
|
||||
if tItem.parHandle is None:
|
||||
return tHandle
|
||||
else:
|
||||
tHandle = tItem.parHandle
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return tHandle
|
||||
return None
|
||||
|
||||
def getItemPath(self, tHandle):
|
||||
"""Iterate upwards in the tree until we find the item with
|
||||
parent None, the root item, and return the list of handles.
|
||||
We do this with a for loop with a maximum depth of 200 to make
|
||||
infinite loops impossible.
|
||||
"""
|
||||
tTree = []
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is not None:
|
||||
tTree.append(tHandle)
|
||||
for i in range(200):
|
||||
if tItem.parHandle is None:
|
||||
return tTree
|
||||
else:
|
||||
tHandle = tItem.parHandle
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return tTree
|
||||
else:
|
||||
tTree.append(tHandle)
|
||||
return tTree
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setOrder(self, newOrder):
|
||||
"""Reorders the tree based on a list of items.
|
||||
"""
|
||||
tmpOrder = []
|
||||
|
||||
# Add all known elements to a new temp list
|
||||
for tHandle in newOrder:
|
||||
if tHandle in self._projTree:
|
||||
tmpOrder.append(tHandle)
|
||||
else:
|
||||
logger.error("Handle %s in new tree order is not in project tree" % tHandle)
|
||||
|
||||
# Do a reverse lookup to check for items that will be lost
|
||||
# This is mainly for debugging purposes
|
||||
for tHandle in self._treeOrder:
|
||||
if tHandle not in tmpOrder:
|
||||
logger.warning("Handle %s in old tree order is not in new tree order" % tHandle)
|
||||
|
||||
# Save the temp list
|
||||
self._treeOrder = tmpOrder
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
logger.verbose("Project tree order updated")
|
||||
|
||||
return
|
||||
|
||||
def setSeed(self, theSeed):
|
||||
"""Used for debugging!
|
||||
Sets a seed for generating handles so that they always come out
|
||||
in a predictable order.
|
||||
"""
|
||||
self._handleSeed = theSeed
|
||||
return
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
|
||||
def countTypes(self):
|
||||
"""Count the number of files, folders and roots in the project.
|
||||
"""
|
||||
nRoot = 0
|
||||
nFolder = 0
|
||||
nFile = 0
|
||||
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
continue
|
||||
elif tItem.itemType == nwItemType.ROOT:
|
||||
nRoot += 1
|
||||
elif tItem.itemType == nwItemType.FOLDER:
|
||||
nFolder += 1
|
||||
elif tItem.itemType == nwItemType.FILE:
|
||||
nFile += 1
|
||||
|
||||
return nRoot, nFolder, nFile
|
||||
|
||||
##
|
||||
# Meta Methods
|
||||
##
|
||||
|
||||
def __len__(self):
|
||||
"""Return the length counter. Does not check that it is correct!
|
||||
"""
|
||||
return self._theLength
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns True if the tree has any entries.
|
||||
"""
|
||||
return self._theLength > 0
|
||||
|
||||
##
|
||||
# Item Access Methods
|
||||
##
|
||||
|
||||
def __getitem__(self, tHandle):
|
||||
"""Return a project item based on its handle. Returns None if
|
||||
the handle doesn't exist in the project.
|
||||
"""
|
||||
if tHandle in self._projTree:
|
||||
return self._projTree[tHandle]
|
||||
logger.error("No tree item with handle %s" % str(tHandle))
|
||||
return None
|
||||
|
||||
def __delitem__(self, tHandle):
|
||||
"""This only removes the item from the order list, but not from
|
||||
the project tree.
|
||||
"""
|
||||
if tHandle not in self._treeOrder:
|
||||
logger.warning(
|
||||
"Could not remove item %s from project tree as it does not exist" % tHandle
|
||||
)
|
||||
return False
|
||||
self._treeOrder.remove(tHandle)
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
return True
|
||||
|
||||
def __contains__(self, tHandle):
|
||||
"""Checks if a handle exists in the tree.
|
||||
"""
|
||||
return tHandle in self._treeOrder
|
||||
|
||||
##
|
||||
# Iterator Methods
|
||||
##
|
||||
|
||||
def __iter__(self):
|
||||
"""Initiates the iterator.
|
||||
"""
|
||||
self._theIndex = 0
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
"""Returns the item from the next entry in the _treeOrder list.
|
||||
"""
|
||||
if self._theIndex < self._theLength:
|
||||
theItem = self.__getitem__(self._treeOrder[self._theIndex])
|
||||
self._theIndex += 1
|
||||
return theItem
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _setTreeChanged(self, theState):
|
||||
"""Set the changed flag to theState, and if being set to True,
|
||||
propagate that state change to the parent NWProject class.
|
||||
"""
|
||||
self._treeChanged = theState
|
||||
if theState:
|
||||
self.theProject.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def _makeHandle(self, addSeed=""):
|
||||
"""Generate a unique item handle. In the unlikely event that the
|
||||
key already exists, salt the seed and generate a new handle.
|
||||
"""
|
||||
if self._handleSeed is None:
|
||||
newSeed = str(time()) + addSeed
|
||||
else:
|
||||
# This is used for debugging
|
||||
newSeed = str(self._handleSeed)
|
||||
self._handleSeed += 1
|
||||
logger.verbose("Generating handle with seed '%s'" % newSeed)
|
||||
itemHandle = sha256(newSeed.encode()).hexdigest()[0:13]
|
||||
if itemHandle in self._projTree:
|
||||
logger.warning("Duplicate handle encountered! Retrying ...")
|
||||
itemHandle = self._makeHandle(addSeed+"!")
|
||||
return itemHandle
|
||||
|
||||
# END Class NWTree
|
||||
Reference in New Issue
Block a user