def readDB(self): from CondDBUI import CondDB db = CondDB(self.testDB) s = self.testFolder if not db.db.existsFolder(s): return "" data = db.getPayload(s, 0, 0) if 'data' not in data: return "" return data['data']
def createSlice(self): d = CreateSliceDialog(self) partName = str(self.db.db.databaseName()) i = d.partition.findText(partName) if i >= 0: d.partition.setCurrentIndex(i) else: d.partition.lineEdit().setText(partName) if d.exec_(): try: selectionList = [PyCoolCopy.Selection(path, since, until, tags = tags) for path, since, until, tags in d.selectionsModel.selections] connStr = str(d.connectionString()) copyTool = PyCoolCopy.PyCoolCopy(self.db.db) # reduce the verbosity of PyCoolCopy PyCoolCopy.log.setLevel(PyCoolCopy.logging.WARNING) from Utils import BusyCursor _bc = BusyCursor() if d.append.isChecked(): copyTool.append(connStr, selectionList) else: # FIXME : https://sft.its.cern.ch/jira/browse/ROOT-5603 targetDb = CondDB(connStr,create_new_db=True,readOnly=False).db copyTool.copy(targetDb, selectionList) except: self.exceptionDialog()
def openDatabase(self, connString, readOnly = True): try: # Clean the central view to avoid that old data stays there self.dataView.clear() if connString: try: self.db = CondDB(connString, readOnly = readOnly) except Exception as x: from functools import reduce msg = reduce(lambda x,y: x + y, list(x.args), '') if msg.startswith("Database not found:"): QMessageBox.critical(self, "Cannot open database", msg) return raise title = "%s - %s" % (connString, self.appName) # Notify the widgets that the database is open self.emit(SIGNAL("databaseOpen(bool)"), True) # Notify the widgets if the database is open in read-only mode editable = not readOnly self.emit(SIGNAL("databaseOpenReadOnly(bool)"), bool(readOnly)) self.menuEdit.setEnabled(editable) self.menuAdvanced.setEnabled(editable) if editable: # The "Delete Node" entry should be active only # when a node is selected (when opening a new db there is # no selection) self.actionDelete_node.setEnabled(False) # ... same for the "new tag" and "delete tag" self.actionNew_tag.setEnabled(False) self.actionDelete_tag.setEnabled(False) else: self.db = None title = self.appName # Notify the widgets that there is no database open self.emit(SIGNAL("databaseOpen(bool)"), False) self.menuEdit.setEnabled(False) self.menuAdvanced.setEnabled(False) self.setWindowTitle(title) self._path = None # remember the used connection string self._connectionString = connString self._addToRecents(connString) # update the DB instance of the models self.emit(SIGNAL("openedDB"), self.db) except: self.exceptionDialog()
def newDatabaseDialog(self): dd = NewDatabaseDialog(self) if dd.exec_(): connString = str(dd.connectionString()) # Create an empty database CondDB(connString, readOnly = False, create_new_db = True) # and open it in read/write mode self.openDatabase(connString, readOnly = False)
def connect2db(destination, readOnly=True): """Function to connect to CondDB. Returns db instance""" destination = destination.lower() from CondDBUI import CondDB if destination == 'sqlite': return CondDB('sqlite_file:%s/DQFLAGS.db/DQFLAGS' % os.environ['SQLITEDBPATH'], readOnly=readOnly) elif destination == 'oracle': # TODO: connect to Cern Oracle database raise Exception, ( "Sorry, DQ flag commitment to the Oracle db is not yet implemented." ) else: raise Exception, "Destination '%s' is unknown. Allowed are 'sqlite' and 'oracle'." % destination
def DumpToFiles(database, time=0, tag="HEAD", srcs=['/'], destroot='DDDB', force=False, addext=False, monitor=None): _log = logging.getLogger("CondDBUI.Admin.DumpToFiles") _log.debug("called with arguments: %s", repr((database, time, tag, srcs, destroot, force, addext))) if monitor is None: monitor = _dummyMonitor() from CondDBUI import CondDB import re if type(database) is str: # Connect to the database db = CondDB(database, defaultTag=tag) _log.debug("connected to database '%s'", database) else: # Let's assume we were given a CondDBUI.CondDB instance db = database # @note: This piece of code is needed if we want to allow only global tags # # Check the validity of the tag # if not db.isTagReady(tag): # raise RuntimeError("Tag '%s' is not a valid global tag."%tag) # Collect the list of folders we are going to use. nodes = [] for s in srcs: if db.db.existsFolder(s): nodes.append(s) elif db.db.existsFolderSet(s): nodes += db.getAllChildNodes(s) else: _log.warning("Node '%s' does not exist. Ignored", s) nodes = [n for n in set(nodes) if db.db.existsFolder(n)] nodes.sort() monitor.setMaximum(len(nodes)) # matching: SYSTEM "blah" sysIdRE = re.compile('SYSTEM[^>"\']*("[^">]*"|' + "'[^'>]*')") # matching: href "conddb:blah" hrefRE = re.compile('href *= *("conddb:[^">]*"|' + "'conddb:[^'>]*')") value = 0 for node in (n for n in nodes if db.db.existsFolder(n)): if monitor.wasCanceled(): break monitor.setValue(value) monitor.setLabelText(node) _log.debug("retrieve data from '%s'", node) f = db.getCOOLNode(node) channels = [i for i in f.listChannels()] if len(channels) > 1: _log.debug("processing %d channels", len(channels)) for ch in channels: monitor.setValue(value) try: data = db.getPayload(node, time, channelID=ch, tag=tag) nodesplit = node.split('/') nodebase = '/'.join(nodesplit[:-1]) nodename = nodesplit[-1] if '/' != os.sep: tmppath = nodebase.replace('/', os.sep) else: tmppath = nodebase if tmppath and (tmppath[0] == os.sep): tmppath = tmppath[1:] dirname = os.path.join(destroot, tmppath) if not os.path.isdir(dirname): _log.debug("create directory '%s'", dirname) os.makedirs(dirname) _log.debug("looping over data keys") for key, xml in data.items(): _log.debug("key: '%s'", key) filename = nodename if key != 'data': filename = '%s@%s' % (key, nodename) # Let's assume that if there is more than 1 channel also the "0" is used explicitly if ch != 0 or len(channels) > 1: filename += ':%d' % ch tmppath = os.path.join(dirname, filename) if not force and os.path.exists(tmppath): _log.warning("file '%s' already exists: skipping", tmppath) continue _log.debug("fixating XML") # fix system IDs xml = _fixate_string(xml, sysIdRE, _relativize_url(node, key, nodebase)) # fix hrefs pointing to absolute conddb urls xml = _fixate_string(xml, hrefRE, _relativize_url(node, key, nodebase)) if tmppath.endswith( os.path.join("Conditions", "MainCatalog.xml")): # make the href to Online point to the DB xml = xml.replace('"Online"', '"conddb:/Conditions/Online"') _log.debug("write '%s'", tmppath) open(tmppath, 'w').write(xml) except RuntimeError, x: desc = str(x) if "Object not found" in desc: _log.info("no data in %s", node) elif "No child tag" in desc: _log.info("tag not found in %s", node) elif "not a child of" in desc: # tag exists in a folderset not containing the current one _log.info("tag not found in %s", node) elif 'Attempt to access data of NULL attribute "NODE_PARENTID"' in desc: pass else: raise value += 1
def main(argv): # Configure the parser from optparse import OptionParser default_options_file = os.path.join(os.environ["SQLDDDBROOT"], "options", "SQLDDDB.py") parser = OptionParser( usage="%prog [options] partition destination", version=__version__, description= """This script produces a snapshot of the specified partition of the CondDB. The argument 'partition' can be the partition name (the connection string is guessed from standard or provided job options), an SQLite file or a COOL connection string. 'destination' must be a connection string.""") parser.add_option("-T", "--tag", type="string", action="append", dest="tags", help="Tag to use for the snapshot. [default = %default]") parser.add_option( "--options", type="string", action="append", help="Options files to use to guess the mapping from the " "partition name to the actual connection string. " "[default = %s]" % default_options_file) parser.add_option( "-s", "--since", type="string", help="Start of the interesting Interval Of Validity (local time). " "Format: YYYY-MM-DD[_HH:MM[:SS.SSS]][UTC]") parser.add_option( "-u", "--until", type="string", help="End of the interesting Interval Of Validity (local time) " "Format: YYYY-MM-DD[_HH:MM[:SS.SSS]][UTC]") parser.add_option("--merge", action="store_true", help="Whether to merge the data into an existing DB") parser.add_option( "-F", "--to-files", action="store_true", help="Write the snapshot to XML files instead of a database. " "When this option is enabled, the option '--since' is used " "to define the event time at which the snapshot has to be " "valid and <destination> is interpreted as the directory " "where to create the files. If no '--since' is specified, " "the current time is used.") parser.add_option("-v", "--verbose", action="store_true", help="Print more messages") #parser.add_option("-n","--dry-run", action = "store_true", # help = "Do not create the output file." # ) parser.set_defaults(tags=[], options=[], merge=False, verbose=False, to_files=False) # parse command line options, args = parser.parse_args(args=argv[1:]) # check arguments if len(args) != 2: parser.error("not enough arguments. Try with --help.") # Prepare logger import logging log = logging.getLogger(parser.prog or os.path.basename(sys.argv[0])) if not options.verbose: log.setLevel(logging.INFO) else: log.setLevel(logging.DEBUG) _fixLogger() # use the global handler if not options.options: options.options.append(default_options_file) source = os.path.expandvars(guessConnectionString(args[0], options.options)) dest = os.path.expandvars(args[1]) # Import the heavy modules from PyCool import cool from CondDBUI.Admin import timeToValKey from time import time, ctime, asctime, gmtime try: if options.to_files: # special checks on options for --to-files if options.until: parser.error( "conflicting options '--until' and '--to-files'. Try with --help." ) if not options.tags: tag = "HEAD" else: # If many tags are give, use only the last one tag = options.tags[-1] since = timeToValKey(options.since, int(time() * 1e9)) log.info("Dumping %s to %s" % (source, dest)) log.info("Event Time (loc): %s" % ctime(since / 1e9)) log.info("Event Time (UTC): %s" % asctime(gmtime(since / 1e9))) log.info("Tag: %s" % tag) log.info("Copying, please wait...") from CondDBUI.Admin import DumpToFiles DumpToFiles(connString=source, time=since, tag=tag, destroot=dest, force=False) else: since = timeToValKey(options.since, cool.ValidityKeyMin) until = timeToValKey(options.until, cool.ValidityKeyMax) log.info("Cloning %s to %s" % (source, dest)) log.info("IoV(loc): %s to %s" % (ctime(since / 1e9), ctime(until / 1e9))) log.info( "IoV(UTC): %s to %s" % (asctime(gmtime(since / 1e9)), asctime(gmtime(until / 1e9)))) log.info("Tags: %s" % options.tags) log.info("Copying, please wait...") from PyCoolCopy import Selection, PyCoolCopy, log as pcc_log # Avoid multiple logging handlers pcc_log.handlers = [] if not options.verbose: pcc_log.setLevel(logging.WARNING) else: pcc_log.setLevel(logging.INFO) from CondDBUI import CondDB # FIXME: not needed because LFCReplicaService was dropped # Connect to source database (PyCoolCopy do not support CORAL LFCReplicaService) sourceDb = CondDB(source).db selection = Selection(since=since, until=until, tags=options.tags) if options.merge: log.info( "Warning: merge works only on folders that do not exist on the target" ) PyCoolCopy(sourceDb).append(dest, selection) else: # FIXME : https://sft.its.cern.ch/jira/browse/ROOT-5603 targetDb = CondDB(dest, create_new_db=True, readOnly=False).db PyCoolCopy(sourceDb).copy(targetDb, selection) log.info("Copy completed.") except Exception, details: log.error('Copy failed with error: %s' % str(details))
from PyCool import cool from CondDBUI import CondDB from subprocess import Popen from pprint import pformat filename = "know_priv_nodes.py" try: cache = eval(open(filename).read()) except IOError, x: if x.errno == 2: cache = {} else: raise for connStr in ["CondDBPrivate(owner)/PRIVATE", "CondDBOnline(owner)/ONLINE"]: db = CondDB(connStr) new_list = db.getAllNodes() if new_list != cache.get(connStr, []): proc = Popen([ "coolPrivileges", connStr, "GRANT", "READER", "lhcb_conddb_reader" ]) #proc = Popen("echo " + " ".join(["coolPrivileges", '"%s"' % connStr, "GRANT", "READER", "lhcb_conddb_reader"]), shell = True) proc.wait() cache[connStr] = db.getAllNodes() open(filename, "w").write(pformat(cache) + "\n")
""" Small script to generate the test database for the run stamp test. (kept for reference) """ from CondDBUI import CondDB from PyCool import cool def ts(*args): from datetime import datetime epoch = datetime(1970, 1, 1) return int((datetime(*args) - epoch).total_seconds() * 1000000000) db = CondDB("sqlite_file:RSTEST.db/RSTEST", readOnly=False, create_new_db=True) db.createNode("/Conditions", storageType="NODE") db.createNode("/Conditions/Online", storageType="NODE") db.createNode("/Conditions/Online/LHCb", storageType="NODE") db.createNode("/Conditions/Online/LHCB/RunInfo", versionMode="SINGLE") db.createNode("/Conditions/Online/LHCb/RunStamp.xml", versionMode="SINGLE") rs = """<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE DDDB SYSTEM "conddb:/DTD/structure.dtd"> <DDDB> <condition name="RunStamp"> <param name="RunNumber" type="int">1</param> </condition> </DDDB> """
#!/usr/bin/env python """ Small script to generate the test database for the heart beat test. (kept for reference) """ from CondDBUI import CondDB from PyCool import cool base_time = 1262304000 # (2010, 1, 1, 0, 0, 0, 4, 1, 0) GMT unit = 60 db = CondDB("sqlite_file:HBTEST.db/HBTEST", readOnly = False, create_new_db = True) db.createNode("/Conditions", storageType = "NODE") db.createNode("/Conditions/Online", storageType = "NODE") db.createNode("/Conditions/Online/HeartBeatTest", storageType = "NODE") db.createNode("/Conditions/Online/HeartBeatTest/Condition1", versionMode = "SINGLE") db.createNode("/Conditions/Online/HeartBeatTest/Condition2", versionMode = "SINGLE") db.createNode("/Conditions/Online/HeartBeatTest/Tick", versionMode = "SINGLE") cond = """<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE DDDB SYSTEM "conddb:/DTD/structure.dtd"> <DDDB> <condition name = "Condition%d"> <param name = "Data" type = "int"> %d </param> </condition> </DDDB> """ db.storeXMLString("/Conditions/Online/HeartBeatTest/Condition1", cond % (1, 0), 0, cool.ValidityKeyMax)
Small script to generate the test database for the heart beat test. (kept for reference) """ from CondDBUI import CondDB from PyCool import cool from datetime import datetime, timedelta def toTimeStamp(dt): t = dt - datetime(1970, 1, 1, 0) return (t.days * 60 * 60 * 24 + t.seconds) * 1000000000 db = CondDB("sqlite_file:DQFLAGS.db/DQFLAGS", readOnly=False, create_new_db=True) db.createNode("/Conditions", storageType="NODE") db.createNode("/Conditions/DQ", storageType="NODE") db.createNode("/Conditions/DQ/Flags", versionMode="SINGLE") def cond(d): data = ['<item key="%s" value="%d"/>' % i for i in d.items()] return """<?xml version='1.0' encoding='ISO-8859-1'?> <!DOCTYPE DDDB SYSTEM "conddb:/DTD/structure.dtd"> <DDDB> <condition name="Flags"> <map keytype="string" name="map" valuetype="int"> %s
class MainWindow(QMainWindow, Ui_MainWindow): ## Constructor. # Initialises the base class and defines some internal structures. def __init__(self, parent = None, flags = Qt.Widget): # Base class constructor. super(MainWindow, self).__init__(parent, flags) # Application and organization names app = QApplication.instance() self.appName = str(app.applicationName()) self.appOrg = str(app.organizationName()) # Preconfigured databases self.defaultDatabases = {} # action # Icons for the model (property) self._icons = None # Current selected (path, channel) in the database self._path = (None, None) # The database self.db = None # Current connection string. Needed because the copy in # self.db has the variables expanded. self._connectionString = None # Maximum number of entries in the list of recent databases self.maxRecentEntries = 10 # Whether to show the welcome message self._showWelcome = True # set in readSettings # External editor, to be passed to the add condition dialog self._externalEditor = "emacs" # set in readSettings # Prepare the GUI. self.setupUi(self) # --- Part of the initialization that require the GUI objects. --- # Window title self.setWindowTitle(self.appName) # Connect the models self.models = { "tree": CondDBStructureModel(), "nodes": CondDBNodesListModel(), "tags": CondDBTagsListModel(), "iovs": CondDBIoVModel(), "fields": CondDBPayloadFieldModel(), } setModelsIcons(self.icons) self.hierarchyTreeView.setModel(self.models["tree"]) self.pathComboBox.setModel(self.models["nodes"]) self.tagComboBox.setModel(self.models["tags"]) self.iovView.setModel(self.models["iovs"]) self.fieldsView.setModel(self.models["fields"]) for m in [self.models[n] for n in ["tree", "nodes", "iovs", "fields"]]: QObject.connect(self, SIGNAL("openedDB"), m.connectDB) QObject.connect(self, SIGNAL("openedDB"), tagsGlobalCache.setDB) QObject.connect(self.hierarchyTreeView.selectionModel(), SIGNAL("currentChanged(QModelIndex,QModelIndex)"), self.selectedItem) # special settings for the tags model tagsmodel = self.models["tags"] QObject.connect(self, SIGNAL("changedPath"), tagsmodel.setPath) QObject.connect(self.hideAutoCheckBox, SIGNAL("stateChanged(int)"), tagsmodel.setHideAutoTags) QObject.connect(tagsmodel, SIGNAL("setViewEnabled(bool)"), self.tagComboBox, SLOT("setEnabled(bool)")) QObject.connect(tagsmodel, SIGNAL("setViewEnabled(bool)"), self.hideAutoCheckBox, SLOT("setEnabled(bool)")) tagsmodel.setHideAutoTags(self.hideAutoCheckBox.checkState()) # connection for the iovModel iovsmodel = self.models["iovs"] QObject.connect(self, SIGNAL("changedPathChannel"), iovsmodel.setPathChannel) QObject.connect(self.sinceFilterWidget, SIGNAL("validityKeyChange"), iovsmodel.setSince) QObject.connect(self.untilFilterWidget, SIGNAL("validityKeyChange"), iovsmodel.setUntil) QObject.connect(self.tagComboBox, SIGNAL("currentIndexChanged(QString)"), iovsmodel.setTag) QObject.connect(iovsmodel, SIGNAL("setViewEnabled(bool)"), self.iovView, SLOT("setEnabled(bool)")) self.iovView.setEnabled(False) self.iovView.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents) QObject.connect(self.iovView.selectionModel(), SIGNAL("currentChanged(QModelIndex,QModelIndex)"), iovsmodel.selectionChanged) QObject.connect(iovsmodel, SIGNAL("setCurrentIndex(QModelIndex,QItemSelectionModel::SelectionFlags)"), self.iovView.selectionModel(), SLOT("setCurrentIndex(QModelIndex,QItemSelectionModel::SelectionFlags)")) QObject.connect(self.iovUTCCheckBox, SIGNAL("stateChanged(int)"), iovsmodel.setShowUTC) iovsmodel.setShowUTC(self.iovUTCCheckBox.checkState()) # Use a consistent DisplayFormat iovsmodel.setDisplayFormat(self.sinceFilterWidget.displayFormat()) # connection for the fields model fieldsmodel = self.models["fields"] QObject.connect(self, SIGNAL("changedPath"), fieldsmodel.setPath) #QObject.connect(fieldsmodel, SIGNAL("setViewEnabled(bool)"), self.fieldsView, SLOT("setEnabled(bool)")) #self.fieldsView.setEnabled(False) QObject.connect(fieldsmodel, SIGNAL("setViewEnabled(bool)"), self.payloadGroupBox, SLOT("setVisible(bool)")) self.payloadGroupBox.setVisible(False) QObject.connect(self.fieldsView.selectionModel(), SIGNAL("currentChanged(QModelIndex,QModelIndex)"), fieldsmodel.selectionChanged) QObject.connect(fieldsmodel, SIGNAL("setCurrentIndex(QModelIndex,QItemSelectionModel::SelectionFlags)"), self.fieldsView.selectionModel(), SLOT("setCurrentIndex(QModelIndex,QItemSelectionModel::SelectionFlags)")) # Filter panel # Default startup values for the IOV filter. self.sinceFilterWidget.setMaxEnabled(False) self.sinceFilterWidget.setDateTime(QDateTime.currentDateTime().addMonths(-1)) #iovsmodel.setSince(self.sinceFilterWidget.toValidityKey()) self.untilFilterWidget.setMaxChecked(True) #iovsmodel.setUntil(self.untilFilterWidget.toValidityKey()) # When created, we have to ensure that everything is grayed out. self.emit(SIGNAL("databaseOpen(bool)"), False) self.menuEdit.setEnabled(False) self.menuAdvanced.setEnabled(False) # Triggers for the update of the central panel QObject.connect(self.iovView.selectionModel(), SIGNAL("currentChanged(QModelIndex,QModelIndex)"), self.showData) QObject.connect(self.fieldsView.selectionModel(), SIGNAL("currentChanged(QModelIndex,QModelIndex)"), self.showData) # Add the list of actions to show/hide the panels self.menuPanels.addAction(self.browsePanel.toggleViewAction()) self.menuPanels.addAction(self.filterPanel.toggleViewAction()) self.readSettings() if self._showWelcome: self._showWelcome = self.showWelcomeInfo(cancel = True) ## Store settings into the configuration file def writeSettings(self): settings = QSettings() settings.beginGroup("MainWindow") settings.setValue("Size", QVariant(self.size())) settings.setValue("Pos", QVariant(self.pos())) settings.endGroup() settings.beginGroup("BrowsePanel") settings.setValue("Visible", QVariant(self.browsePanel.isVisible())) settings.setValue("Floating", QVariant(self.browsePanel.isFloating())) settings.setValue("Size", QVariant(self.browsePanel.size())) settings.setValue("Pos", QVariant(self.browsePanel.pos())) settings.endGroup() settings.beginGroup("FilterPanel") settings.setValue("Visible", QVariant(self.filterPanel.isVisible())) settings.setValue("Floating", QVariant(self.filterPanel.isFloating())) settings.setValue("Size", QVariant(self.filterPanel.size())) settings.setValue("Pos", QVariant(self.filterPanel.pos())) settings.endGroup() settings.beginGroup("DataView") settings.setValue("FixedWidthFont", QVariant(self.dataView.isFixedWidthFont())) settings.endGroup() settings.beginGroup("FindDialog") d = self.dataView.findDialog settings.setValue("Visible", QVariant(d.isVisible())) settings.setValue("Pos", QVariant(d.pos())) settings.setValue("Flags", QVariant(d.getFindFlags())) settings.setValue("WrappedSearch", QVariant(d.getWrappedSearch())) settings.endGroup() settings.setValue("IOVs/UTC", QVariant(self.iovUTCCheckBox.isChecked())) settings.beginWriteArray("Recent") recents = self.menuRecent.actions() i = 0 for action in recents: settings.setArrayIndex(i) settings.setValue("ConnString", QVariant(action.text())) i += 1 settings.endArray() settings.beginGroup("Misc") settings.setValue("ShowWelcome", QVariant(self._showWelcome)) settings.setValue("ExternalEditor", QVariant(self._externalEditor)) settings.endGroup() ## Load settings from the configuration file def readSettings(self): settings = QSettings() settings.beginGroup("MainWindow") self.resize(settings.value("Size", QVariant(QSize(965, 655))).toSize()) self.move(settings.value("Pos", QVariant(QPoint(0, 0))).toPoint()) settings.endGroup() settings.beginGroup("BrowsePanel") self.browsePanel.setVisible(settings.value("Visible", QVariant(True)).toBool()) self.browsePanel.setFloating(settings.value("Floating", QVariant(False)).toBool()) self.browsePanel.resize(settings.value("Size", QVariant(QSize(250, 655))).toSize()) self.browsePanel.move(settings.value("Pos", QVariant(QPoint(0, 0))).toPoint()) settings.endGroup() settings.beginGroup("FilterPanel") self.filterPanel.setVisible(settings.value("Visible", QVariant(True)).toBool()) self.filterPanel.setFloating(settings.value("Floating", QVariant(False)).toBool()) self.filterPanel.resize(settings.value("Size", QVariant(QSize(270, 655))).toSize()) self.filterPanel.move(settings.value("Pos", QVariant(QPoint(0, 0))).toPoint()) settings.endGroup() settings.beginGroup("DataView") self.dataView.setFixedWidthFont(settings.value("FixedWidthFont", QVariant(False)).toBool()) settings.endGroup() settings.beginGroup("FindDialog") d = self.dataView.findDialog d.setVisible(settings.value("Visible", QVariant(False)).toBool()) d.move(settings.value("Pos", QVariant(QPoint(0, 0))).toPoint()) # Note: QVariant.toInt returns a tuple with the result of the conversion # and a boolean for the successful conversion d.setFindFlags(settings.value("Flags", QVariant(0)).toInt()[0]) d.setWrappedSearch(settings.value("WrappedSearch", QVariant(True)).toBool()) settings.endGroup() self.iovUTCCheckBox.setChecked(settings.value("IOVs/UTC", QVariant(True)).toBool()) size = settings.beginReadArray("Recent") for i in range(size): settings.setArrayIndex(i) conn = settings.value("ConnString").toString() action = QAction(self) action.setText(conn) QObject.connect(action, SIGNAL("triggered()"), self.openRecentDatabase) self.menuRecent.addAction(action) settings.endArray() settings.beginGroup("Misc") self._showWelcome = settings.value("ShowWelcome", QVariant(True)).toBool() self._externalEditor = str(settings.value("ExternalEditor", QVariant("emacs")).toString()) settings.endGroup() ## Close Event handler def closeEvent(self, _event): self.writeSettings() ## Fills the menu of standard databases from the connString dictionary. # @see getStandardConnectionStrings() def setDefaultDatabases(self, connStrings): self.menuStandard.clear() self.defaultDatabases = {} names = connStrings.keys() names.sort() for name in names: conn = connStrings[name] action = QAction(self) action.setText(name) action.setData(QVariant(conn)) action.setStatusTip(conn) QObject.connect(action, SIGNAL("triggered()"), self.openStandardDatabase) self.menuStandard.addAction(action) self.defaultDatabases[name] = action self.menuStandard.setEnabled(not self.menuStandard.isEmpty()) ## Slot called by the actions in the menu "Database->Standard". # It can also be called passing the name of one of those databases. def openStandardDatabase(self, name = None, readOnly = True): if name is None: sender = self.sender() else: if name in self.defaultDatabases: sender = self.defaultDatabases[name] else: QMessageBox.critical(self, "Database nickname not known", "The conditions database '%s' is not in the list of known database." % name) return # Open the database using the connection string in the action self.openDatabase(str(sender.data().toString()), readOnly = readOnly) ## Slot called by the actions in the menu "Database->Recent" def openRecentDatabase(self): sender = self.sender() self.openDatabase(str(sender.text())) ## Open the "create new database" dialog box def newDatabaseDialog(self): dd = NewDatabaseDialog(self) if dd.exec_(): connString = str(dd.connectionString()) # Create an empty database CondDB(connString, readOnly = False, create_new_db = True) # and open it in read/write mode self.openDatabase(connString, readOnly = False) ## Open the "open database" dialog box def openDatabaseDialog(self): dd = OpenDatabaseDialog(self) if dd.exec_(): connString = str(dd.connectionString()) self.openDatabase(connString, dd.readOnly()) ## Add a connection string to the list of the recent opened databases def _addToRecents(self, connString): # do nothing if there is no connection string if not connString: return # action to add to the menu action = None # check if the connection string is already in the list for oldAction in self.menuRecent.actions(): if oldAction.text() == connString: # if it is found, remove it from the list and use it as action to add action = oldAction self.menuRecent.removeAction(action) break # if the action was not found in the menu, create a new one if action is None: action = QAction(self) action.setText(connString) QObject.connect(action, SIGNAL("triggered()"), self.openRecentDatabase) # if the menu is not empty if self.menuRecent.actions(): # add the action at the beginning of the list self.menuRecent.insertAction(self.menuRecent.actions()[0], action) # and remove the entries exceeding the maximum while len(self.menuRecent.actions()) > self.maxRecentEntries: self.menuRecent.removeAction(self.menuRecent.actions()[-1]) else: # otherwise just add the action self.menuRecent.addAction(action) ## Open the database identified by a connection string or by a nickname. # If name is an empty string (or None) the result is a disconnection from the # current database. # # @param connString connection string # @param readOnly flag to select if the database has to be opened in read-only mode or in read/write mode def openDatabase(self, connString, readOnly = True): try: # Clean the central view to avoid that old data stays there self.dataView.clear() if connString: try: self.db = CondDB(connString, readOnly = readOnly) except Exception as x: from functools import reduce msg = reduce(lambda x,y: x + y, list(x.args), '') if msg.startswith("Database not found:"): QMessageBox.critical(self, "Cannot open database", msg) return raise title = "%s - %s" % (connString, self.appName) # Notify the widgets that the database is open self.emit(SIGNAL("databaseOpen(bool)"), True) # Notify the widgets if the database is open in read-only mode editable = not readOnly self.emit(SIGNAL("databaseOpenReadOnly(bool)"), bool(readOnly)) self.menuEdit.setEnabled(editable) self.menuAdvanced.setEnabled(editable) if editable: # The "Delete Node" entry should be active only # when a node is selected (when opening a new db there is # no selection) self.actionDelete_node.setEnabled(False) # ... same for the "new tag" and "delete tag" self.actionNew_tag.setEnabled(False) self.actionDelete_tag.setEnabled(False) else: self.db = None title = self.appName # Notify the widgets that there is no database open self.emit(SIGNAL("databaseOpen(bool)"), False) self.menuEdit.setEnabled(False) self.menuAdvanced.setEnabled(False) self.setWindowTitle(title) self._path = None # remember the used connection string self._connectionString = connString self._addToRecents(connString) # update the DB instance of the models self.emit(SIGNAL("openedDB"), self.db) except: self.exceptionDialog() ## Re-open the current database, changing the readOnly flag. # If the database is already in the correct mode, nothing is done. def reopenDatabase(self, readOnly): path = self._path if readOnly != self.db.readOnly: self.openDatabase(self._connectionString, readOnly) if path: self._selectPath(path[0]) ## Disconnect from the database. # @see: openDatabase() def closeDatabase(self): self.openDatabase(None) ## Display the standard Qt information dialog box def aboutQt(self): QMessageBox.aboutQt(self) ## Display the application information dialog box def aboutDialog(self): app = QApplication.instance() message = '''<p><b>%s</b><br/>%s</p> <p>Browser for the LHCb-COOL condition database.</p> <p>This is build on top of the Python API to LHCb-COOL: CondDBUI, and the Python API to COOL: PyCool.</p> <p>The Graphical Library is PyQt %s, based on Qt %s</p> <p><i>Marco Clemencic, Nicolas Gilardi</i></p>''' \ % (app.objectName(), app.applicationVersion(), PYQT_VERSION_STR, qVersion()) QMessageBox.about(self, app.objectName(), message) ## Slots to react to a selected item in the structure view def selectedItem(self, index): item = index.internalPointer() if self._path != (item.path, item.channel): try: i = self.models["nodes"].nodes.index(item.path) self._path = (item.path, item.channel) self.emit(SIGNAL("changedPath"), item.path) if item.leaf and not item.children: self.emit(SIGNAL("changedPathChannel"), item.path, item.channel) else: self.emit(SIGNAL("changedPathChannel"), None, None) # Nodes can be deleted only if the database is in r/w mode and # they are Folders or empty FolderSets self.actionDelete_node.setEnabled(not self.db.readOnly and (item.leaf or not item.children)) # Tags manipulation should be enabled only for FolderSets # and multi-version folders taggable = (not self.db.readOnly and ((not item.leaf) or (not item.singleVersion))) self.actionNew_tag.setEnabled(taggable) self.actionDelete_tag.setEnabled(taggable) except ValueError: i = -1 self.pathComboBox.setCurrentIndex(i) ## Slots to react to a selected entry in the path combo box def selectedPath(self, path): path = str(path) if self._path != (path, None) and path: index = self.models["tree"].findPath(path) self.hierarchyTreeView.setCurrentIndex(index) self._path = (path, None) item = index.internalPointer() self.emit(SIGNAL("changedPath"), item.path) if item.leaf and not item.children: self.emit(SIGNAL("changedPathChannel"), path, None) else: self.emit(SIGNAL("changedPathChannel"), None, None) # Nodes can be deleted only if they are Folders or empty FolderSets self.actionDelete_node.setEnabled(item.leaf or not item.children) ## Return the cool::FolderSet object currently selected in the structure tree. # If the selected item is not a FolderSet, the parent is returned. # If there is not database opened, returns None. def getSelectedFolderSet(self): if not self.db: return None # Get the FolderSet currently selected in the tree view ("/" if none) idx = self.hierarchyTreeView.currentIndex() while idx.isValid() and idx.internalPointer().leaf: idx = idx.internalPointer().parent.index if idx.isValid(): currentFolderSet = idx.internalPointer().node else: currentFolderSet = self.models["tree"].root.node return currentFolderSet def testSlot(self): print "Test slot triggered" @property def icons(self): if self._icons is None: self._icons = {} style = QApplication.instance().style() for iconName, iconId in [ ("folderset", QStyle.SP_DirIcon), ("folder", QStyle.SP_FileIcon), ("up", QStyle.SP_ArrowUp), ("down", QStyle.SP_ArrowDown), ]: self._icons[iconName] = style.standardIcon(iconId) return self._icons ## This function gets called if any selection changes and decides if there # is something to show in the central widget. def showData(self): data = "" payload = self.models["iovs"].getPayload() if payload: field = self.models["fields"].getFieldName() # It may happen (it actually often does) that the function is # called for a change in the field before the IoVs are updated, so # it is better to check if the field exists in the payload. if field in payload: data = payload[field] self.dataView.setPlainText(data) ## Show a critical dialog with the latest exception traceback. # @param source string representing the function that trapped the exception. def exceptionDialog(self, source = None): import traceback if source: msg = "Called from '%s':\n" % source else: msg = "" msg += traceback.format_exc() print msg QMessageBox.critical(self, "Exception in Python code", msg) ## Helper function useful to force a refresh of the models and the selection # of node if specified. def _refreshModels(self, selectPath = None): # trigger a refresh of the caches in the models self.emit(SIGNAL("openedDB"), self.db) self._selectPath(selectPath) ## Helper function to select a path in both the combo box and the hierarchy # view. def _selectPath(self, selectPath = None): if selectPath: # select the specified path i = self.pathComboBox.findText(selectPath) self.pathComboBox.setCurrentIndex(i) self.selectedPath(selectPath) ## Add a new node to the database def newNodeDialog(self): fs = self.getSelectedFolderSet() fsname = str(fs.fullPath()) if fsname != "/": fsname += "/" d = NewNodeDialog(self) folders = set() foldersets = set() for n in self.models["nodes"].nodes: if self.db.db.existsFolder(n): folders.add(n) else: foldersets.add(n) d.setNodes(folders, foldersets) d.setText(fsname) if d.exec_(): if d.getType() == "FolderSet": storageType = "NODE" storageKeys = None # ignored versionMode = None # ignored else: storageType = "XML" # actually it is not used storageKeys = {} for k,v in d.fields(): storageKeys[str(k)] = v versionMode = {"MultiVersion": "MULTI", "SingleVersion": "SIGLE"}[d.getType()] path = str(d.text()) self.db.createNode(path = path, description = str(d.description()), storageType = storageType, versionMode = versionMode, storageKeys = storageKeys) # Now that the node has been created, we have to notify the models. # FIXME: this is the easiest solution to implement, but not optimal self._refreshModels(path) ## Ask for confirmation to delete the selected node and delete it if ok def deleteNode(self): path = str(self.pathComboBox.currentText()) if path: message = """<p>Are you sure you want to delete the node?</p> <p><center><tt>%s</tt><center></p> <p><font color="red">The action cannot be undone.</font></p>""" % path answer = QMessageBox.question(self, "Delete node?", message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if answer == QMessageBox.Yes: try: self.db.deleteNode(path) except: self.exceptionDialog() # Now that the node has been deleted, we have to notify the models. # FIXME: this is the easiest solution to implement, but not optimal self._refreshModels(parentpath(path)) ## Helper to show a dialog about def _unimplemented(self): QMessageBox.information(self, "Unimplemented", "Sorry, the function you selected is not implemented yet.") ## Dump a snapshot of the current database to files def dumpToFiles(self): d = DumpToFilesDialog(self) if d.exec_(): from CondDBUI.Admin import DumpToFiles monitor = ProgressDialog(self) monitor.setWindowModality(Qt.WindowModal) monitor.setWindowTitle("Dumping to files") monitor.setMinimumDuration(0) try: DumpToFiles(database = self.db, time = d.pointInTime.toValidityKey(), tag = str(d.tag.currentText()), srcs = ['/'], destroot = str(d.destDir.text()), force = d.overwrite.isChecked(), addext = False, monitor = monitor) if monitor.wasCanceled(): monitor.reset() QMessageBox.information(self, "Dump to files canceled", "The dump has been canceled.") except: monitor.reset() self.exceptionDialog() ## Create a slice of the current database to a database def createSlice(self): d = CreateSliceDialog(self) partName = str(self.db.db.databaseName()) i = d.partition.findText(partName) if i >= 0: d.partition.setCurrentIndex(i) else: d.partition.lineEdit().setText(partName) if d.exec_(): try: selectionList = [PyCoolCopy.Selection(path, since, until, tags = tags) for path, since, until, tags in d.selectionsModel.selections] connStr = str(d.connectionString()) copyTool = PyCoolCopy.PyCoolCopy(self.db.db) # reduce the verbosity of PyCoolCopy PyCoolCopy.log.setLevel(PyCoolCopy.logging.WARNING) from Utils import BusyCursor _bc = BusyCursor() if d.append.isChecked(): copyTool.append(connStr, selectionList) else: # FIXME : https://sft.its.cern.ch/jira/browse/ROOT-5603 targetDb = CondDB(connStr,create_new_db=True,readOnly=False).db copyTool.copy(targetDb, selectionList) except: self.exceptionDialog() ## Add a new condition to the selected folder+channel def addCondition(self): d = AddConditionDialog(self, externalEditor = self._externalEditor) d.setShowUTC(self.iovUTCCheckBox.checkState()) folder, channel = self._path d.setLocation(folder, channel) payload = self.models["iovs"].getPayload() if payload: d.buffer = payload if d.exec_(): try: from Utils import BusyCursor _bc = BusyCursor() data = [] for cond in d.conditionsStack: data.append({"since": cond.since, "until": cond.until, "channel": int(cond.channel), "payload": cond.data}) folder = d.getFolder() self.db.storeXMLStringList(folder, data) self._refreshModels(folder) except: self.exceptionDialog() ## Create a new tag def newTag(self): path = self._path[0] d = NewTagDialog(self.db, path) if d.exec_(): from Utils import BusyCursor _bc = BusyCursor() tag = str(d.tag.text()) if self.db.db.existsFolder(path): self.db.recursiveTag(path, tag) else: tags = {} if path == "/": p = "" else: p = path for n in d.childTagsModel: tags[p + "/" + n] = d.childTagsModel[n] self.db.moveTagOnNodes(path, tag, tags) self._refreshModels(path) ## Delete a tag def deleteTag(self): path = self._path[0] d = SelectTagDialog(path, self) d.tagLabel.setText("&Tag to delete") tag = d.run() if tag is not None: tag = str(tag) message = """<p>Are you sure you want to delete the tag</p> <p><center><tt>%s</tt><center></p> <p>from the node</p> <p><center><tt>%s</tt><center></p> <p><font color="red">The action cannot be undone.</font></p>""" % (tag, path) answer = QMessageBox.question(self, "Delete tag?", message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if answer == QMessageBox.Yes: try: self.db.deleteTag(path, tag) except: self.exceptionDialog() # Now that the node has been deleted, we have to notify the models. # FIXME: this is the easiest solution to implement, but not optimal self._refreshModels(path) ## Display context menu for the tree view def showHierarchyContextMenu(self, position): index = self.hierarchyTreeView.indexAt(position) if index.isValid(): menu = QMenu(self) item = index.internalPointer() if item.leaf: menu.addAction(self.actionAdd_condition) else: menu.addAction(self.actionNew_node) menu.addAction(self.actionNew_tag) menu.addSeparator() menu.addAction(self.actionCopy_path) menu.addSeparator() menu.addAction(self.actionDelete_node) menu.addAction(self.actionDelete_tag) menu.exec_(self.hierarchyTreeView.mapToGlobal(position)) ## Copy the current selected path to the clipboard. def copyPathToClipboard(self): QApplication.clipboard().setText(self._path[0]) ## Copy the current connection string to the clipboard. def copyConnStrToClipboard(self): QApplication.clipboard().setText(self._connectionString) ## Display the initial info message to notify that this is the new version # of the CondDBBrowser. def showWelcomeInfo(self, cancel = False): mb = QMessageBox(self) mb.setWindowTitle("Welcome to the CondDBBrowser") mb.setText("""<html><body> <p>Welcome to the Qt4-based version of the CondDBBrowser.</p> <p>This is a complete rewrite of the old application, which includes a lot of improvements and clean up.</p> <p>Since it is completely new, some features may not work. In that case you can still use the old version invoking <tt>CondDBBrowserOld.py</tt></p> <p>Among the improvements you can find: <ul> <li>based on Qt4</li> <li>better responsiveness</li> <li>contextual menus</li> <li>improved managing of recent and standard databases</li> <li>extensive usage of tooltips</li> </ul></p> <p>If you find a bug post it to <a href="https://savannah.cern.ch/bugs/?group=lhcbcore">savannah</a>, for comments and suggestions send an email to <a href="mailto:[email protected]">Marco Clemencic</a>.</p> </body></html>""") ok = mb.addButton(QMessageBox.Ok) if cancel: mb.addButton(QMessageBox.Cancel) mb.exec_() return mb.clickedButton() != ok