diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /kexi/plugins/scripting/scripts | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kexi/plugins/scripting/scripts')
19 files changed, 2701 insertions, 0 deletions
diff --git a/kexi/plugins/scripting/scripts/Makefile.am b/kexi/plugins/scripting/scripts/Makefile.am new file mode 100644 index 00000000..b63eedee --- /dev/null +++ b/kexi/plugins/scripting/scripts/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = exportxhtml importxhtml projectdocumentor copycenter python diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenter.py b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.py new file mode 100644 index 00000000..3718512f --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.py @@ -0,0 +1,644 @@ +""" +Copy Center + +Description: +Python script to copy data between different datastores. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +Dual-licensed under LGPL v2+higher and the BSD license. +""" + +class CopyCenter: + + class Plugin: + def __init__(self, plugin): + self.plugin = plugin + self.name = plugin.name + self.source = self.load("Source") + self.destination = self.load("Destination") + + def load(self, plugintype): + instance = None + try: + if hasattr(self.plugin, plugintype): + return getattr(self.plugin, plugintype)(self.plugin) + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + return None + + def __init__(self, scriptpath): + self.scriptpath = scriptpath + self.homepath = self.getHomePath() + self.plugins = {} + + import os + import sys + if not os.path.exists(scriptpath): + print "The Path %s does not exist" % scriptpath + else: + import re + regexp = re.compile('^CopyCenterPlugin(.*)\\.py$') + for f in os.listdir(scriptpath): + file = os.path.join(scriptpath, f) + if not os.path.isfile(file): continue + m = regexp.match(f) + if not m: continue + print "Plugin name=%s file=%s" % (m.group(1),file) + mylocals = {} + try: + execfile(file, globals(), mylocals) + if mylocals.has_key("CopyCenterPlugin"): + plugin = mylocals.get("CopyCenterPlugin")(self) + self.plugins[plugin.name] = self.Plugin(plugin) + except: + print "Failed to import file=%s" % file + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + + def getHomePath(self): + """ Return the homedirectory. """ + import os + try: + home = os.getenv("HOME") + if not home: + import pwd + user = os.getenv("USER") or os.getenv("LOGNAME") + if not user: + pwent = pwd.getpwuid(os.getuid()) + else: + pwent = pwd.getpwnam(user) + home = pwent[6] + return home + except (KeyError, ImportError): + return os.curdir + +class Copierer: + def __init__(self): pass + def appendProgressMessage(self,messagetext): pass + def writeSuccess(self,record,rowcount): pass + def writeFailed(self,record): pass + +def runGuiApp(copycenter, name): + import qt + import sys + + #-------------------------------------------------------------------- + + class ListViewDialog(qt.QDialog): + def __init__(self, parent, caption): + qt.QDialog.__init__(self, parent, "ProgressDialog", 1) + self.parent = parent + self.setCaption(caption) + layout = qt.QVBoxLayout(self) + box = qt.QVBox(self) + box.setMargin(2) + layout.addWidget(box) + self.listview = qt.QListView(box) + self.listview.setAllColumnsShowFocus(True) + self.listview.header().setStretchEnabled(True,0) + btnbox = qt.QHBox(box) + btnbox.setMargin(6) + btnbox.setSpacing(6) + self.okbtn = qt.QPushButton(btnbox) + self.okbtn.setText("Ok") + #qt.QObject.connect(okbtn, qt.SIGNAL("clicked()"), self.okClicked) + self.cancelbtn = qt.QPushButton(btnbox) + self.cancelbtn.setText("Cancel") + qt.QObject.connect(self.cancelbtn, qt.SIGNAL("clicked()"), self.close) + box.setMinimumSize(qt.QSize(460,380)) + def addItem(self,valuelist,afteritem = None): + if afteritem == None: + item = qt.QListViewItem(self.listview) + else: + item = qt.QListViewItem(self.listview,afteritem) + i = 0 + for value in valuelist: + item.setText(i,value) + i += 1 + return item + + #-------------------------------------------------------------------- + + class CopyJobWidget(qt.QVBox): + def __init__(self,dialog,parent): + self.dialog = dialog + qt.QVBox.__init__(self,parent) + self.setSpacing(6) + typebox = qt.QHBox(self) + typebox.setSpacing(6) + label = qt.QLabel("Job File:",typebox) + self.jobfilecombobox = qt.QComboBox(typebox) + typebox.setStretchFactor(self.jobfilecombobox,1) + self.jobfilecombobox.setEditable(True) + self.jobfilecombobox.insertItem("") + label.setBuddy(self.jobfilecombobox) + qt.QObject.connect(self.jobfilecombobox, qt.SIGNAL("textChanged(const QString&)"), self.jobfilecomboboxChanged) + + import os + import re + for f in os.listdir(self.dialog.copycenter.homepath): + file = os.path.join(self.dialog.copycenter.homepath,f) + if os.path.isfile(file) and re.search(".+\\.copycenterjob.xml$",f): + self.jobfilecombobox.insertItem(file) + + loadbtn = qt.QPushButton(typebox) + loadbtn.setText("Open...") + qt.QObject.connect(loadbtn, qt.SIGNAL("clicked()"), self.openClicked) + savebtn = qt.QPushButton(typebox) + savebtn.setText("Save...") + qt.QObject.connect(savebtn, qt.SIGNAL("clicked()"), self.saveClicked) + + self.listview = qt.QListView(self) + self.listview.setAllColumnsShowFocus(True) + self.listview.setSorting(-1) + self.listview.setDefaultRenameAction(qt.QListView.Reject) + self.listview.header().setClickEnabled(False) + self.listview.addColumn("Name") + self.listview.addColumn("Value") + qt.QObject.connect(self.listview, qt.SIGNAL("doubleClicked(QListViewItem*, const QPoint&, int)"), self.doubleClicked) + #qt.QObject.connect(self.listview, qt.SIGNAL("itemRenamed(QListViewItem*, int, const QString&)"), self.itemRenamed) + + def doubleClicked(self, **args): + print "CopyJobWidget.doubleClicked" + item = self.listview.selectedItem() + if item and item.parent(): item.startRename(1) + + def readOptions(self,domnode,plugininst): + print "CopyJobWidget.readOptions plugintype=\"%s\"" % plugininst.plugintype + for node in domnode.childNodes: + if node.nodeType == node.ELEMENT_NODE: + v = node.getAttribute("value") + plugininst.options[node.nodeName] = v + print "Option \"%s\" has value \"%s\" now." % (node.nodeName, v) + + def jobfilecomboboxChanged(self, **args): + print "CopyJobWidget.jobfilecomboboxChanged" + import os + import xml.dom.minidom + filename = str(self.jobfilecombobox.currentText()) + if not os.path.isfile(filename): return + domdoc = xml.dom.minidom.parse(filename) + try: + elements = domdoc.getElementsByTagName("CopyCenterJob")[0] + sourcenode = elements.getElementsByTagName("Source")[0] + destinationnode = elements.getElementsByTagName("Destination")[0] + except: + raise "The XML-file \"%s\" does not contain a valid copy-job." % filename + + sourcepluginname = str(sourcenode.getAttribute('plugin')) + if not self.dialog.sourcedata.combobox.listBox().findItem(sourcepluginname,qt.Qt.ExactMatch): + raise "There exists no plugin with the name \"%s\"." % sourcepluginname + self.dialog.sourcedata.combobox.setCurrentText(sourcepluginname) + + destinationpluginname = str(destinationnode.getAttribute('plugin')) + if not self.dialog.destinationdata.combobox.listBox().findItem(destinationpluginname,qt.Qt.ExactMatch): + raise "There exists no plugin with the name \"%s\"." % destinationpluginname + self.dialog.destinationdata.combobox.setCurrentText(destinationpluginname) + + self.readOptions(sourcenode,self.dialog.getSourcePluginImpl()) + self.readOptions(destinationnode,self.dialog.getDestinationPluginImpl()) + self.maybeUpdate() + + def openClicked(self): + text = str(self.jobfilecombobox.currentText()) + if text == "": text = self.dialog.copycenter.homepath + filename = str(qt.QFileDialog.getOpenFileName(text,"*.copycenterjob.xml;;*",self.dialog)) + if filename != "": self.jobfilecombobox.setCurrentText(filename) + + def escape(self,s): + return s.replace("&", "&").replace("'", "'").replace("<", "<").replace(">", ">").replace('"', """) + + def writeOptions(self,writer,pluginname,plugininst): + print "CopyJobWidget.writeOptions" + writer.write("<%s plugin=\"%s\">\n" % (plugininst.plugintype, pluginname)) + for optionname in plugininst.options: + value = self.escape( unicode(plugininst.options[optionname]).encode("utf-8") ) + writer.write("\t<%s value=\"%s\" />\n" % (optionname,value)) + writer.write("</%s>\n" % plugininst.plugintype) + + def saveClicked(self): + text = str(self.jobfilecombobox.currentText()) + if text == "": + import os + text = os.path.join(self.dialog.copycenter.homepath,"default.copycenterjob.xml") + filename = str(qt.QFileDialog.getSaveFileName(text,"*.copycenterjob.xml;;*",self.dialog)) + if str(filename) == "": return + f = open(filename, "w") + f.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") + f.write("<CopyCenterJob>\n") + sourcepluginname = self.dialog.sourcedata.combobox.currentText() + self.writeOptions(f, sourcepluginname, self.dialog.getSourcePluginImpl()) + destinationpluginname = self.dialog.destinationdata.combobox.currentText() + self.writeOptions(f, destinationpluginname, self.dialog.getDestinationPluginImpl()) + f.write("</CopyCenterJob>\n") + f.close() + print "File \%s\" successfully written." % filename + + def addItem(self, pluginimpl, afteritem = None, parentitem = None): + #print "CopyJobWidget.addItem" + class ListViewItem(qt.QListViewItem): + def __init__(self, pluginimpl, listview, parentitem = None, afteritem = None): + self.pluginimpl = pluginimpl + if parentitem == None: + qt.QListViewItem.__init__(self,listview) + self.setOpen(True) + else: + if afteritem == None: + qt.QListViewItem.__init__(self,parentitem) + else: + qt.QListViewItem.__init__(self,parentitem,afteritem) + self.setRenameEnabled(1,True) + def startRename(self, columnindex): + qt.QListViewItem.startRename(self,columnindex) + #lineedit = self.listView().viewport().child("qt_renamebox") + #if lineedit: + # regexp = qt.QRegExp("^[_A-Z]+[_A-Z0-9]*$", False) + # v = qt.QRegExpValidator(regexp, self.listView()); + # lineedit.setValidator(v) + def okRename(self, columnindex): + if columnindex == 1: + n = str(self.text(0)) + if not self.pluginimpl.options.has_key(n): + raise "No such option \"%s\"" % n + qt.QListViewItem.okRename(self,columnindex) + v = str(qt.QListViewItem.text(self,1)) + print "Option \"%s\" has value \"%s\" now." % (n,v) + self.pluginimpl.options[n] = v + + def text(self, columnindex): + if columnindex == 1: + if qt.QListViewItem.text(self,0).contains("password"): + return "*" * len(str(qt.QListViewItem.text(self,1))) + return qt.QListViewItem.text(self,columnindex) + return ListViewItem(pluginimpl, self.listview, parentitem, afteritem) + + def updateItem(self,pluginname,pluginimpl): + #print "CopyJobWidget.updateItem" + if pluginimpl == None: return + #plugin = self.dialog.plugins[pluginname] + item = self.addItem(pluginimpl) + item.setText(0,"%s: %s" % (pluginimpl.plugintype, pluginname)) + afteritem = None + for i in pluginimpl.options: + afteritem = self.addItem(pluginimpl, afteritem, item) + afteritem.setText(0,str(i)) + afteritem.setText(1,str(pluginimpl.options[i])) + print "CopyJobWidget.updateItem Added item with name \"%s\" and value \"%s\"" % (str(i),str(pluginimpl.options[i])) + pass + + def maybeUpdate(self): + print "CopyJobWidget.maybeUpdate" + self.listview.clear() + try: + self.updateItem(self.dialog.getDestinationPluginName(), self.dialog.getDestinationPluginImpl()) + self.updateItem(self.dialog.getSourcePluginName(), self.dialog.getSourcePluginImpl()) + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + self.listview.clear() + + #-------------------------------------------------------------------- + + class ProgressDialog(qt.QDialog): + def __init__(self, dialog): + self.dialog = dialog + self.starttime = None + qt.QDialog.__init__(self, dialog, "ProgressDialog", 1) + self.setCaption("Copying...") + layout = qt.QVBoxLayout(self) + box = qt.QVBox(self) + box.setSpacing(6) + box.setMargin(6) + layout.addWidget(box) + self.textbrowser = qt.QTextBrowser(box) + self.textbrowser.setWordWrap(qt.QTextEdit.WidgetWidth) + self.textbrowser.setTextFormat(qt.Qt.RichText) + statusbox = qt.QFrame(box) + layout = qt.QGridLayout(statusbox,4,2,0,2) + layout.addWidget(qt.QLabel("Number of records done:",statusbox),0,0) + self.donecounter = 0 + self.donelabel = qt.QLabel("-",statusbox) + layout.addWidget(self.donelabel,0,1) + layout.addWidget(qt.QLabel("Successfully copied records:",statusbox),1,0) + self.successcounter = 0 + self.successlabel = qt.QLabel("-",statusbox) + layout.addWidget(self.successlabel,1,1) + layout.addWidget(qt.QLabel("Failed to copy records:",statusbox),2,0) + self.failedcounter = 0 + self.failedlabel = qt.QLabel("-",statusbox) + layout.addWidget(self.failedlabel,2,1) + layout.addWidget(qt.QLabel("Elapsed time in seconds:",statusbox),3,0) + self.elapsedlabel = qt.QLabel("-",statusbox) + layout.addWidget(self.elapsedlabel,3,1) + btnbox = qt.QHBox(box) + btnbox.setSpacing(6) + self.donebtn = qt.QPushButton(btnbox) + self.donebtn.setText("Done") + self.donebtn.setEnabled(False) + qt.QObject.connect(self.donebtn,qt.SIGNAL("clicked()"),self.close) + self.cancelbtn = qt.QPushButton(btnbox) + self.cancelbtn.setText("Cancel") + qt.QObject.connect(self.cancelbtn,qt.SIGNAL("clicked()"),self.close) + box.setMinimumSize( qt.QSize(500,380) ) + + def updateStates(self): + if self.starttime != None: + self.donelabel.setText(str(self.donecounter)) + self.failedlabel.setText(str(self.failedcounter)) + self.successlabel.setText(str(self.successcounter)) + self.elapsedlabel.setText( str(self.starttime.elapsed() / 1000) ) + self.donelabel.update() + self.failedlabel.update() + self.successlabel.update() + self.elapsedlabel.update() + + def writeSuccess(self, record, rowcount): + self.donecounter += rowcount + self.successcounter += rowcount + qt.qApp.processEvents() + def writeFailed(self, record): + self.donecounter += 1 + self.failedcounter += 1 + qt.qApp.processEvents() + + def startCopy(self): + try: + global Copierer + copierer = Copierer() + copierer.appendProgressMessage = self.textbrowser.append + copierer.writeSuccess = self.writeSuccess + copierer.writeFailed = self.writeFailed + + self.starttime = qt.QTime() + self.updatetimer = qt.QTimer(self) + qt.QObject.connect(self.updatetimer,qt.SIGNAL("timeout()"),self.updateStates) + + # Initialize the source + sourcename = self.dialog.getSourcePluginName() + sourceimpl = self.dialog.getSourcePluginImpl() + self.textbrowser.append("Source: %s" % sourcename) + if sourceimpl == None: + raise "No such source." + try: + sourceimpl.init(copierer) + + # Initialize the destination + destinationname = self.dialog.getDestinationPluginName() + destinationimpl = self.dialog.getDestinationPluginImpl() + self.textbrowser.append("<hr>Destination: %s" % destinationname) + if destinationimpl == None: + raise "No such destination." + try: + destinationimpl.init(copierer) + + self.starttime.start() + self.updatetimer.start(500) + qt.qApp.processEvents() + + # Copy the records + self.textbrowser.append("<hr><i>Copy the records...</i>") + while True: + record = sourceimpl.read() + if record == None: break + destinationimpl.write(record) + + self.updateStates() + finally: + destinationimpl.finish() + finally: + sourceimpl.finish() + + self.setCaption("Copy done") + self.textbrowser.append("<hr><b>Copy done.</b>") + except: + self.setCaption("Copy failed") + self.textbrowser.append("<b>Error: %s</b>" % sys.exc_info()[0]) + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + #self.progressbar.setEnabled(False) + self.donebtn.setEnabled(True) + self.cancelbtn.setEnabled(False) + self.updatetimer.stop() + self.starttime = None + + def show(self): + qt.QDialog.show(self) + qt.QTimer.singleShot(10,self.startCopy) + qt.qApp.processEvents() + + def closeEvent(self, closeevent): + if not self.dialog.getSourcePluginImpl().isFinished(): + if qt.QMessageBox.warning(self,"Abort?","Abort the copy?",qt.QMessageBox.Yes,qt.QMessageBox.No) != qt.QMessageBox.Yes: + closeevent.ignore() + return + self.dialog.getSourcePluginImpl().finish() + self.dialog.getDestinationPluginImpl().finish() + closeevent.accept() + + #-------------------------------------------------------------------- + + class DataSelector(qt.QVGroupBox): + def __init__(self, plugintype, title, caption, parent, dialog, items): + self.plugintype = plugintype + self.pluginimpl = None + self.dialog = dialog + self.mainbox = None + + qt.QVGroupBox.__init__(self,title,parent) + self.setInsideMargin(6) + self.setInsideSpacing(0) + + typebox = qt.QHBox(self) + label = qt.QLabel(caption,typebox) + self.combobox = qt.QComboBox(typebox) + for item in items: + self.combobox.insertItem(str(item)) + label.setBuddy(self.combobox) + typebox.setStretchFactor(self.combobox,1) + + self.scrollview = qt.QScrollView(self) + try: + self.scrollview.setResizePolicy(qt.QScrollView.AutoOne) + self.scrollview.setFrameStyle(qt.QFrame.NoFrame); + self.scrollview.setResizePolicy(qt.QScrollView.AutoOneFit); + self.scrollview.viewport().setPaletteBackgroundColor(self.paletteBackgroundColor()) + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + qt.QObject.connect(self.combobox, qt.SIGNAL("activated(int)"), self.activated) + + def updatePlugin(self): + print "DataSelector.updatePlugin" + self.pluginimpl = None + text = str(self.combobox.currentText()) + plugin = self.dialog.copycenter.plugins[text] + self.pluginimpl = getattr(plugin, self.plugintype) + + def removeMainBox(self): + if self.mainbox == None: return + try: + self.scrollview.removeChild(self.mainbox) + self.mainbox.destroy() + except: + pass + self.mainbox = None + + def updateMainBox(self): + print "DataSelector.updateMainBox" + self.removeMainBox() + self.mainbox = qt.QVBox( self.scrollview.viewport() ) + self.mainbox.setSpacing(2) + if self.pluginimpl != None: + try: + self.pluginimpl.createWidget(self.dialog, self.mainbox) + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + self.mainbox.setStretchFactor(qt.QWidget(self.mainbox), 1) + self.mainbox.show() + self.scrollview.addChild(self.mainbox) + + def activated(self, **args): + self.updatePlugin() + self.updateMainBox() + + def maybeUpdate(self): + print "DataSelector.maybeUpdate" + self.removeMainBox() + qt.QTimer.singleShot(50, self.activated) + + def maybeDone(self): + print "DataSelector.maybeDone" + if self.pluginimpl.widget == None: return + for optionname in self.pluginimpl.options: + self.pluginimpl.options[optionname] = self.pluginimpl.widget.getOptionValue(optionname) + + #-------------------------------------------------------------------- + + class Dialog(qt.QDialog): + def __init__(self, copycenter, parent): + self.copycenter = copycenter + + import qt + import os + import sys + + self.ListViewDialog = ListViewDialog + qt.QDialog.__init__(self, parent, "Dialog", 1, qt.Qt.WDestructiveClose) + self.setCaption("Copy Center") + layout = qt.QVBoxLayout(self) + box = qt.QVBox(self) + box.setMargin(6) + box.setSpacing(6) + layout.addWidget(box) + self.tab = qt.QTabWidget(box) + self.tab.setMargin(6) + box.setStretchFactor(self.tab,1) + + self.jobsbox = CopyJobWidget(self,self.tab) + self.tab.addTab(self.jobsbox,"Jobs") + + self.splitter = qt.QSplitter(self.tab) + + sourceplugins = [] + destinationplugins = [] + for pluginname in self.copycenter.plugins: + if self.copycenter.plugins[pluginname].source != None: + sourceplugins.append(pluginname) + if self.copycenter.plugins[pluginname].destination != None: + destinationplugins.append(pluginname) + sourceplugins.sort() + destinationplugins.sort() + + self.sourcedata = DataSelector( + "source", # id + "Read Data From", # title + "Source:", # caption + self.splitter, self, sourceplugins) + self.destinationdata = DataSelector( + "destination", # id + "Write Data to", # title + "Destination:", # caption + self.splitter, self, destinationplugins) + + btnbox = qt.QHBox(box) + btnbox.setSpacing(6) + okbtn = qt.QPushButton(btnbox) + okbtn.setText("Start Copy") + okbtn.setDefault(True) + qt.QObject.connect(okbtn,qt.SIGNAL("clicked()"),self.startCopy) + cancelbtn = qt.QPushButton(btnbox) + cancelbtn.setText("Cancel") + qt.QObject.connect(cancelbtn,qt.SIGNAL("clicked()"),self.close) + + self.tab.addTab(self.splitter,"Copy") + self.tab.setCurrentPage(1) + + self.helpbrowser = qt.QTextBrowser(self.tab) + self.helpbrowser.setLinkUnderline(False) + self.helpbrowser.setUndoRedoEnabled(False) + self.tab.addTab(self.helpbrowser,"Help") + qt.QObject.connect(self.tab,qt.SIGNAL("currentChanged(QWidget*)"),self.currentTabChanged) + + box.setMinimumSize( qt.QSize(760,500) ) + + defaultfile = os.path.join(self.copycenter.homepath,"default.copycenterjob.xml") + if os.path.isfile(defaultfile): + print "Reading default copy job file: %s" % defaultfile + self.jobsbox.jobfilecombobox.setCurrentText(defaultfile) + + def getSourcePluginName(self): + return str(self.sourcedata.combobox.currentText()) + def getSourcePluginImpl(self): + return self.copycenter.plugins[self.getSourcePluginName()].source + def getDestinationPluginName(self): + return str(self.destinationdata.combobox.currentText()) + def getDestinationPluginImpl(self): + return self.copycenter.plugins[self.getDestinationPluginName()].destination + + def currentTabChanged(self,widget): + if self.tab.currentPage() == self.jobsbox: + # The "Copy" page is done + self.sourcedata.maybeDone() + self.destinationdata.maybeDone() + # Update the "Jobs" page + self.jobsbox.maybeUpdate() + elif self.tab.currentPage() == self.splitter: + # Update the "Copy" page + self.sourcedata.maybeUpdate() + self.destinationdata.maybeUpdate() + elif self.tab.currentPage() == self.helpbrowser and self.helpbrowser.lines() <= 1: + # Update the "Help" page + import os + file = os.path.join(self.copycenter.scriptpath, "readme.html") + if not os.path.isfile(file): return + fh = open(file,'r') + self.helpbrowser.setText( fh.read() ) + fh.close() + + def startCopy(self): + dlg = ProgressDialog(self) + dlg.show() + + #-------------------------------------------------------------------- + + if name == "__main__": + qtapp = qt.QApplication(sys.argv) + else: + qtapp = qt.qApp + dialog = Dialog(copycenter, qtapp.mainWidget()) + dialog.exec_loop() + +import os + +if __name__ == "__main__": + scriptpath = os.getcwd() +else: + scriptpath = os.path.dirname(__name__) + +copycenter = CopyCenter(scriptpath) +runGuiApp(copycenter, __name__) diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenter.rc b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.rc new file mode 100644 index 00000000..e3e758b4 --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.rc @@ -0,0 +1,10 @@ +<KrossScripting> + <ScriptAction + name="CopyCenter" + version="1" + text="Copy Center" + description="Copy data from one source to another." + icon="editcopy" + interpreter="python" + file="CopyCenter.py" /> +</KrossScripting> diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginKexiDB.py b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginKexiDB.py new file mode 100644 index 00000000..e8241405 --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginKexiDB.py @@ -0,0 +1,646 @@ +""" +CopyCenterPlugin to provide 'KexiDB'. + +Description: +This python-script is a plugin for the CopyCenter.py. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +GPL v2 or higher. +""" + +class CopyCenterPlugin: + """ The CopyCenterPlugin to provide abstract access to the 'KexiDB' + framework to CopyCenter.py """ + + name = "Kexi Database" + """ The name this plugin has. The name should be unique and + will be used for displaying a caption. """ + + class Plugin: + """ The implementation of a plugin which is published to the + CopyCenter.py script. While there exists only one instance of + the CopyCenterPlugin class, there will be n instances of this + Plugin class (one for 'source' aka read-data-from and one for + 'destination' aka write-data-to) created from within the + CopyCenter.py. The Source and Destination classes are extending + this Plugin class with specialized functionality. """ + def __init__(self,copycenterplugin): + """ Constructor. """ + self.copycenterplugin = copycenterplugin + self.widget = None + self.options = { + 'autoconnect' : False, + 'project' : '', #'~/test.kexi', + 'driver' : '', #'MySQL', #'SQLite3','MySQL',... + 'file' : '', #'/path/to/mysqlite3dbfile.kexi' + 'hostname' : '127.0.0.1', + 'port' : '', + 'usesocketfile' : False, + 'socketfile' : '', + 'username' : '', + 'password' : '', + 'database' : '', + 'table' : '', + 'fields' : '', + 'where' : '', + } + self.connection = copycenterplugin.Connection(self) + def isFinished(self): + return self.connection.isFinished() + def finish(self): + """ Called if reading is finished.""" + self.connection.finish() + def createWidget(self, dialog, parent): + """ Create and return a widget to modify the plugin settings. """ + return self.copycenterplugin.createWidget(dialog, self, parent) + + class Source(Plugin): + """ Specialization of the Plugin class to implement the + 'source' aka read-data-from functionality. """ + plugintype = "Source" + def init(self,copierer): + """ Called if reading should be initialize. """ + self.connection.init(copierer) + self.connection.initRead() + # Called if a record should be readed. + self.read = self.connection.readRecord + + class Destination(Plugin): + """ Specialization of the Plugin class to implement the + 'destination' aka write-data-to functionality. """ + plugintype = "Destination" + def init(self,copierer): + """ Called if writing should be initialize. """ + self.connection.init(copierer) + self.connection.initWrite() + # Called if a record should be written. + self.write = self.connection.writeRecord + + class Connection: + """ Abstract access to work with KexiDB. """ + def __init__(self,plugin): + self.plugin = plugin + self.copierer = None + self.kexidbconnection = None + self.kexidbcursor = None + self.tableschema = None + self.fieldlist = None + def lastError(self): return self.kexidbconnection.lastError() + def connect(self): return self.kexidbconnection.connect() + def disconnect(self): + if self.kexidbconnection == None or self.kexidbconnection.disconnect(): + self.kexidbconnection = None + return True + return False + def isConnected(self): return self.kexidbconnection != None and self.kexidbconnection.isConnected() + def tableNames(self): return self.kexidbconnection.tableNames() + def hasTableName(self,tablename): return tablename in self.kexidbconnection.tableNames() + def tableSchema(self,tablename): return self.kexidbconnection.tableSchema(tablename) + + def init(self,copierer): + self.copierer = copierer + if self.kexidbconnection == None: + if self.plugin.widget == None: + raise "No connection established." + self.copierer.appendProgressMessage("<i>Trying to connect...</i>") + if self.plugin.widget.driverbox.driver == None: + raise "Invalid driver." + if not self.plugin.widget.connectClicked(): + raise "Failed to connect." + connectiondata = self.kexidbconnection.data() + self.copierer.appendProgressMessage("Connected: %s %s" % (connectiondata.driverName(),connectiondata.serverInfoString())) + + tablename = str(self.plugin.widget.tablebox.tableedit.text()) + if tablename == "": + raise "No table defined" + fields = [ f.strip() for f in str(self.plugin.widget.fieldbox.fieldsedit.text()).split(",") if len(f) > 0 ] + if len(fields) < 1: + raise "No fields defined" + + self.tableschema = self.kexidbconnection.tableSchema(tablename) + if not self.tableschema: raise "No such tableschema \"%s\"" % tablename + self.copierer.appendProgressMessage("Table: %s" % self.tableschema.name()) + + if len(fields) == 1 and fields[0] == "*": + self.fieldlist = self.tableschema.fieldlist() + else: + self.fieldlist = self.tableschema.fieldlist().subList(fields) + if not self.fieldlist: raise "No such fields \"%s\"" % fields + fieldlistnames = self.fieldlist.names() + if len(fieldlistnames) < 1: raise "No valid fields defined for \"%s\"" % fields + self.copierer.appendProgressMessage("Fields: %s" % fieldlistnames) + def finish(self): + if self.plugin.widget == None: + self.disconnect() + else: + self.plugin.widget.disconnectClicked() + self.kexidbcursor = None + self.kexidbconnection = None + self.tableschema = None + self.fieldlist = None + self.copierer = None + def isFinished(self): + return self.copierer == None + + def initRead(self): + print "Initialize read" + #queryschema = self.plugin.copycenterplugin.drivermanager.querySchema() + queryschema = self.tableschema.query() + queryschema.fieldlist().setFields(self.fieldlist) + print "QuerySchema: %s" % queryschema.fieldlist().names() + + whereexpression = str(self.plugin.widget.whereedit.text()) + if whereexpression != "": + print "WHERE-expression: %s" % whereexpression + if not queryschema.setWhereExpression(whereexpression): + raise "Invalid WHERE-expression." + + #print "QuerySchema statement=%s" % queryschema.statement() + self.kexidbcursor = self.kexidbconnection.executeQuerySchema(queryschema) + if not self.kexidbcursor: + raise "Failed to create cursor." + if not self.kexidbcursor.moveFirst(): + raise "The cursor has no records to read from." + + def readRecord(self): + if self.kexidbcursor == None or self.kexidbcursor.eof(): + return None + record = [] + for i in range( self.kexidbcursor.fieldCount() ): + record.append( self.kexidbcursor.value(i) ) + self.kexidbcursor.moveNext() + #print "read record: %s" % record + return record + + def initWrite(self): + print "Initialize write" + + def writeRecord(self,record): + print "write record: %s" % record + if self.kexidbconnection.insertRecord(self.fieldlist,record): + print "=> insert successfully" + self.copierer.writeSuccess(record, 1) + else: + print "=> insert failed: %s" % self.kexidbconnection.lastError() + self.copierer.writeFailed(record) + #import time + #time.sleep(1) + return True + + def __init__(self, copycenter): + """ Constructor. """ + import krosskexidb + self.drivermanager = krosskexidb.DriverManager() + self.copycenter = copycenter + + def createWidget(self, dialog, plugin, parent): + """ Each plugin may provide a qt.QWidget back to the + CopyCenter.py. The widget will be used to configure our + plugin settings. """ + + import qt + import os + import re + + self.dialog = dialog + self.mainbox = None + class ProjectBox(qt.QHBox): + def __init__(self,main,copycenterplugin,plugin,parent): + self.main = main + self.copycenterplugin = copycenterplugin + self.plugin = plugin + + qt.QHBox.__init__(self,parent) + prjlabel = qt.QLabel("Project File:",self) + self.prjcombo = qt.QComboBox(self) + self.prjcombo.setEditable(True) + self.prjcombo.insertItem("") + + path = copycenterplugin.copycenter.homepath + for f in os.listdir(path): + file = os.path.join(path,f) + if os.path.isfile(file) and re.search(".+\\.(kexi|kexis|kexic)$",f): + self.prjcombo.insertItem(os.path.join("~",f)) + + prjlabel.setBuddy(self.prjcombo) + prjsavebtn = qt.QPushButton("...",self) + qt.QObject.connect(prjsavebtn, qt.SIGNAL("clicked()"),self.buttonClicked) + qt.QObject.connect(self.prjcombo, qt.SIGNAL("textChanged(const QString&)"), self.main.projectChanged) + self.setStretchFactor(self.prjcombo,1) + def buttonClicked(self): + text = str(self.prjcombo.currentText()) + if text == "": + text = self.copycenterplugin.copycenter.homepath + elif re.search("^\\~(\\/|\\\\)",text): + import os + text = os.path.join(self.copycenterplugin.copycenter.homepath,text[2:]) + if self.plugin.plugintype == "Source": + filename = qt.QFileDialog.getOpenFileName(text,"*.kexi *.kexis *.kexic;;*",self.copycenterplugin.dialog) + else: # "Destination": + filename = qt.QFileDialog.getSaveFileName(text,"*.kexi *.kexis *.kexic;;*",self.copycenterplugin.dialog) + if str(filename) != "": self.prjcombo.setCurrentText(str(filename)) + + class DriverBox(qt.QVBox): + def __init__(self,main,parent): + qt.QVBox.__init__(self,parent) + self.main = main + self.copycenterplugin = main.copycenterplugin + self.plugin = main.plugin + self.driver = None + + driverbox = qt.QHBox(self) + driverlabel = qt.QLabel("Driver:",driverbox) + self.drivercombo = qt.QComboBox(driverbox) + self.drivercombo.insertItem("") + for driver in self.copycenterplugin.drivermanager.driverNames(): + self.drivercombo.insertItem(driver) + + qt.QObject.connect(self.drivercombo, qt.SIGNAL("activated(int)"), self.activated) + driverlabel.setBuddy(self.drivercombo) + driverbox.setStretchFactor(self.drivercombo,1) + + self.box = qt.QVBox(self) + self.mainbox = None + + def activated(self,index): + drivertext = str(self.drivercombo.currentText()) + + self.box.hide() + if self.mainbox: + self.mainbox.hide() + self.mainbox.destroy() + self.mainbox = None + if index == 0 or drivertext == "": + self.driver = None + self.box.show() + self.main.updateConnectButtons() + return False + + self.driver = self.copycenterplugin.drivermanager.driver(drivertext) + + mainbox = qt.QVBox(self.box) + mainbox.setSpacing(2) + + if self.driver.isFileDriver(): + filebox = qt.QHBox(mainbox) + filelabel = qt.QLabel("File:",filebox) + self.fileedit = qt.QLineEdit(self.plugin.options['file'],filebox) + filelabel.setBuddy(self.fileedit) + filebox.setStretchFactor(self.fileedit,1) + filebtn = qt.QPushButton("...",filebox) + qt.QObject.connect(filebtn, qt.SIGNAL("clicked()"), self.fileClicked) + else: + hostbox = qt.QHBox(mainbox) + hostlabel = qt.QLabel("Hostname:",hostbox) + self.hostedit = qt.QLineEdit(self.plugin.options['hostname'],hostbox) + hostlabel.setBuddy(self.hostedit) + hostbox.setStretchFactor(self.hostedit,1) + + portbox = qt.QHBox(mainbox) + portlabel = qt.QLabel("Port:",portbox) + self.portedit = qt.QLineEdit(self.plugin.options['port'],portbox) + portlabel.setBuddy(self.portedit) + portbox.setStretchFactor(self.portedit,1) + + sockbox = qt.QHBox(mainbox) + self.sockfilecheckbox = qt.QCheckBox("Socket File:",sockbox) + qt.QObject.connect(self.sockfilecheckbox, qt.SIGNAL("toggled(bool)"), self.sockfilecheckboxClicked) + self.sockfilebox = qt.QHBox(sockbox) + self.sockfileedit = qt.QLineEdit(self.plugin.options['socketfile'],self.sockfilebox) + self.sockfilebox.setEnabled(False) + sockfilebtn = qt.QPushButton("...",self.sockfilebox) + self.sockfilecheckbox.setChecked( str(self.plugin.options['usesocketfile']) == str(True) ) + qt.QObject.connect(sockfilebtn, qt.SIGNAL("clicked()"), self.sockfileClicked) + self.sockfilebox.setStretchFactor(self.sockfileedit,1) + sockbox.setStretchFactor(self.sockfilebox,1) + + userbox = qt.QHBox(mainbox) + userlabel = qt.QLabel("Username:",userbox) + self.useredit = qt.QLineEdit(self.plugin.options['username'],userbox) + userlabel.setBuddy(self.useredit) + userbox.setStretchFactor(self.useredit,1) + + passbox = qt.QHBox(mainbox) + passlabel = qt.QLabel("Password:",passbox) + self.passedit = qt.QLineEdit(self.plugin.options['password'],passbox) + self.passedit.setEchoMode(qt.QLineEdit.Password) + passlabel.setBuddy(self.passedit) + passbox.setStretchFactor(self.passedit,1) + + dbbox = qt.QHBox(mainbox) + dblabel = qt.QLabel("Database:",dbbox) + self.dbedit = qt.QLineEdit(self.plugin.options['database'],dbbox) + dblabel.setBuddy(self.dbedit) + dbbox.setStretchFactor(self.dbedit,1) + #self.tablecombo.setText("") + + self.mainbox = mainbox + self.mainbox.show() + self.box.show() + self.main.updateConnectButtons() + return True + + def fileClicked(self): + text = str(self.fileedit.text()) + if text == "": text = self.copycenterplugin.copycenter.homepath + if self.plugin.plugintype == "Source": + filename = qt.QFileDialog.getOpenFileName(text,"*",self.copycenterplugin.dialog) + else: # "Destination": + filename = qt.QFileDialog.getSaveFileName(text,"*",self.copycenterplugin.dialog) + if str(filename) != "": self.fileedit.setText(str(filename)) + def sockfilecheckboxClicked(self,checked): + self.sockfilebox.setEnabled(checked) + + def sockfileClicked(self): + text = str(self.sockfileedit.text()) + if text == "": text = self.copycenterplugin.copycenter.homepath + if self.plugin.plugintype == "Source": + filename = qt.QFileDialog.getOpenFileName(text,"*",self.copycenterplugin.dialog) + else: # "Destination": + filename = qt.QFileDialog.getSaveFileName(text,"*",self.copycenterplugin.dialog) + if str(filename) != "": self.sockfileedit.setText(str(filename)) + + class TableBox(qt.QHBox): + def __init__(self,copycenterplugin,plugin,parent): + qt.QHBox.__init__(self,parent) + self.copycenterplugin = copycenterplugin + self.plugin = plugin + tablelabel = qt.QLabel("Table:",self) + self.tableedit = qt.QLineEdit(self.plugin.options['table'],self) + self.tablebtn = qt.QPushButton("...",self) + self.tablebtn.setEnabled(False) + qt.QObject.connect(self.tablebtn, qt.SIGNAL("clicked()"), self.buttonClicked) + tablelabel.setBuddy(self.tableedit) + self.setStretchFactor(self.tableedit,1) + def buttonClicked(self): + ListViewDialog = self.copycenterplugin.dialog.ListViewDialog + class TableDialog(ListViewDialog): + def __init__(self,tablebox): + ListViewDialog.__init__(self,tablebox,"Tables") + self.mainwidget = tablebox + self.listview.addColumn("Name") + text = str(self.mainwidget.tableedit.text()) + item = None + for table in self.mainwidget.plugin.connection.tableNames(): + if item == None: + item = qt.QListViewItem(self.listview,table) + else: + item = qt.QListViewItem(self.listview,item,table) + if table == text: + self.listview.setSelected(item,True) + self.listview.ensureItemVisible(item) + qt.QObject.connect(self.listview, qt.SIGNAL("doubleClicked(QListViewItem*, const QPoint&, int)"), self.okClicked) + qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked) + def okClicked(self): + item = self.listview.selectedItem() + if item == None: + self.mainwidget.tableedit.setText("") + else: + self.mainwidget.tableedit.setText(item.text(0)) + self.close() + dialog = TableDialog(self) + dialog.show() + + class FieldBox(qt.QHBox): + def __init__(self,copycenterplugin,plugin,parent): + qt.QHBox.__init__(self,parent) + self.copycenterplugin = copycenterplugin + self.plugin = plugin + self.tablename = "" + fieldslabel = qt.QLabel("Fields:",self) + self.fieldsedit = qt.QLineEdit(self.plugin.options['fields'],self) + self.setStretchFactor(self.fieldsedit,1) + fieldslabel.setBuddy(self.fieldsedit) + self.fieldsbtn = qt.QPushButton("...",self) + self.fieldsbtn.setEnabled(False) + qt.QObject.connect(self.fieldsbtn, qt.SIGNAL("clicked()"), self.fieldsClicked) + def fieldsClicked(self): + ListViewDialog = self.copycenterplugin.dialog.ListViewDialog + class FieldsDialog(ListViewDialog): + def __init__(self, fieldbox): + ListViewDialog.__init__(self,fieldbox,"Fields") + self.fieldbox = fieldbox + self.listview.setSelectionMode(qt.QListView.Multi) + self.listview.setSorting(-1) + self.listview.header().setClickEnabled(False) + self.listview.addColumn("Name") + self.listview.addColumn("Type") + self.listview.addColumn("Options") + fieldslist = str(self.fieldbox.fieldsedit.text()).split(",") + allfields = ("*" in fieldslist) + tableschema = self.fieldbox.plugin.connection.tableSchema(self.fieldbox.tablename) + item = None + for field in tableschema.fieldlist().fields(): + opts = [] + for opt in ("isAutoInc","isNotNull","isNotEmpty"): + if getattr(field,opt)(): + opts.append(opt[2:]) + item = self.addItem(( field.name(),field.type(),",".join(opts) ),item) + if allfields or field.name() in fieldslist: + self.listview.setSelected(item,True) + qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked) + def okClicked(self): + selitems = [] + item = self.listview.firstChild() + while item: + if item.isSelected(): + selitems.append(str(item.text(0))) + item = item.nextSibling() + self.fieldbox.fieldsedit.setText(",".join(selitems)) + self.close() + dialog = FieldsDialog(self) + dialog.show() + def tableChanged(self, text): + self.tablename = str(text) + if self.plugin.connection.isConnected(): + if self.plugin.connection.hasTableName(self.tablename): + self.fieldsbtn.setEnabled(True) + return + self.fieldsbtn.setEnabled(False) + + class MainBox(qt.QHBox): + def __init__(self,copycenterplugin,plugin,parent): + qt.QHBox.__init__(self,parent) + self.copycenterplugin = copycenterplugin + self.plugin = plugin + + self.prjbox = ProjectBox(self,copycenterplugin,plugin,parent) + self.driverbox = DriverBox(self,parent) + + statusbar = qt.QHBox(parent) + statusbar.setSpacing(2) + #self.statuslabel = qt.QLabel("Disconnected",statusbar) + #statusbar.setStretchFactor(self.statuslabel,1) + statusbar.setStretchFactor(qt.QWidget(statusbar),1) + self.connectbtn = qt.QPushButton("Connect",statusbar) + self.connectbtn.setEnabled(False) + qt.QObject.connect(self.connectbtn, qt.SIGNAL("clicked()"),self.connectClicked) + self.disconnectbtn = qt.QPushButton("Disconnect",statusbar) + self.disconnectbtn.setEnabled(False) + qt.QObject.connect(self.disconnectbtn, qt.SIGNAL("clicked()"),self.disconnectClicked) + + #self.connectionbox = ConnectionBox(copycenterplugin,plugin,parent) + self.tablebox = TableBox(copycenterplugin,plugin,parent) + self.fieldbox = FieldBox(copycenterplugin,plugin,parent) + qt.QObject.connect(self.tablebox.tableedit, qt.SIGNAL("textChanged(const QString&)"), self.fieldbox.tableChanged) + + if self.plugin.options['project'] != '': + self.prjbox.prjcombo.setCurrentText(self.plugin.options['project']) + + if self.plugin.options['driver'] != '': + try: + item = str(self.driverbox.drivercombo.listBox().findItem(self.plugin.options['driver'],qt.Qt.ExactMatch).text()) + self.driverbox.drivercombo.setCurrentText(item) + self.driverbox.activated(item) + except: + pass + + if self.plugin.plugintype == "Destination": + #typebox = qt.QHBox(parent) + #label = qt.QLabel("Operation:",typebox) + #combobox = qt.QComboBox(typebox) + #combobox.insertItem("Append") + #combobox.insertItem("Replace") + #combobox.insertItem("Update") + #combobox.insertItem("Update/Insert") + #combobox.insertItem("Insert new") + #label.setBuddy(combobox) + #typebox.setStretchFactor(combobox,1) + pass + elif self.plugin.plugintype == "Source": + wherebox = qt.QHBox(parent) + wherelabel = qt.QLabel("Where:",wherebox) + self.whereedit = qt.QLineEdit(self.plugin.options['where'],wherebox) + + #orderbox = qt.QHBox(parent) + #orderlabel = qt.QLabel("Order By:",orderbox) + #orderedit = qt.QLineEdit("",orderbox) + + #errbox = qt.QHBox(parent) + #errlabel = qt.QLabel("On Error:",errbox) + #errcombo = qt.QComboBox(errbox) + #errcombo.insertItem("Ask") + #errcombo.insertItem("Skip") + #errcombo.insertItem("Abort") + #errlabel.setBuddy(errcombo) + #errbox.setStretchFactor(errcombo,1) + + if self.plugin.options['autoconnect']: + self.connectClicked() + + def projectChanged(self, text): + #if self.driverbox.drivercombo.currentItem() != 0: + # self.driverbox.drivercombo.setCurrentItem(0) + + file = str(text) + import os + if re.search("^\\~(\\/|\\\\)",file): + file = os.path.join(self.copycenterplugin.copycenter.homepath,file[2:]) + if file == "" or not os.path.isfile(file): + self.driverbox.drivercombo.setCurrentItem(0) + self.driverbox.activated(0) + return + + connectiondata = self.copycenterplugin.drivermanager.createConnectionDataByFile(file) + if connectiondata == None: + raise "Unsupported file." + + drivername = connectiondata.driverName().lower() + print "driver: %s" % drivername + for i in range(1,self.driverbox.drivercombo.count()): + if drivername == self.driverbox.drivercombo.text(i).lower(): + self.driverbox.drivercombo.setCurrentItem(i) + self.driverbox.activated(i) + break + + if self.driverbox.driver != None: + if self.driverbox.driver.isFileDriver(): + self.driverbox.fileedit.setText(connectiondata.fileName()) + else: # server + self.driverbox.hostedit.setText(connectiondata.hostName()) + self.driverbox.portedit.setText(str(connectiondata.port())) + self.driverbox.sockfilecheckbox.setChecked(connectiondata.localSocketFileUsed()) + self.driverbox.sockfileedit.setText(connectiondata.localSocketFileName()) + self.driverbox.useredit.setText(connectiondata.userName()) + self.driverbox.passedit.setText(connectiondata.password()) + self.driverbox.dbedit.setText(connectiondata.databaseName()) + + def connectClicked(self): + if self.driverbox.driver == None: + print "No driver selected." + return False + connectiondata = self.copycenterplugin.drivermanager.createConnectionData() + if self.driverbox.driver.isFileDriver(): + file = str(self.driverbox.fileedit.text()) + if file == "" or not os.path.isfile(file): + qt.QMessageBox.critical(self,"Failed to connect","There exists no such database file \"%s\"" % file) + return False + connectiondata.setFileName(file) + connectiondata.setDatabaseName(file) + else: + connectiondata.setHostName(str(self.driverbox.hostedit.text())) + connectiondata.setPort(str(self.driverbox.portedit.text())) + connectiondata.setLocalSocketFileUsed(self.driverbox.sockfilecheckbox.isChecked()) + connectiondata.setLocalSocketFileName(str(self.driverbox.sockfileedit.text())) + connectiondata.setPassword(str(self.driverbox.passedit.text())) + connectiondata.setUserName(str(self.driverbox.useredit.text())) + connectiondata.setDatabaseName(str(self.driverbox.dbedit.text())) + print "Creating connection" + connection = self.driverbox.driver.createConnection(connectiondata) + print "Trying to connect" + if not connection.connect(): + qt.QMessageBox.critical(self,"Failed to connect",connection.lastError()) + return False + print "Use database \"%s\"" % connectiondata.databaseName() + if not connection.useDatabase( connectiondata.databaseName() ): + qt.QMessageBox.critical(self,"Failed to connect",connection.lastError()) + return False + print "dbnames = %s" % connection.databaseNames() + print "tablenames = %s" % connection.tableNames() + #self.useDatabase(connection, filename) + + self.plugin.connection.kexidbconnection = connection + self.updateConnectButtons() + return True + + def disconnectClicked(self): + if not self.plugin.connection.disconnect(): + qt.QMessageBox.critical(self,"Failed to disconnect",self.plugin.connection.lastError()) + return + self.updateConnectButtons() + + def updateConnectButtons(self): + connected = self.plugin.connection.isConnected() + self.prjbox.setEnabled(not connected) + self.driverbox.setEnabled(not connected) + self.connectbtn.setEnabled( (not connected) and (self.driverbox.driver != None) ) + self.disconnectbtn.setEnabled(connected) + self.tablebox.tablebtn.setEnabled(connected) + self.fieldbox.tableChanged(self.tablebox.tableedit.text()) + + def getOptionValue(self,optionname): + try: + if optionname == 'project': return str(self.prjbox.prjcombo.currentText()) + elif optionname == 'driver': return str(self.driverbox.drivercombo.currentText()) + elif optionname == 'file': return str(self.driverbox.fileedit.text()) + elif optionname == 'hostname': return str(self.driverbox.hostedit.text()) + elif optionname == 'port': return str(self.driverbox.portedit.text()) + elif optionname == 'usesocketfile': return str(self.driverbox.sockfilecheckbox.isChecked()) + elif optionname == 'socketfile': return str(self.driverbox.sockfileedit.text()) + elif optionname == 'username': return str(self.driverbox.useredit.text()) + elif optionname == 'password': return str(self.driverbox.passedit.text()) + elif optionname == 'database': return str(self.driverbox.dbedit.text()) + elif optionname == 'table': return str(self.tablebox.tableedit.text()) + elif optionname == 'fields': return str(self.fieldbox.fieldsedit.text()) + elif optionname == 'where': return str(self.whereedit.text()) + except: + pass + return "" + + mainbox = MainBox(self,plugin,parent) + plugin.widget = mainbox + return mainbox + diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginQtSQL.py b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginQtSQL.py new file mode 100644 index 00000000..985d757d --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginQtSQL.py @@ -0,0 +1,495 @@ +""" +CopyCenterPlugin to provide 'QtSQL'. + +Description: +This python-script is a plugin for the CopyCenter.py. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +GPL v2 or higher. +""" + +class CopyCenterPlugin: + """ The CopyCenterPlugin to provide 'QtSQL' to CopyCenter.py """ + + name = "QtSQL Database" + """ The name this plugin has. The name should be unique and + will be used for displaying a caption. """ + + class Plugin: + def _init_(self,copycenterplugin): + self.copycenterplugin = copycenterplugin + self.widget = None + self.database = None + self.cursor = None + self.isfinished = True + def _init(self,copierer): + self.copierer = copierer + if not self.widget.connectClicked(): + raise "Failed to connect with database." + if self.database == None or not self.database.isOpen(): + raise "Database is not initialized or not opened." + self.copierer.appendProgressMessage("Connected: %s %s@%s:%i %s" % + (str(self.database.driverName()),str(self.database.userName()),str(self.database.hostName()),self.database.port(),str(self.database.databaseName())) ) + self.isfinished = False + def isFinished(self): + return self.isfinished + def finish(self): + self.isfinished = True + self.widget.disconnectClicked() + def createWidget(self,dialog,parent): + return self.copycenterplugin.widget(dialog, self, parent) + + class Source(Plugin): + plugintype = "Source" + def __init__(self,copycenterplugin): + self._init_(copycenterplugin) + self.options = { + 'driver': 'QMYSQL3', #'QMYSQL3','QPSQL7','QODBC3',... + 'hostname': '127.0.0.1', + 'port': 3306, + 'username': 'root', #'MyUsername', + 'password': '', #'MySecretPassword', + 'database': '', #'MyQtSQLDatabase', + 'table': '', #'table1', + 'fields': '', #'f1,f2', + 'where': '', + } + def init(self,copierer): + self._init(copierer) + tablename = str(self.widget.tableedit.text()) + wherestatement = str(self.widget.whereedit.text()) + import qt + import qtsql + self.cursor = qtsql.QSqlCursor(tablename,True,self.database) + self.cursor.setFilter(wherestatement) + if not self.cursor.select(): + raise "Select on cursor failed.<br>%s<br>%s" % ( str(self.cursor.lastError().driverText()),str(self.cursor.lastError().databaseText()) ) + self.fieldlist = [] + for fieldname in str(self.widget.fieldedit.text()).split(","): + fn = fieldname.strip() + if fn != "": + field = self.cursor.field(fn) + if not field: + raise "There exists no such field \"%s\" in the table \"%s\"." % (fn,tablename) + self.fieldlist.append(str(field.name())) + if len(self.fieldlist) < 1: + raise "No fields for table \"%s\" defined." % tablename + copierer.appendProgressMessage("SQL: %s" % str(self.cursor.executedQuery())) + + def read(self): + if not self.cursor.next(): + return None + record = [] + for fieldname in self.fieldlist: + record.append( unicode(self.cursor.value(fieldname).toString()).encode("latin-1") ) + #print "read record: %s" % record + return record + + class Destination(Plugin): + plugintype = "Destination" + def __init__(self,copycenterplugin): + self._init_(copycenterplugin) + self.options = { + 'driver': 'QMYSQL3', #'QMYSQL3','QPSQL7','QODBC3',... + 'hostname': '127.0.0.1', + 'port': 3306, + 'username': 'root', #'MyUsername', + 'password': '', #'MySecretPassword', + 'database': '', #'MyQtSQLDatabase', + 'table': '', #'table2', + 'fields': '', #'field1,field2', + 'operation': 'Insert', #'Insert','Update'... + 'indexfield': '', + } + def init(self,copierer): + self._init(copierer) + import qt + import qtsql + + self.fieldlist = [] + for fieldname in str(self.widget.fieldedit.text()).split(","): + fn = fieldname.strip() + if fn != "": self.fieldlist.append(fn) + + tablename = str(self.widget.tableedit.text()) + self.cursor = qtsql.QSqlCursor(tablename,True,self.database) + { + 0: self.initInsert, + 1: self.initUpdate + }[ self.widget.operationedit.currentItem() ]() + + def initInsert(self): + self.write = self.writeInsert + if not self.cursor.select(): + raise "Select on cursor failed.<br>%s<br>%s" % ( str(self.cursor.lastError().driverText()),str(self.cursor.lastError().databaseText()) ) + for fieldname in self.fieldlist: # check fieldlist + field = self.cursor.field(fieldname) + if not field: raise "There exists no such field \"%s\" in the table \"%s\"." % (fieldname, self.cursor.name()) + self.copierer.appendProgressMessage("Insert SQL: %s" % str(self.cursor.executedQuery())) + + def writeInsert(self, record): + print "insert record: %s" % record + import qt + cursorrecord = self.cursor.primeInsert() + count = len(record) + for i in range(len(self.fieldlist)): + if i == count: break + r = record[i] + if r == None: + v = qt.QVariant() + else: + v = qt.QVariant(r) + cursorrecord.setValue(self.fieldlist[i], v) + rowcount = self.cursor.insert() + if rowcount < 1: + drv = unicode(self.cursor.lastError().driverText()).encode("latin-1") + db = unicode(self.cursor.lastError().databaseText()).encode("latin-1") + print "failed: %s %s" % (drv,db) + self.copierer.writeFailed(record) + else: + self.copierer.writeSuccess(record,rowcount) + #import time + #time.sleep(1) + return True + + def initUpdate(self): + self.write = self.writeUpdate + self.indexfieldname = str(self.widget.indexedit.text()).strip() + if self.indexfieldname == "": raise "No index-field defined." + pkindex = self.cursor.index(self.indexfieldname) + if not pkindex: raise "Invalid index-field defined." + self.cursor.setPrimaryIndex(pkindex) + #self.cursor.setMode( qtsql.QSqlCursor.Insert | qtsql.QSqlCursor.Update ) + self.copierer.appendProgressMessage("Update SQL: %s" % str(self.cursor.executedQuery())) + + def writeUpdate(self, record): + import qt + # determinate the primary-index + try: + idx = self.fieldlist.index(self.indexfieldname) + indexvalue = record[idx] + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + raise "Failed to determinate the value for the primary key." + # select cursor and go to matching record. + wherestatement = "%s = \"%s\"" % (self.indexfieldname, indexvalue) + if not self.cursor.select(wherestatement): + raise "Select on cursor failed.<br>%s<br>%s" % ( str(self.cursor.lastError().driverText()),str(self.cursor.lastError().databaseText()) ) + if not self.cursor.next(): + #print "No such record to update !" + return False + # Prepare updating the record. + cursorrecord = self.cursor.primeUpdate() + # Update the fields in the record. + count = len(record) + for i in range(len(self.fieldlist)): + if i == count: break + fieldname = self.fieldlist[i] + if self.indexfieldname != fieldname: # don't update the indexfield! + r = record[i] + if r == None: + v = qt.QVariant() + else: + v = qt.QVariant(r) + cursorrecord.setValue(fieldname, v) + # Write updated record. + rowcount = self.cursor.update() + if rowcount < 1: + self.copierer.writeFailed(record) + else: + self.copierer.writeSuccess(record,rowcount) + print "updated record (rowcount %s): %s" % (rowcount,record) + return True + + def __init__(self, copycenter): + """ Constructor. """ + pass + + def widget(self,dialog,plugin,parent): + """ Each plugin may provide a qt.QWidget back to the + CopyCenter.py. The widget will be used to configure our + plugin settings. """ + + import qt + import os + + self.dialog = dialog + ListViewDialog = self.dialog.ListViewDialog + class TableDialog(ListViewDialog): + def __init__(self, mainwidget): + ListViewDialog.__init__(self,mainwidget,"Tables") + self.mainwidget = mainwidget + self.listview.addColumn("Name") + text = str(self.mainwidget.tableedit.text()) + item = None + for table in self.mainwidget.plugin.database.tables(): + if item == None: + item = qt.QListViewItem(self.listview,table) + else: + item = qt.QListViewItem(self.listview,item,table) + if table == text: + self.listview.setSelected(item,True) + self.listview.ensureItemVisible(item) + qt.QObject.connect(self.listview, qt.SIGNAL("doubleClicked(QListViewItem*, const QPoint&, int)"), self.okClicked) + qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked) + def okClicked(self): + item = self.listview.selectedItem() + if item == None: + self.mainwidget.tableedit.setText("") + else: + self.mainwidget.tableedit.setText(item.text(0)) + self.close() + + class FieldsDialog(ListViewDialog): + def __init__(self, mainwidget): + ListViewDialog.__init__(self,parent,"Fields") + self.mainwidget = mainwidget + self.listview.setSelectionMode(qt.QListView.Multi) + self.listview.setSorting(-1) + self.listview.header().setClickEnabled(False) + self.listview.addColumn("Name") + self.listview.addColumn("Type") + self.listview.addColumn("Options") + tablename = str(self.mainwidget.tableedit.text()) + recinfo = self.mainwidget.plugin.database.recordInfo(tablename) + if recinfo != None: + fieldslist = str(self.mainwidget.fieldedit.text()).split(",") + allfields = ("*" in fieldslist) + item = None + for fieldinfo in recinfo: + opts = "" + for s in ('Required','Calculated'): #,'Generated'): + if getattr(fieldinfo,"is%s" % s)(): opts += "%s " % s + item = self.addItem((fieldinfo.name(), qt.QVariant.typeToName(fieldinfo.type()), opts),item) + if allfields or fieldinfo.name() in fieldslist: + self.listview.setSelected(item,True) + qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked) + def okClicked(self): + selitems = [] + item = self.listview.firstChild() + while item: + if item.isSelected(): + selitems.append(str(item.text(0))) + item = item.nextSibling() + self.mainwidget.fieldedit.setText(",".join(selitems)) + self.close() + + + class MainWidget(qt.QHBox): + def __init__(self,plugin,dialog,parent): + import qt + import qtsql + qt.QHBox.__init__(self,parent) + self.dialog = dialog + self.plugin = plugin + + self.connectionbox = qt.QVBox(parent) + self.connectionbox.setSpacing(2) + + driverbox = qt.QHBox(self.connectionbox) + driverlabel = qt.QLabel("Driver:",driverbox) + self.driveredit = qt.QComboBox(driverbox) + for driver in qtsql.QSqlDatabase.drivers(): + self.driveredit.insertItem(driver) + if self.plugin.options['driver'] == driver: + self.driveredit.setCurrentItem(self.driveredit.count() - 1) + driverlabel.setBuddy(self.driveredit) + driverbox.setStretchFactor(self.driveredit,1) + + hostbox = qt.QHBox(self.connectionbox) + hostlabel = qt.QLabel("Hostname:",hostbox) + self.hostedit = qt.QLineEdit(self.plugin.options['hostname'],hostbox) + hostlabel.setBuddy(self.hostedit) + hostbox.setStretchFactor(self.hostedit,1) + + portbox = qt.QHBox(self.connectionbox) + portlabel = qt.QLabel("Port:",portbox) + self.portedit = qt.QLineEdit(str(self.plugin.options['port']),portbox) + portlabel.setBuddy(self.portedit) + portbox.setStretchFactor(self.portedit,1) + + userbox = qt.QHBox(self.connectionbox) + userlabel = qt.QLabel("Username:",userbox) + self.useredit = qt.QLineEdit(self.plugin.options['username'],userbox) + userlabel.setBuddy(self.useredit) + userbox.setStretchFactor(self.useredit,1) + + passbox = qt.QHBox(self.connectionbox) + passlabel = qt.QLabel("Password:",passbox) + self.passedit = qt.QLineEdit(self.plugin.options['password'],passbox) + self.passedit.setEchoMode(qt.QLineEdit.Password) + passlabel.setBuddy(self.passedit) + passbox.setStretchFactor(self.passedit,1) + + dbbox = qt.QHBox(self.connectionbox) + dblabel = qt.QLabel("Database:",dbbox) + self.dbedit = qt.QLineEdit(self.plugin.options['database'],dbbox) + dblabel.setBuddy(self.dbedit) + dbbox.setStretchFactor(self.dbedit,1) + + statusbar = qt.QHBox(parent) + statusbar.setSpacing(2) + statusbar.setStretchFactor(qt.QWidget(statusbar),1) + self.connectbtn = qt.QPushButton("Connect",statusbar) + qt.QObject.connect(self.connectbtn, qt.SIGNAL("clicked()"),self.connectClicked) + self.disconnectbtn = qt.QPushButton("Disconnect",statusbar) + self.disconnectbtn.setEnabled(False) + qt.QObject.connect(self.disconnectbtn, qt.SIGNAL("clicked()"),self.disconnectClicked) + + tablebox = qt.QHBox(parent) + tablelabel = qt.QLabel("Table:",tablebox) + self.tableedit = qt.QLineEdit(self.plugin.options['table'],tablebox) + qt.QObject.connect(self.tableedit, qt.SIGNAL("textChanged(const QString&)"), self.tableEditChanged) + self.tablebtn = qt.QPushButton("...",tablebox) + self.tablebtn.setEnabled(False) + qt.QObject.connect(self.tablebtn, qt.SIGNAL("clicked()"), self.tableBtnClicked) + tablelabel.setBuddy(self.tableedit) + tablebox.setStretchFactor(self.tableedit,1) + + fieldbox = qt.QHBox(parent) + fieldlabel = qt.QLabel("Fields:",fieldbox) + self.fieldedit = qt.QLineEdit(self.plugin.options['fields'],fieldbox) + self.fieldbtn = qt.QPushButton("...",fieldbox) + self.fieldbtn.setEnabled(False) + qt.QObject.connect(self.fieldbtn, qt.SIGNAL("clicked()"), self.fieldBtnClicked) + fieldlabel.setBuddy(self.fieldedit) + fieldbox.setStretchFactor(self.fieldedit,1) + + if self.plugin.plugintype == "Source": + box = qt.QHBox(parent) + wherelabel = qt.QLabel("Where:",box) + self.whereedit = qt.QLineEdit(self.plugin.options['where'],box) + wherelabel.setBuddy(self.whereedit) + box.setStretchFactor(self.whereedit,1) + elif self.plugin.plugintype == "Destination": + + class OperationBox(qt.QVBox): + def __init__(self, mainwidget, parent): + self.mainwidget = mainwidget + qt.QVBox.__init__(self, parent) + opbox = qt.QHBox(self) + operationlabel = qt.QLabel("Operation:",opbox) + self.mainwidget.operationedit = qt.QComboBox(opbox) + for op in ('Insert','Update'): + self.mainwidget.operationedit.insertItem(op) + if self.mainwidget.plugin.options['operation'] == op: + self.mainwidget.operationedit.setCurrentItem(self.mainwidget.operationedit.count() - 1) + operationlabel.setBuddy(self.mainwidget.operationedit) + opbox.setStretchFactor(self.mainwidget.operationedit,1) + self.box = None + qt.QObject.connect(self.mainwidget.operationedit, qt.SIGNAL("activated(int)"), self.operationActivated) + self.operationActivated() + def operationActivated(self, **args): + if self.box: + self.box.hide() + self.box.destroy() + self.box = None + def showInsert(self): + pass + def showUpdate(self): + self.box = qt.QHBox(self) + indexlabel = qt.QLabel("Indexfield:", self.box) + self.mainwidget.indexedit = qt.QLineEdit(self.mainwidget.plugin.options['indexfield'], self.box) + indexlabel.setBuddy(self.mainwidget.indexedit) + self.box.setStretchFactor(self.mainwidget.indexedit,1) + { + 0: showInsert, + 1: showUpdate, + }[ self.mainwidget.operationedit.currentItem() ](self) + if self.box != None: self.box.show() + OperationBox(self,parent) + + def tableEditChanged(self,text): + if self.plugin.database != None and self.plugin.database.isOpen(): + if str(text) in self.plugin.database.tables(): + self.fieldbtn.setEnabled(True) + return + self.fieldbtn.setEnabled(False) + + def tableBtnClicked(self): + dialog = TableDialog(self) + dialog.show() + + def fieldBtnClicked(self): + dialog = FieldsDialog(self) + dialog.show() + + def updateConnectState(self): + connected = self.plugin.database != None and self.plugin.database.isOpen() + self.connectionbox.setEnabled(not connected) + self.connectbtn.setEnabled(not connected) + self.disconnectbtn.setEnabled(connected) + self.tablebtn.setEnabled(connected) + self.tableEditChanged(self.tableedit.text()) + + def getOptionValue(self,optionname): + try: + if optionname == 'driver': return str(self.driveredit.currentText()) + if optionname == 'hostname': return str(self.hostedit.text()) + if optionname == 'port': return str(self.portedit.text()) + if optionname == 'username': return str(self.useredit.text()) + if optionname == 'password': return str(self.passedit.text()) + if optionname == 'database': return str(self.dbedit.text()) + if optionname == 'table': return str(self.tableedit.text()) + if optionname == 'fields': return str(self.fieldedit.text()) + if optionname == 'where': return str(self.whereedit.text()) + if optionname == 'operation': return str(self.operationedit.currentText()) + if optionname == 'indexfield': return str(self.indexedit.text()) + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + return "" + + def connectClicked(self): + if self.plugin.database != None and self.plugin.database.isOpen(): + print "already connected. not needed to reconnect..." + self.updateConnectState() + return True + print "trying to connect..." + + import qtsql + drivername = str(self.driveredit.currentText()) + print "drivername: %s" % drivername + connectionname = "CopyCenter%s" % self.plugin.plugintype + print "connectionname: %s" % connectionname + self.plugin.database = qtsql.QSqlDatabase.addDatabase(drivername,connectionname) + if not self.plugin.database: + qt.QMessageBox.critical(self,"Failed to connect","<qt>Failed to create database for driver \"%s\"</qt>" % drivername) + return False + + hostname = str(self.hostedit.text()) + self.plugin.database.setHostName(hostname) + + portnumber = int(str(self.portedit.text())) + self.plugin.database.setPort(portnumber) + + username = str(self.useredit.text()) + self.plugin.database.setUserName(username) + + password = str(self.passedit.text()) + self.plugin.database.setPassword(password) + + databasename = str(self.dbedit.text()) + self.plugin.database.setDatabaseName(databasename) + + if not self.plugin.database.open(): + qt.QMessageBox.critical(self,"Failed to connect","<qt>%s<br><br>%s</qt>" % (self.plugin.database.lastError().driverText(),self.plugin.database.lastError().databaseText())) + return False + print "database is opened now!" + self.updateConnectState() + return True + + def disconnectClicked(self): + print "trying to disconnect..." + if self.plugin.database: + self.plugin.database.close() + self.plugin.database = None + print "database is closed now!" + self.updateConnectState() + + plugin.widget = MainWidget(plugin,self.dialog,parent) + return plugin.widget diff --git a/kexi/plugins/scripting/scripts/copycenter/Makefile.am b/kexi/plugins/scripting/scripts/copycenter/Makefile.am new file mode 100644 index 00000000..d46928ed --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/kexi/Makefile.global + +scriptsdir = $(kde_datadir)/kexi/scripts/copycenter +scripts_SCRIPTS = CopyCenter.py CopyCenterPluginQtSQL.py CopyCenterPluginKexiDB.py CopyCenter.rc readme.html diff --git a/kexi/plugins/scripting/scripts/copycenter/readme.html b/kexi/plugins/scripting/scripts/copycenter/readme.html new file mode 100644 index 00000000..2aff6152 --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/readme.html @@ -0,0 +1,20 @@ +<html><body> +<h1>Copy Center</h1> + +<b>Version 1.2</b> + +<p>Python script to copy data between database backends. The flexible +plugin-architecture allows transparent copies between different backends.</p> + +<ul> +<li>Read+write Kexi Databases. This includes all database backends supported by Kexi (like SQLite, MySQL or PostgreSQL).</li> +<li>Read+write QtSQL Databases. MySQL, PostgreSQL and UnixODBC are supported. There might even be more like Oracle in the commercial Qt version or 3rd party backends.</li> +<li>Runs embedded in Kexi (from the tools=>scripts menu) as well as independent of Kexi (use "krossrunner ~/.kde/share/apps/kexi/scripts/copycenter/CopyCenter.py" or python direct).</li> +<li>Depends only on PyQt. PyKDE is not used at all and Kross (included in KOffice 1.5) is optional.</li> +</ul> + +Author: (C)2006 Sebastian Sauer (mail at dipe dot org)<br> +Website: http://www.kde-files.org/content/show.php?content=35251<br> +License: GPL v2 or higher<br> + +</body></html> diff --git a/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.py b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.py new file mode 100644 index 00000000..cace0340 --- /dev/null +++ b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.py @@ -0,0 +1,196 @@ +""" +Export table or query data. + +Description: +This script exports a KexiDB table or query to different fileformats. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +Dual-licensed under LGPL v2+higher and the BSD license. +""" + +class Datasource: + def __init__(self): + import kexiapp + keximainwindow = kexiapp.get("KexiAppMainWindow") + + try: + self.connection = keximainwindow.getConnection() + except: + raise "No connection established. Please open a project before." + + self.schema = None + + def getSources(self): + sources = [] + for table in self.connection.tableNames(): + sources.append("Tables/%s" % table) + for query in self.connection.queryNames(): + sources.append("Queries/%s" % query) + sources.sort() + return sources + + def setSource(self, source): + s = source.split("/",1) + if s[0] == "Tables": + self.schema = self.connection.tableSchema( s[1] ) + self.queryschema = self.schema.query() + elif s[0] == "Queries": + self.schema = self.connection.querySchema( s[1] ) + self.queryschema = self.schema + self.cursor = None + return self.schema != None + + def name(self): + return self.schema.name() + + def caption(self): + return self.schema.caption() + + def description(self): + return self.schema.description() + + def header(self): + h = [] + for field in self.schema.fieldlist().fields(): + s = field.caption() + if s == None or s == "": + s = field.name() + h.append(s) + return h + + def getNext(self): + if not self.cursor: + self.cursor = self.connection.executeQuerySchema( self.queryschema ) + if not self.cursor: + raise "Failed to execute queryschema." + if not self.cursor.moveFirst(): + raise "Failed to move cursor to first record." + if self.cursor.eof(): + self.cursor = None + return None + items = [] + for i in range( self.cursor.fieldCount() ): + items.append( self.cursor.value(i) ) + self.cursor.moveNext() + return items + +class HtmlExporter: + def __init__(self, datasource): + self.datasource = datasource + + def htmlescape(self, text): + import string + return string.replace(string.replace(string.replace(str(text),'&','&'),'<','<'),'>','>') + + def write(self, output, style): + name = self.datasource.name() + + output.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n") + output.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n") + output.write("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n") + output.write("<head><title>%s</title>\n" % name) + output.write("<style type=\"text/css\">\n<!--\n") + if style == "Paper": + output.write("html { background-color:#efefef; }") + output.write("body { background-color:#fafafa; color:#303030; margin:1em; padding:1em; border:#606060 1px solid; }") + elif style == "Blues": + output.write("html { background-color:#0000aa; }") + output.write("body { background-color:#000066; color:#efefff; margin:1em; padding:1em; border:#00f 1px solid; }") + output.write("h1 { color:#0000ff; }") + output.write("th { color:#0000aa; }") + else: + output.write("html { background-color:#ffffff; color:#000; }") + output.write("body { margin:1em; }") + output.write("\n//-->\n</style>\n") + output.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n") + output.write("</head><body><h1>%s</h1>\n" % name) + + caption = self.datasource.caption() + if caption and caption != name: + output.write("caption: %s<br />\n" % caption) + + description = self.datasource.description() + if description: + output.write("description: %s<br />\n" % description) + + #import datetime + #output.write("date: %s<br />" % datetime.datetime.now()) + + output.write("<table border='1'>\n") + + output.write("<tr>") + for h in self.datasource.header(): + output.write("<th>%s</th>" % h) + output.write("</tr>") + + while 1 == 1: + items = self.datasource.getNext() + if items == None: break + output.write("<tr>") + for item in items: + u = unicode(str(self.htmlescape(item)),"latin-1") + output.write("<td>%s</td>" % u.encode("utf-8")) + output.write("</tr>\n") + output.write("</table>\n") + output.write("</body></html>\n") + +class GuiApp: + def __init__(self, datasource): + self.datasource = datasource + + try: + import gui + except: + raise "Import of the Kross GUI module failed." + + self.dialog = gui.Dialog("Export XHTML") + self.dialog.addLabel(self.dialog, "Export a table- or query-datasource to a XHTML-file.") + + datasourceitems = self.datasource.getSources() + self.datasourcelist = self.dialog.addList(self.dialog, "Datasource:", datasourceitems) + + styleitems = ["Plain", "Paper", "Blues"] + self.stylelist = self.dialog.addList(self.dialog, "Style:", styleitems) + + #queryframe = Tkinter.Frame(frame) + #queryframe.pack() + #Tkinter.Label(queryframe, text="Table or query to export:").pack(side=Tkinter.LEFT) + #self.querycontent = Tkinter.StringVar() + #self.query = apply(Tkinter.OptionMenu, (queryframe, self.querycontent) + tuple( self.datasource.getSources() )) + #self.query.pack(side=Tkinter.LEFT) + + self.file = self.dialog.addFileChooser(self.dialog, + "File:", + gui.getHome() + "/kexidata.xhtml", + (('XHTML files', '*.xhtml'),('All files', '*'))) + + btnframe = self.dialog.addFrame(self.dialog) + self.dialog.addButton(btnframe, "Export", self.doExport) + self.dialog.addButton(btnframe, "Cancel", self.dialog.close) + + self.dialog.show() + + def doExport(self): + file = str( self.file.get() ) + query = str( self.datasourcelist.get() ) + print "Exporting '%s' to file '%s' ..." % (query,file) + + if not self.datasource.setSource(query): + raise "Invalid datasource selected." + #return + + style = str( self.stylelist.get() ) + + f = open(file, "w") + global HtmlExporter + exporter = HtmlExporter(self.datasource) + exporter.write(f, style) + f.close() + + print "Successfully exported '%s' to file %s" % (query,file) + self.dialog.close() + +GuiApp( Datasource() ) diff --git a/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.rc b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.rc new file mode 100644 index 00000000..11c1dcdf --- /dev/null +++ b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.rc @@ -0,0 +1,8 @@ +<KrossScripting> + <ScriptAction + name="exportxhtml" + text="Export Data to XHTML File" + icon="fileexport" + interpreter="python" + file="ExportXHTML.py" /> +</KrossScripting> diff --git a/kexi/plugins/scripting/scripts/exportxhtml/Makefile.am b/kexi/plugins/scripting/scripts/exportxhtml/Makefile.am new file mode 100644 index 00000000..1c7b9ca6 --- /dev/null +++ b/kexi/plugins/scripting/scripts/exportxhtml/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/kexi/Makefile.global + +scriptsdir = $(kde_datadir)/kexi/scripts/exportxhtml +scripts_SCRIPTS = ExportXHTML.py ExportXHTML.rc diff --git a/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.py b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.py new file mode 100755 index 00000000..200b3dee --- /dev/null +++ b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.py @@ -0,0 +1,434 @@ +""" +Import data from a XHTML file to a KexiDB table. + +Description: +This script implements import of data from a XHTML file to a KexiDB table. The +table needs to be an already existing table the data should be added to. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +Dual-licensed under LGPL v2+higher and the BSD license. +""" + +class SaxInput: + """ The inputsource we like to import the data from. This class + provides us abstract access to the SAX XML parser we use internaly + to import data from the XML-file. """ + + xmlfile = None + """ The XML file we should read the content from. """ + + def __init__(self): + """ Constructor. """ + + # try to import the xml.sax python module. + try: + import xml.sax.saxlib + import xml.sax.saxexts + except: + raise "Import of the python xml.sax.saxlib module failed. This module is needed by the ImportXHTML python script." + + def read(self, outputwriter): + """ Start reading and parsing the XML-file. """ + + import xml.sax.saxlib + import xml.sax.saxexts + + class SaxHandler(xml.sax.saxlib.HandlerBase): + """ The SaxHandler is our event-handler SAX calls on + parsing the XML-file. """ + + tablebase = ["html","body","table"] + """ The table-base defines where we will find our table-tag + that holds all the data we are interessted at. The default + is to look at <html><body><table></table></body></html>. """ + + def __init__(self, inputreader, outputwriter): + """ Constructor. """ + + # The to a SaxInput instance pointing inputreader. + self.inputreader = inputreader + # The to a KexiDBOutput instance pointing outputwriter. + self.outputwriter = outputwriter + # The hierachy-level in the DOM-tree we are in. + self.level = 0 + # Defines if we are in the with tablebase defined DOM-element. + self.intable = False + + # Points to a KexiDBOutput.Record instance if we are in a DOM-element that defines a record. + self.record = None + # Points to a KexiDBOutput.Field instance if we are in a record's field. + self.field = None + + def startDocument(self): + sys.stdout.write('=> Starting parsing\n') + + def endDocument(self): + sys.stdout.write('=> Fineshed parsing\n') + + def startElement(self, name, attrs): + """ This method is called by SAX if a DOM-element starts. """ + + if self.level < len(self.tablebase): + if self.tablebase[self.level] != name: + self.intable = False + else: + self.intable = True + self.level += 1 + if not self.intable: + return + + # Print some debugging-output to stdout. + for idx in range(self.level): sys.stdout.write(' ') + sys.stdout.write('Element: %s' % name) + for attrName in attrs.keys(): + sys.stdout.write(' %s="%s"' % (attrName,attrs.get(attrName))) + sys.stdout.write('\n') + + # handle tr-, th- and td-tags inside the table. + if name == "tr" and (self.level == len(self.tablebase) + 1): + self.record = self.outputwriter.Record() + elif name == "td" and (self.level == len(self.tablebase) + 2): + self.field = self.outputwriter.Field() + elif name == "th" and (self.level == len(self.tablebase) + 2): + self.field = self.outputwriter.Field() + + def endElement(self, name): + """ This method is called by SAX if a DOM-Element ends. """ + + self.level -= 1 + #sys.stdout.write('EndElement:%s level:%s len(self.tablebase):%s\n' % (name,self.level,len(self.tablebase))) + + if self.record != None: + # a record is defined. so, we are looking for the matching + # end-tags to close a record or a field. + if name == "tr" and (self.level == len(self.tablebase)): + self.outputwriter.write(self.record) + self.record = None + self.field = None + elif name == "td" and (self.level == len(self.tablebase) + 1): + #if self.field == None: + # raise "Unexpected closing </td>" + self.record.setField( self.field ) + self.field = None + elif name == "th" and (self.level == len(self.tablebase) + 1): + #if self.field == None: + # raise "Unexpected closing </td>" + self.record.setHeader( self.field ) + self.field = None + + def characters(self, chars, offset, length): + """ This method is called by SAX if the text-content of a DOM-Element + was parsed. """ + + if self.field != None: + # the xml-data is unicode and we need to encode it + # to latin-1 cause KexiDB deals only with latin-1. + u = unicode(chars[offset:offset+length]) + self.field.append(u.encode("latin-1")) + + # start the job + outputwriter.begin() + # create saxhandler to handle parsing events. + handler = SaxHandler(self, outputwriter) + # we need a sax-parser and connect it with the handler. + parser = xml.sax.saxexts.make_parser() + parser.setDocumentHandler(handler) + # open the XML-file, parse the content and close the file again. + f = file(self.xmlfile, 'r') + parser.parseFile(f) + f.close() + # job is done + outputwriter.end() + +class KexiDBOutput: + """ The destination target we like to import the data to. This class + provides abstract access to the KexiDB module. """ + + class Result: + """ Holds some informations about the import-result. """ + def __init__(self, outputwriter): + self.outputwriter = outputwriter + # number of records successfully imported. + self.successcount = 0 + # number of records where import failed. + self.failedcount = 0 + + def addLog(self, record, state): + import datetime + date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M.%S") + self.outputwriter.logfile.write("%s (%s) %s\n" % (date,state,str(record))) + + def success(self, record): + """ Called if a record was written successfully. """ + print "SUCCESS: %s" % str(record) + self.successcount += 1 + if hasattr(self.outputwriter,"logfile"): + self.addLog(record, "Success") + + def failed(self, record): + """ Called if we failed to write a record. """ + print "FAILED: %s" % str(record) + self.failedcount += 1 + if hasattr(self.outputwriter,"logfile"): + self.addLog(record, "Failed") + + class Record: + """ A Record in the dataset. """ + def __init__(self): + self.fields = [] + def setHeader(self, headerfield): + self.fields.append( headerfield ) + self.isHeader = True + def setField(self, field): + self.fields.append( field ) + def __str__(self): + s = "[" + for f in self.fields: + s += "%s, " % str(f) + return s + "]" + + class Field: + """ A field in a record. """ + def __init__(self): + self.content = [] + def append(self, content): + self.content.append( content ) + def __str__(self): + return "".join(self.content) + + def __init__(self): + """ Constructor. """ + import kexiapp + keximainwindow = kexiapp.get("KexiAppMainWindow") + + try: + self.connection = keximainwindow.getConnection() + except: + raise "No connection established. Please open a project before." + + self.fieldlist = None + self.headerrecord = None + self.mapping = {} + + def begin(self): + """ Called before parsing starts. """ + print "START JOB" + if self.fieldlist == None: + raise "Invalid tableschema or fieldlist!" + global KexiDBOutput + self.result = KexiDBOutput.Result(self) + if hasattr(self,"logfilename") and self.logfilename != None and self.logfilename != "": + self.logfile = open(self.logfilename,'w') + + def end(self): + """ Called if parsing is fineshed. """ + print "END JOB" + self.logfile = None + self.mapping = {} + #self.headerrecord = None + + def getTables(self): + """ return a list of avaiable tablenames. """ + tables = self.connection.tableNames() + tables.sort() + return tables + + def setTable(self, tablename): + """ Set the tablename we like to import the data to. """ + tableschema = self.connection.tableSchema(tablename) + if tableschema == None: + raise "There exists no table with the name '%s'!" % tablename + self.fieldlist = tableschema.fieldlist() + fields = self.fieldlist.fields() + for field in fields: + print "KexiDBOutput.setTable(%s): %s(%s)" % (tablename,field.name(),field.type()) + print "names=%s" % self.fieldlist.names() + + def setMapping(self, mapping): + """ Set the tablefieldname=xmlcolnr dictonary we should map the data to. """ + self.mapping = mapping + + def setLogFile(self, logfilename): + """ Set the name of the logfile. """ + self.logfilename = logfilename + + def write(self, record): + """ Write the record to the KexiDB table. """ + + if hasattr(record, "isHeader"): + self.headerrecord = record + return + + sys.stdout.write('KexiDBOutput.write:') + for f in record.fields: + sys.stdout.write(' "%s"' % f) + sys.stdout.write('\n') + + if hasattr(self,"onWrite"): + if not self.onWrite(record): + raise RuntimeError() + delattr(self,"onWrite") + self.fieldlist = self.fieldlist.subList( list( self.mapping ) ) + + # Translate a KexiDBOutput.Record into a list of values. + values = [] + for k in self.fieldlist.names(): + values.append( str(record.fields[ int(self.mapping[k]) ]) ) + print "Import values: %s" % values + + try: + if self.connection.insertRecord(self.fieldlist, values): + self.result.success(record) + else: + self.result.failed(record) + except: + err = self.connection.lastError() + raise Exception( "Failed to insert the record:\n%s\n\n%s" % (values,err) ) + #raise Exception( "Failed to insert into table \"%s\" the record:\n%s\n%s" % (self.tableschema.name(),values,self.connection.lastError()) ) + +class GuiApp: + """ The GUI-dialog displayed to let the user define the source + XML-file and the destination KexiDB table. """ + + class InitialDialog: + def __init__(self, guiapp): + self.guiapp = guiapp + self.ok = False + + import gui + self.dialog = gui.Dialog("Import XHTML") + self.dialog.addLabel(self.dialog, "Import data from a XHTML-file to a KexiDB table.\n" + "The destination table needs to be an existing table the data should be added to.") + self.importfile = self.dialog.addFileChooser(self.dialog, + "Source File:", + gui.getHome() + "/kexidata.xhtml", + (('XHTML files', '*.xhtml'),('All files', '*'))) + + self.desttable = self.dialog.addList(self.dialog, "Destination Table:", self.guiapp.outputwriter.getTables()) + + #self.operation = self.dialog.addList(self.dialog, "Operation:", ("Insert","Update","Insert/Update")) + #self.error = self.dialog.addList(self.dialog, "On error:", ("Ask","Skip","Abort")) + + self.logfile = self.dialog.addFileChooser(self.dialog, + "Log File:", + "", + (('Logfiles', '*.log'),('All files', '*'))) + + btnframe = self.dialog.addFrame(self.dialog) + self.dialog.addButton(btnframe, "Next", self.doNext) + self.dialog.addButton(btnframe, "Cancel", self.doCancel) + self.dialog.show() + + def doCancel(self): + """ Called if the Cancel-button was pressed. """ + self.dialog.close() + self.dialog = None + #self.guiapp.InitialDialog + + def doNext(self): + """ Start to import the XML-file into the KexiDB table. """ + + self.guiapp.inputreader.xmlfile = str(self.importfile.get()) + self.guiapp.outputwriter.setTable( str(self.desttable.get()) ) + self.guiapp.outputwriter.setLogFile( str(self.logfile.get()) ) + + try: + self.guiapp.inputreader.read( self.guiapp.outputwriter ) + + msgbox = self.dialog.showMessageBox("info","Import done", + "Successfully imported records: %s\nFailed to import records: %s" % (self.guiapp.outputwriter.result.successcount, self.guiapp.outputwriter.result.failedcount) ) + msgbox.show() + + self.doCancel() + except RuntimeError, e: + pass + #except Exception, e: + # import traceback + # traceback.print_exc() + # msgbox = self.dialog.showMessageBox("error", "Error", e) + # msgbox.show() + + class MapperDialog: + """ The dialog that provides us a way to map + XHTML columns to the destination table. """ + + def __init__(self, outputwriter, record): + self.outputwriter = outputwriter + self.ok = False + fieldlist = outputwriter.fieldlist + + import gui + self.dlg = gui.Dialog("Import XHTML") + self.dlg.addLabel(self.dlg, "Define how the destination table should be mapped to the data from the XHTML file.") + values = ["",] + for i in range(len(record.fields)): + try: + values.append( "%s: %s" % (i,str(outputwriter.headerrecord.fields[i])) ) + except: + values.append( "%s: (%s)" % (i,str(record.fields[i])) ) + + self.items = [] + i = 0 + for field in fieldlist.fields(): + f = self.dlg.addFrame(self.dlg) + + l = self.dlg.addList(f, "%s:" % field.name(), values) + self.items.append( (field,l) ) + + details = "%s:" % str( field.type() ) + if field.isAutoInc(): details += "autoinc," + if field.isUniqueKey(): details += "unique," + if field.isNotNull(): details += "notnull," + if field.isNotEmpty(): details += "notempty," + self.dlg.addLabel(f, "(%s)" % details[:-1]) + + try: + variable = str( record.fields[i] ) + try: + int(variable) + i += 1 + if not field.isAutoInc(): + l.set(i) + except ValueError, e: + if not field.type() in ("Integer","BigInteger","ShortInteger","Float","Double"): + i += 1 + l.set(i) + except: + pass + + btnframe = self.dlg.addFrame(self.dlg) + self.dlg.addButton(btnframe, "Next", self.doNext) + self.dlg.addButton(btnframe, "Cancel", self.dlg.close) + self.dlg.show() + + def doNext(self): + mapping = {} + for item in self.items: + (field,l) = item + fieldname = field.name() + colnr = str( l.get() ).split(":",1)[0] + if colnr.isdigit(): + print "Table field '%s' is mapped to XML column '%s'" % (fieldname,colnr) + mapping[ fieldname ] = colnr + self.outputwriter.setMapping(mapping) + self.ok = True + self.dlg.close() + + def __init__(self, inputreader, outputwriter): + """ Constructor. """ + + self.inputreader = inputreader + self.outputwriter = outputwriter + self.outputwriter.onWrite = self.onWrite + + self.InitialDialog(self) + + def onWrite(self, record): + """ This method got called after the first record got + readed and before we start to import. """ + return self.MapperDialog(self.outputwriter, record).ok + +GuiApp( SaxInput(), KexiDBOutput() ) diff --git a/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.rc b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.rc new file mode 100644 index 00000000..0cfe7718 --- /dev/null +++ b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.rc @@ -0,0 +1,8 @@ +<KrossScripting> + <ScriptAction + name="importxhtml" + text="Import Data From XHTML File" + icon="fileimport" + interpreter="python" + file="ImportXHTML.py" /> +</KrossScripting> diff --git a/kexi/plugins/scripting/scripts/importxhtml/Makefile.am b/kexi/plugins/scripting/scripts/importxhtml/Makefile.am new file mode 100644 index 00000000..a0a424fa --- /dev/null +++ b/kexi/plugins/scripting/scripts/importxhtml/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/kexi/Makefile.global + +scriptsdir = $(kde_datadir)/kexi/scripts/importxhtml +scripts_SCRIPTS = ImportXHTML.py ImportXHTML.rc diff --git a/kexi/plugins/scripting/scripts/projectdocumentor/Makefile.am b/kexi/plugins/scripting/scripts/projectdocumentor/Makefile.am new file mode 100644 index 00000000..9d32e165 --- /dev/null +++ b/kexi/plugins/scripting/scripts/projectdocumentor/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/kexi/Makefile.global + +scriptsdir = $(kde_datadir)/kexi/scripts/projectdocumentor +scripts_SCRIPTS = ProjectDocumentor.py ProjectDocumentor.rc diff --git a/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.py b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.py new file mode 100755 index 00000000..89a60301 --- /dev/null +++ b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.py @@ -0,0 +1,186 @@ +""" +Project Documentor + +Description: +This script collects various informations about a Kexi project +and exports them to a HTML file. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +Dual-licensed under LGPL v2+higher and the BSD license. +""" + +class DataProvider: + def __init__(self): + import kexiapp + keximainwindow = kexiapp.get("KexiAppMainWindow") + + try: + self.connection = keximainwindow.getConnection() + except: + raise "No connection established. Please open the project to be documented first." + + def printConnection(self): + condata = self.connection.data() + infos = [] + for item in ("caption", "description", "driverName", "hostName", "port", "userName", "fileName", "dbPath", "localSocketFileName", "serverInfoString"): + result = getattr(condata, item)() + if result != None and result != "" and (item != "port" or result != 0): + infos.append( (item, result) ) + return infos + + def printDriver(self): + driver = self.connection.driver() + result = [ ("Version", "%s.%s" % (driver.versionMajor(),driver.versionMinor())) ] + conlist = driver.connectionsList() + if len(conlist) > 0: + result.append( ("Connections",str(conlist)) ) + return result + + def printDatabases(self): + result = [ ("Current database", self.connection.currentDatabase()) ] + dbnames = self.connection.databaseNames() + if len(dbnames) > 0: + result.append( ("Databases",str(dbnames)) ) + return result + + def printTables(self): + result = [] + for t in self.connection.tableNames(): + tableschema = self.connection.tableSchema(t) + ti = [] + for i in ("name", "caption", "description"): + v = getattr(tableschema,i)() + if v != None and v != "": + ti.append( (i,v) ) + tf = [] + for field in tableschema.fieldlist().fields(): + tfi = [] + for n in ("caption","description","type","subType","typeGroup","length","defaultValue"): + v = getattr(field,n)() + if v != None and v != "": + tfi.append( (n,v) ) + props = [] + for n in ("PrimaryKey","ForeignKey","AutoInc","UniqueKey","NotNull", "NotEmpty","Indexed","Unsigned"): + v = getattr(field,"is%s" % n)() + if v != None and v != "" and v != False and v != 0: + props.append( "%s " % n ) + if len(props) > 0: + tfi.append( ("properties",props) ) + + tf.append( (field.name(), tfi) ) + ti.append( ("fields", tf) ) + if len(ti) > 0: + result.append( (t, ti) ) + return result + + def printQueries(self): + result = [] + for q in self.connection.queryNames(): + queryschema = self.connection.querySchema(q) + qi = [] + for i in ("name", "caption", "description", "statement"): + v = getattr(queryschema,i)() + if v != None and v != "": + qi.append( (i,v) ) + if len(qi) > 0: + result.append( (q, qi) ) + return result + +class GuiApp: + def __init__(self, dataprovider): + self.dataprovider = dataprovider + + try: + import gui + except: + raise "Import of the Kross GUI module failed." + + self.dialog = gui.Dialog("Project Documentor") + + self.dialog.addLabel(self.dialog, "Save information about the project to an HTML file.") + + self.file = self.dialog.addFileChooser(self.dialog, + "File:", + gui.getHome() + "/projectdoc.html", + (('HTML files', '*.html'),('All files', '*'))) + + self.printCheckBoxes = {} + for d in dir(self.dataprovider): + if d.startswith("print"): + self.printCheckBoxes[d] = self.dialog.addCheckBox(self.dialog, d[5:], True) + + #value = getattr(self.dataprovider,d)() + #if value != None and len(value) > 0: + # f.write("<h2>%s</h2>" % d[5:]) + # f.write( self.toHTML(value) ) + + #self.exportProjectdetails = + #self.exportTableschemas = self.dialog.addCheckBox(self.dialog, "Table schemas", True) + #self.exportQueryschemas = self.dialog.addCheckBox(self.dialog, "Query schemas", True) + + btnframe = self.dialog.addFrame(self.dialog) + self.dialog.addButton(btnframe, "Save", self.doSave) + self.dialog.addButton(btnframe, "Cancel", self.dialog.close) + + self.dialog.show() + + def toHTML(self, value): + import types + result = "" + if isinstance(value, types.TupleType): + result += "<ul>" + if len(value) == 1: + result += "<li>%s</li>" % value + elif len(value) == 2: + result += "<li>%s: %s</li>" % (value[0], self.toHTML(value[1])) + elif len(value) > 2: + for item in value: + i = self.toHTML(item) + if i != "": + result += "<li>%s</li>" % i + result += "</ul>" + elif isinstance(value, types.ListType): + for item in value: + result += "%s" % self.toHTML(item) + else: + result += "%s" % value + return result + + def doSave(self): + file = str( self.file.get() ) + print "Attempting to save project documentation to file: %s" % file + + f = open(file, "w") + + f.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>") + f.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 4.01 Strict//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd\">") + f.write("<html><head><title>Project information</title>") + f.write("<style type=\"text/css\">") + f.write(" html { background-color:#fafafa; }") + f.write(" body { background-color:#ffffff; margin:1em; padding:1em; border:#99a 1px solid; color:#003; }") + f.write("</style>") + f.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />") + f.write("</head><body><h1>Project information</h1>") + + for d in dir(self.dataprovider): + if d.startswith("print"): + print "GuiApp.doSave() CHECK %s" % d + a = self.printCheckBoxes[d] + if a and a.isChecked(): + print "GuiApp.doSave() BEGIN %s" % d + value = getattr(self.dataprovider,d)() + if value != None and len(value) > 0: + f.write("<h2>%s</h2>" % d[5:]) + f.write( self.toHTML(value) ) + print "GuiApp.doSave() END %s" % d + + f.close() + + print "Successfully saved project documentation to file: %s" % file + self.dialog.close() + +GuiApp( DataProvider() ) + diff --git a/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.rc b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.rc new file mode 100644 index 00000000..bb0f6c69 --- /dev/null +++ b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.rc @@ -0,0 +1,8 @@ +<KrossScripting> + <ScriptAction + name="projectdocumentor" + text="Project Documentation Generator" + icon="contents" + interpreter="python" + file="ProjectDocumentor.py" /> +</KrossScripting> diff --git a/kexi/plugins/scripting/scripts/python/Makefile.am b/kexi/plugins/scripting/scripts/python/Makefile.am new file mode 100644 index 00000000..4b31c35a --- /dev/null +++ b/kexi/plugins/scripting/scripts/python/Makefile.am @@ -0,0 +1,2 @@ +include $(top_srcdir)/kexi/Makefile.global +SUBDIRS = kexiapp diff --git a/kexi/plugins/scripting/scripts/python/kexiapp/Makefile.am b/kexi/plugins/scripting/scripts/python/kexiapp/Makefile.am new file mode 100644 index 00000000..f0f0492d --- /dev/null +++ b/kexi/plugins/scripting/scripts/python/kexiapp/Makefile.am @@ -0,0 +1,2 @@ +kexiapppythondir = $(kde_datadir)/kexi/kross/python/kexiapp +kexiapppython_SCRIPTS = __init__.py diff --git a/kexi/plugins/scripting/scripts/python/kexiapp/__init__.py b/kexi/plugins/scripting/scripts/python/kexiapp/__init__.py new file mode 100755 index 00000000..b5224304 --- /dev/null +++ b/kexi/plugins/scripting/scripts/python/kexiapp/__init__.py @@ -0,0 +1,25 @@ +""" +Initializer for the krosskexiapp-module. + +Description: +This module provides the entry-point for python scripts +to work with a running Kexi application instance. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +Dual-licensed under LGPL v2+higher and the BSD license. +""" + +try: + import krosskexiapp +except ImportError, e: + raise "Import of the Kross KexiApp module failed.\n%s" % e + +def get(modulename): + return krosskexiapp.get(modulename) + +def currentConnection(): + mainwindow = krosskexiapp.get("KexiAppMainWindow") + return mainwindow.getConnection() |