root/branches/mbutscher/work/lib/pwiki/wikidata/WikiDataManager.py @ 234

Revision 234, 75.1 kB (checked in by mbutscher, 4 years ago)

branches/stable-2.0:
* Bug fixed: Windows: "Invalid handle" error on

command line bridge insertion plugins

branches/mbutscher/work:
* Write last writing program version into wiki db
* Several bug fixes with whoosh (index search)
* Index search: Highlight found terms and jump to

one found term on double-click

* Bug fixed: Windows: "Invalid handle" error on

command line bridge insertion plugins

Line 
1from __future__ import with_statement
2
3
4from weakref import WeakValueDictionary
5import os, os.path, time, shutil, traceback
6from threading import RLock, Thread, Condition
7# from collections import deque
8
9import re
10
11from wx import GetApp
12
13import Consts
14from pwiki.WikiExceptions import *
15
16from ..Utilities import TimeoutRLock, SingleThreadExecutor, DUMBTHREADSTOP
17
18from ..MiscEvent import MiscEventSourceMixin
19
20from .. import ParseUtilities
21from ..StringOps import mbcsDec, re_sub_escape, pathEnc, pathDec, \
22        unescapeWithRe, strToBool
23from ..DocPages import DocPage, WikiPage, FunctionalPage, AliasWikiPage
24# from ..timeView.Versioning import VersionOverview
25
26from .. import AttributeHandling
27
28from ..SearchAndReplace import SearchReplaceOperation
29
30from .. import SpellChecker
31
32import DbBackendUtils, FileStorage
33
34# Some functions import parts of the whoosh library
35
36
37
38_openDocuments = {}  # Dictionary {<path to data dir>: <WikiDataManager>}
39
40
41_globalFuncPages = WeakValueDictionary()  # weak dictionary
42        # {<funcTag starting with "global/">: <funcPage>}
43
44def isDbHandlerAvailable(dbtype):
45    wikiDataFactory, createWikiDbFunc = DbBackendUtils.getHandler(dbtype)
46    return wikiDataFactory is not None
47
48
49def createWikiDb(pWiki, dbtype, wikiName, dataDir, overwrite=False):
50    """
51    Create a new wiki database
52    pWiki -- instance of PersonalWikiFrame
53    dbtype -- internal name of database type
54    wikiName -- Name of the wiki to create
55    dataDir -- directory for storing the data files
56    overwrite -- Should already existing data be overwritten?
57    """
58    global _openDocuments
59
60    wdm = _openDocuments.get(dataDir)
61    if wdm is not None:
62        raise WikiDBExistsException(
63                _(u"Database exists already and is currently in use"))
64
65    wikiDataFactory, createWikiDbFunc = DbBackendUtils.getHandler(dbtype)
66    if wikiDataFactory is None:
67        raise NoDbHandlerException(
68                _(u"Data handler %s not available") % dbtype)
69
70    createWikiDbFunc(wikiName, dataDir, overwrite)
71
72
73def openWikiDocument(wikiConfigFilename, dbtype=None, wikiLang=None,
74        ignoreLock=False, createLock=True):
75    """
76    Create a new instance of the WikiDataManager or return an already existing
77    one
78    dbtype -- internal name of database type
79    wikiLang -- internal name of wiki language
80    dataDir -- directory for storing the data files
81    overwrite -- Should already existing data be overwritten
82    """
83    global _openDocuments
84
85    wdm = _openDocuments.get(wikiConfigFilename)
86    if wdm is not None:
87        if dbtype is not None and dbtype != wdm.getDbtype():
88            # Same database can't be opened twice with different db handlers
89            raise WrongDbHandlerException(_(u"Database is already in use "
90                    u'with database handler "%s". '
91                    u"Can't open with different handler.") %
92                    wdm.getDbtype())
93
94        if wikiLang is not None and wikiLang != wdm.getWikiDefaultWikiLanguage():
95            raise WrongWikiLanguageException(_(u"Database is already in use "
96                    u'with wiki language handler "%s". '
97                    u"Can't open with different handler.") %
98                    wdm.getWikiDefaultWikiLanguage())
99
100        wdm.incRefCount()
101        return wdm
102
103    wdm = WikiDataManager(wikiConfigFilename, dbtype, wikiLang, ignoreLock,
104            createLock)
105
106    _openDocuments[wikiConfigFilename] = wdm
107
108    return wdm
109
110
111
112#     wikiDataFactory, createWikiDbFunc = DbBackendUtils.getHandler(pWiki, dbtype)
113#     if wikiDataFactory is None:
114#         raise NoDbHandlerException("Data handler %s not available" % dbtype)
115#
116#     wd = wikiDataFactory(pWiki, dataDir)
117#     return WikiDataManager(pWiki, wd, dbtype)
118
119
120def splitConfigPathAndWord(wikiCombinedFilename):
121    """
122    wikiCombinedFilename -- Path of config filename or possibly name of a wiki file
123
124    return: tuple (cfg, wikiword) with cfg real config filepath (None if it
125            couldn't be found. wikiword is the wikiword to jump to or None
126    """
127    wikiConfig = GetApp().createWikiConfiguration()
128    if os.path.supports_unicode_filenames:
129        wikiConfigFilename = mbcsDec(wikiCombinedFilename)[0]
130    else:
131        wikiConfigFilename = wikiCombinedFilename
132    wikiWord = None
133
134    while True:
135        try:
136            # config.read(wikiConfigFile)
137            wikiConfig.loadConfig(wikiConfigFilename)
138            return wikiConfigFilename, wikiWord
139        except Exception, e:
140            # try to recover by checking if the parent dir contains the real wiki file
141            # if it does the current wiki file must be a wiki word file, so open the
142            # real wiki to the wiki word.
143#                 try:
144            parentDir = os.path.dirname(os.path.dirname(wikiConfigFilename))
145            if parentDir:
146                try:
147                    wikiFiles = [file for file in os.listdir(parentDir) \
148                            if file.endswith(".wiki")]
149                    if len(wikiFiles) > 0:
150                        wikiWord = os.path.basename(wikiConfigFilename)
151                        wikiWord = wikiWord[0 : len(wikiWord) - 5]
152
153                        # if this is win95 or < the file name could be a 8.3 alias, file~1 for example
154                        windows83Marker = wikiWord.find("~")
155                        if windows83Marker != -1:
156                            wikiWord = wikiWord[0:windows83Marker]
157                            matchingFiles = [file for file in wikiFiles \
158                                    if file.lower().startswith(wikiWord)]
159                            if matchingFiles:
160                                wikiWord = matchingFiles[0]
161                        wikiConfigFilename = os.path.join(parentDir, wikiFiles[0])
162                        continue
163                except (WindowsError, IOError, OSError):
164                    # something went wrong -> give up
165                    traceback.print_exc()
166                    return None, None
167
168            return None, None
169   
170   
171def getGlobalFuncPage(funcTag):
172    global _globalFuncPages
173   
174    if len(funcTag) == 0:
175        return None  # TODO throw exception?
176
177    if not funcTag.startswith(u"global/"):
178        return None  # TODO throw exception?
179
180    value = _globalFuncPages.get(funcTag)
181    if value is None:
182        value = FunctionalPage(None, funcTag)
183        _globalFuncPages[funcTag] = value
184
185    return value
186
187
188
189# TODO Remove this hackish solution
190
191# class WikiDataSynchronizedFunction:
192#     def __init__(self, proxy, lock, function):
193#         self.proxy = proxy
194#         self.accessLock = lock
195#         self.callFunction = function
196#
197#     def __call__(self, *args, **kwargs):
198#         return callInMainThread(self.callFunction, *args, **kwargs)
199
200
201# class WikiDataSynchronizedFunction:
202#     def __init__(self, proxy, lock, function):
203#         self.proxy = proxy
204#         self.accessLock = lock
205#         self.callFunction = function
206#
207#     def __call__(self, *args, **kwargs):
208#         if not self.accessLock.acquire(False):
209#             print "----Lock acquired by"
210#             print "".join(traceback.format_list(self.proxy.accessLockStackTrace))
211#             print
212#             print "----Lock requested by"
213#             print traceback.print_stack()
214#             print
215#
216#             self.accessLock.acquire()
217#         
218#         self.proxy.accessLockStackTrace = traceback.extract_stack()
219#         try:
220# #             print "WikiDataSynchronizedFunction", repr(self.callFunction), repr(args)
221#             return callInMainThread(self.callFunction, *args, **kwargs)
222#         finally:
223# #             self.proxy.accessLockStackTrace = []
224#             self.accessLock.release()
225
226
227class WikiDataSynchronizedFunction:
228    def __init__(self, proxy, lock, function):
229        self.proxy = proxy
230        self.proxyAccessLock = lock
231        self.callFunction = function
232
233    def __call__(self, *args, **kwargs):
234#         if not self.proxyAccessLock.acquire(False):
235#             print "----Lock acquired by"
236#             print "".join(traceback.format_list(self.proxy.accessLockStackTrace))
237#             print
238#             print "----Lock requested by"
239#             print traceback.print_stack()
240#             print
241
242        with self.proxyAccessLock:
243#         self.proxy.accessLockStackTrace = traceback.extract_stack()
244            return self.callFunction(*args, **kwargs)
245
246
247class WikiDataSynchronizedProxy:
248    """
249    Proxy class for synchronized access to a WikiData instance
250    """
251    def __init__(self, wikiData):
252        self.wikiData = wikiData
253        self.proxyAccessLock = TimeoutRLock(Consts.DEADBLOCKTIMEOUT)
254#         self.accessLockStackTrace = None
255
256
257    def __getattr__(self, attr):
258        result = WikiDataSynchronizedFunction(self, self.proxyAccessLock,
259                getattr(self.wikiData, attr))
260               
261        self.__dict__[attr] = result
262
263        return result
264
265
266class WikiDataManager(MiscEventSourceMixin):
267    """
268    Wraps a WikiData object and provides services independent
269    of database backend, especially creation of WikiPage objects.
270
271    When the open wiki database changes, a new DataManager is created.
272
273    When asking for a WikiPage for the same word twice and the first object
274    exists yet, no new object is created, but the same returned.
275
276    WikiDataManager holds internally a reference count to know how many
277    PersonalWikiFrame instances refer to it. Call release() to
278    decrement the refcount. If it goes to zero, the wrapped WikiData
279    instance will be closed. The refcount starts with 1 when creating
280    a WikiDataManager instance.
281    """
282   
283    # Update executor queue for index search update
284    UEQUEUE_INDEX = 2
285
286    def __init__(self, wikiConfigFilename, dbtype, wikiLangName, ignoreLock=False,
287            createLock=True):
288        MiscEventSourceMixin.__init__(self)
289
290        self.lockFileName = wikiConfigFilename + u".lock"
291        if not ignoreLock and os.path.exists(pathEnc(self.lockFileName)):
292            raise LockedWikiException(
293                    _(u"Wiki is probably already in use by other instance"))
294
295        if createLock:
296            try:
297                f = open(pathEnc(self.lockFileName), "w")
298                self.writeAccessDenied = False
299                f.close()
300            except IOError:
301                self.lockFileName = None
302                self.writeAccessDenied = True
303        else:
304            self.lockFileName = None
305
306        wikiConfig = GetApp().createWikiConfiguration()
307        self.connected = False
308        self.readAccessFailed = False
309        self.writeAccessFailed = False
310        self.writeAccessDenied = False
311
312        wikiConfig.loadConfig(wikiConfigFilename)
313
314        # config variables
315        wikiName = wikiConfig.get("main", "wiki_name")
316        dataDir = wikiConfig.get("wiki_db", "data_dir")
317
318        # except Exception, e:
319        if wikiName is None or dataDir is None:
320            self._releaseLockFile()
321            raise BadConfigurationFileException(
322                    _(u"Wiki configuration file is corrupted"))
323
324        # os.access does not answer reliably if file is writable
325        # (at least on Windows), therefore we have to just open it
326        # in writable mode
327        try:
328            f = open(pathEnc(wikiConfigFilename), "r+b")
329            self.writeAccessDenied = False
330            f.close()
331        except IOError:
332            self.writeAccessDenied = True
333
334        self.wikiConfiguration = wikiConfig
335
336        wikiConfig.setWriteAccessDenied(self.writeAccessDenied or
337                self.getWriteAccessDeniedByConfig())
338
339        # absolutize the path to data dir if it's not already
340        if not os.path.isabs(dataDir):
341            dataDir = os.path.join(os.path.dirname(wikiConfigFilename), dataDir)
342
343#         dataDir = mbcsDec(os.path.abspath(dataDir), "replace")[0]
344        dataDir = pathDec(os.path.abspath(dataDir))
345
346#         self.wikiConfigFilename = wikiConfigFilename
347
348        if not dbtype:
349            wikidhName = wikiConfig.get("main",
350                    "wiki_database_type", "")
351        else:
352            wikidhName = dbtype
353
354        if not wikidhName:
355            # Probably old database version without handler tag
356            self._releaseLockFile()
357            raise UnknownDbHandlerException(
358                    _(u'No data handler information found, probably '
359                    u'"Original Gadfly" is right.'))
360
361        if not isDbHandlerAvailable(wikidhName):
362            self._releaseLockFile()
363            raise DbHandlerNotAvailableException(
364                    _(u'Required data handler "%s" unknown to WikidPad') % wikidhName)
365
366        wikiDataFactory, createWikiDbFunc = DbBackendUtils.getHandler(wikidhName)
367        if wikiDataFactory is None:
368            self._releaseLockFile()
369            raise NoDbHandlerException(
370                    _(u'Error on initializing data handler "%s"') % wikidhName)
371
372        if wikiLangName is None:
373            wikiLangName = wikiConfig.get("main", "wiki_wikiLanguage",
374                    "wikidpad_default_2_0")
375        else:
376            wikiConfig.set("main", "wiki_wikiLanguage", wikiLangName)
377
378        if GetApp().getWikiLanguageDescription(wikiLangName) is None:
379            self._releaseLockFile()
380            raise UnknownWikiLanguageException(
381                    _(u'Required wiki language handler "%s" not available') %
382                            wikiLangName)
383
384        self.wikiLangName = wikiLangName
385        self.ensureWikiTempDir()
386
387        wikiData = wikiDataFactory(self, dataDir, self.getWikiTempDir())
388
389        self.baseWikiData = wikiData
390        self.autoLinkRelaxInfo = None
391
392        # Set of camelcase words not to see as wiki words
393        self.ccWordBlacklist = None
394        self.wikiData = WikiDataSynchronizedProxy(self.baseWikiData)
395        self.wikiPageDict = WeakValueDictionary()
396        self.funcPageDict = WeakValueDictionary()
397
398        self.updateExecutor = SingleThreadExecutor(4)
399        self.pageRetrievingLock = TimeoutRLock(Consts.DEADBLOCKTIMEOUT)
400
401        self.wikiName = wikiName
402        self.dataDir = dataDir
403        self.dbtype = wikidhName
404
405        self.whooshIndex = None
406
407        # TODO: Only initialize on demand
408        self.onlineSpellCheckerSession = None
409        if SpellChecker.isSpellCheckSupported():
410            self.onlineSpellCheckerSession = \
411                    SpellChecker.SpellCheckerSession(self)
412            self.onlineSpellCheckerSession.rereadPersonalWordLists()
413
414        self.refCount = 1
415
416
417    def checkDatabaseFormat(self):
418        """
419        Returns a pair (<frmcode>, <plain text>) where frmcode is an integer
420        and means:
421        0: Up to date,  1: Update needed,  2: Unknown format, update not possible
422        """
423        return self.wikiData.checkDatabaseFormat()
424
425
426    def connect(self):
427        # Connect might be called too often, so check if it was already done
428        if self.connected:
429            return
430
431        writeException = None
432        try:
433            self.wikiData.connect()
434        except DbWriteAccessError, e:
435            traceback.print_exc()
436            writeException = e
437
438        # Path to file storage
439        fileStorDir = os.path.join(os.path.dirname(self.getWikiConfigPath()),
440                "files")
441
442        self.fileStorage = FileStorage.FileStorage(self, fileStorDir)
443
444        # Set file storage according to configuration
445        fs = self.fileStorage
446
447        fs.setModDateMustMatch(self.getWikiConfig().getboolean("main",
448                "fileStorage_identity_modDateMustMatch", False))
449        fs.setFilenameMustMatch(self.getWikiConfig().getboolean("main",
450                "fileStorage_identity_filenameMustMatch", False))
451        fs.setModDateIsEnough(self.getWikiConfig().getboolean("main",
452                "fileStorage_identity_modDateIsEnough", False))
453
454        self.wikiConfiguration.getMiscEvent().addListener(self)
455        GetApp().getMiscEvent().addListener(self)
456
457        self._updateCcWordBlacklist()
458       
459        self.readAccessFailed = False
460        self.writeAccessFailed = False
461        self.noAutoSaveFlag = False # Flag is set (by PersonalWikiFrame),
462                # if some error occurred during saving and the user doesn't want
463                # to retry saving. WikiDataManager does not change or respect
464                # this flag.
465               
466        self.autoReconnectTriedFlag = False
467       
468        self.connected = True
469       
470        if writeException:
471            self.writeAccessFailed = True
472            raise writeException
473           
474        self.updateExecutor.start()
475       
476        self.pushDirtyMetaDataUpdate()
477       
478#         if not self.isReadOnlyEffect():
479#             words = self.getWikiData().getWikiWordsForMetaDataState(0)
480#             for word in words:
481#                 self.updateExecutor.executeAsync(1, self._runDatabaseUpdate,
482#                         word)
483
484    def _runDatabaseUpdate(self, word, step, threadstop=DUMBTHREADSTOP):
485        time.sleep(0.1)
486        try:
487            page = self.getWikiPage(word)
488
489            if step == Consts.WIKIWORDMETADATA_STATE_ATTRSPROCESSED:
490                if page.runDatabaseUpdate(step=step, threadstop=threadstop):
491                    if self.isSearchIndexEnabled():
492                        self.updateExecutor.executeAsyncWithThreadStop(
493                                self.UEQUEUE_INDEX,
494                                self._runDatabaseUpdate, word,
495                                Consts.WIKIWORDMETADATA_STATE_SYNTAXPROCESSED)
496
497            elif step == Consts.WIKIWORDMETADATA_STATE_SYNTAXPROCESSED:
498                if self.isSearchIndexEnabled():
499                    page.runDatabaseUpdate(step=step, threadstop=threadstop)
500            else:   # should be: step == Consts.WIKIWORDMETADATA_STATE_DIRTY:
501                if page.runDatabaseUpdate(step=step, threadstop=threadstop):
502                    self.updateExecutor.executeAsyncWithThreadStop(1,
503                            self._runDatabaseUpdate, word,
504                            Consts.WIKIWORDMETADATA_STATE_ATTRSPROCESSED)
505
506
507
508        except WikiWordNotFoundException:
509            return
510
511
512    def incRefCount(self):
513        self.refCount += 1
514        return self.refCount
515
516    def _releaseLockFile(self):
517        """
518        Release lock file if it was created before
519        """
520        if self.lockFileName is not None:
521            try:
522                os.unlink(pathEnc(self.lockFileName))
523            except:
524                traceback.print_exc()
525
526
527    def release(self):
528        """
529        Inform this instance that it is no longer needed by one of the
530        holding PersonalWikiFrame objects.
531        Decrements the internal refcounter, if it goes to zero, the used
532        WikiData instance is closed, cached wiki pages are invalidated.
533       
534        Don't call any other method on the instance after calling this method.
535        """
536        global _openDocuments
537
538        self.refCount -= 1
539
540        if self.refCount <= 0:
541            self.refCount = 0
542            self.updateExecutor.end(hardEnd=True)  # TODO Inform user as this may take some time
543
544            # Invalidate all cached pages to prevent yet running threads from
545            # using them
546            for page in self.wikiPageDict.values():
547                page.invalidate()
548            for page in self.funcPageDict.values():
549                page.invalidate()
550           
551            wikiTempDir = self.getWikiTempDir()
552
553            if self.wikiData is not None:
554                self.wikiData.close()
555                self.wikiData = None
556                self.baseWikiData = None
557           
558            if self.whooshIndex is not None:
559                self.whooshIndex.close()
560                self.whooshIndex = None
561
562            GetApp().getMiscEvent().removeListener(self)
563
564            del _openDocuments[self.getWikiConfig().getConfigPath()]
565
566            self._releaseLockFile()
567
568            if wikiTempDir is not None:
569                # Warning!!! rmtree() is very dangerous, don't make a mistake here!
570                shutil.rmtree(wikiTempDir, True)
571
572        return self.refCount
573
574
575    def getDbtype(self):
576        return self.dbtype
577
578    def getWikiData(self):
579        return self.wikiData
580
581    def getFileStorage(self):
582        return self.fileStorage
583
584    def getWikiConfig(self):
585        return self.wikiConfiguration
586       
587    def getWikiConfigPath(self):
588        return self.wikiConfiguration.getConfigPath()
589       
590    def getWikiPath(self):
591        return os.path.dirname(self.getWikiConfigPath())       
592
593    def getWikiName(self):
594        return self.wikiName
595       
596    def getDataDir(self):
597        return self.dataDir
598       
599    def getCollator(self):
600        return GetApp().getCollator()
601       
602    def getWikiDefaultWikiLanguage(self):
603        """
604        Returns the internal name of the default wiki language of this wiki.
605       
606        Single pages may have different languages (not implemented yet).
607        """
608        return self.getWikiConfig().get("main", "wiki_wikiLanguage",
609                "wikidpad_default_2_0")
610
611    def getPageTitlePrefix(self):
612        """
613        Return the default prefix for a wiki page main title.
614        By default, it is u"++ "
615        """
616        return unescapeWithRe(self.getWikiConfig().get(
617                "main", "wikiPageTitlePrefix", "++"))
618
619    def getWikiTempDir(self):
620#         if GetApp().getGlobalConfig().getboolean("main", "tempFiles_inWikiDir",
621#                 False) and not self.isReadOnlyEffect():
622#             return os.path.join(os.path.dirname(self.getWikiConfigPath()),
623#                     "temp")
624#         else:
625
626        # Warning! The returned directory will be deleted with shutil.rmtree when the wiki is
627        # finally released!
628        return None
629
630
631    def ensureWikiTempDir(self):
632        """
633        Try to ensure existence of wiki temp directory
634        """
635        tempDir = self.getWikiTempDir()
636       
637        if tempDir is not None:
638            try:
639                os.makedirs(tempDir)
640            except OSError:
641                self.setReadAccessFailed(True)
642
643
644    def getOnlineSpellCheckerSession(self):
645        return self.onlineSpellCheckerSession
646
647
648    def createOnlineSpellCheckerSessionClone(self):
649        if self.onlineSpellCheckerSession is None:
650            return None
651       
652        return self.onlineSpellCheckerSession.cloneForThread()
653
654
655    def getNoAutoSaveFlag(self):
656        """
657        Flag is set (by PersonalWikiFrame),
658        if some error occurred during saving and the user doesn't want
659        to retry saving. WikiDataManager does not change or respect
660        this flag.
661        """
662        return self.noAutoSaveFlag
663       
664    def setNoAutoSaveFlag(self, val):
665        self.noAutoSaveFlag = val
666        # TODO send message?
667
668
669    def getReadAccessFailed(self):
670        """
671        Flag is set (by PersonalWikiFrame),
672        """
673        return self.readAccessFailed
674       
675    def setReadAccessFailed(self, val):
676        self.readAccessFailed = val
677        # TODO send message?
678
679
680    def getWriteAccessFailed(self):
681        """
682        Flag is set (by PersonalWikiFrame),
683        """
684        return self.writeAccessFailed
685       
686    def setWriteAccessFailed(self, val):
687        self.writeAccessFailed = val
688        # TODO send message?
689       
690    def getWriteAccessDenied(self):
691        """
692        Flag is set (by PersonalWikiFrame),
693        """
694        return self.writeAccessDenied
695       
696    def getWriteAccessDeniedByConfig(self):
697        return self.getWikiConfig().getboolean("main", "wiki_readOnly")
698
699
700    def setWriteAccessDeniedByConfig(self, newValue):
701        wikiConfig = self.getWikiConfig()
702
703        if wikiConfig.getboolean("main", "wiki_readOnly") == newValue:
704            return
705
706        if self.writeAccessFailed or self.writeAccessDenied:
707            return  # Don't touch if readonly for other reasons
708
709        if newValue:
710            wikiConfig.set("main", "wiki_readOnly", "True")
711            wikiConfig.save()
712            wikiConfig.setWriteAccessDenied(True)
713        else:
714            wikiConfig.setWriteAccessDenied(False)
715            wikiConfig.set("main", "wiki_readOnly", "False")
716
717
718    def pushUpdatePage(self, page):
719        self.updateExecutor.executeAsyncWithThreadStop(0, page.runDatabaseUpdate)
720
721
722    def getUpdateExecutor(self):
723        return self.updateExecutor
724       
725       
726    def pushDirtyMetaDataUpdate(self):
727        """
728        Push all words for which meta-data is set dirty in the queue
729        of the update executor
730        """
731        if not self.isReadOnlyEffect():
732            self.updateExecutor.clearDeque(1)
733            self.updateExecutor.clearDeque(self.UEQUEUE_INDEX)
734            if not strToBool(self.getWikiData().getDbSettingsValue(
735                    "syncWikiWordMatchtermsUpToDate", "0")):
736
737                for wikiWord in self.getWikiData().getAllDefinedWikiPageNames():
738                    wikiPage = self._getWikiPageNoErrorNoCache(wikiWord)
739                    if isinstance(wikiPage, AliasWikiPage):
740                        # This should never be an alias page, so fetch the
741                        # real underlying page
742                        # This can only happen if there is a real page with
743                        # the same name as an alias
744                        wikiPage = WikiPage(self, wikiWord)
745   
746                    wikiPage.refreshSyncUpdateMatchTerms()
747
748                self.getWikiData().setDbSettingsValue(
749                        "syncWikiWordMatchtermsUpToDate", "1")
750
751            words0 = self.getWikiData().getWikiWordsForMetaDataState(
752                    Consts.WIKIWORDMETADATA_STATE_DIRTY)
753            words1 = self.getWikiData().getWikiWordsForMetaDataState(
754                    Consts.WIKIWORDMETADATA_STATE_ATTRSPROCESSED)
755
756            with self.updateExecutor.getDequeCondition():
757                for word in words0:
758                    self.updateExecutor.executeAsyncWithThreadStop(1, self._runDatabaseUpdate,
759                            word, Consts.WIKIWORDMETADATA_STATE_DIRTY)
760   
761                for word in words1:
762                    self.updateExecutor.executeAsyncWithThreadStop(1, self._runDatabaseUpdate,
763                            word, Consts.WIKIWORDMETADATA_STATE_ATTRSPROCESSED)
764           
765            if self.isSearchIndexEnabled():
766                words2 = self.getWikiData().getWikiWordsForMetaDataState(
767                        Consts.WIKIWORDMETADATA_STATE_SYNTAXPROCESSED)
768
769                with self.updateExecutor.getDequeCondition():
770                    for word in words2:
771                        self.updateExecutor.executeAsyncWithThreadStop(
772                                self.UEQUEUE_INDEX, self._runDatabaseUpdate,
773                                word, Consts.WIKIWORDMETADATA_STATE_SYNTAXPROCESSED)
774
775
776    def isReadOnlyEffect(self):
777        """
778        Return true if underlying wiki is effectively read-only, this means
779        "for any reason", regardless if error or intention.
780        """
781        return self.writeAccessFailed or self.writeAccessDenied or \
782                self.getWriteAccessDeniedByConfig()
783
784
785    def getAutoReconnectTriedFlag(self):
786        """
787        Flag is set (by PersonalWikiFrame),
788        if after some read/write error the program already tried to reconnect
789        to database and should not automatically try again, only on user
790        request.
791        """
792        return self.autoReconnectTriedFlag
793       
794    def setAutoReconnectTriedFlag(self, val):
795        self.autoReconnectTriedFlag = val
796        # TODO send message?
797
798
799    def isDefinedWikiPage(self, wikiWord):
800        """
801        Check if a page with this name exists (no aliases)
802        """
803        return self.wikiData.isDefinedWikiPage(wikiWord)
804
805
806    def isDefinedWikiLink(self, wikiWord):
807        """
808        check if a word is a valid wikiword (page name or alias)
809        """
810        return self.wikiData.isDefinedWikiLink(wikiWord)
811
812
813    # For plugin compatibility
814    isDefinedWikiWord = isDefinedWikiLink
815
816    def isCreatableWikiWord(self, wikiWord):
817        """
818        Returns True if wikiWord can be created in the database. Does not
819        check against regular expression of wiki language, but checks if word
820        already exists or (if document is in caseless mode) if word with
821        different case but otherwise the same already exists.
822        If this returns False, self.getUnAliasedWikiWord(wikiWord) must be able to
823        return the existing word whose existence prevents creation of wikiWord
824
825        TODO: Check against existing aliases
826        """
827        # TODO: Caseless mode
828#         return not self.wikiData.isDefinedWikiWord(wikiWord)
829        return not self.getUnAliasedWikiWord(wikiWord)
830
831
832    def getNormcasedWikiWord(self, word):
833        """
834        Get normcased version of word. It isn't checked if word exists.
835        Currently this function just calls word.lower().
836        """
837        return word.lower()
838
839    def getAllDefinedWikiPageNames(self):
840        """
841        get the names of all wiki pages in the db, no aliases, no functional
842        pages.
843        Function must work for read-only wiki.
844        """
845        return self.wikiData.getAllDefinedWikiPageNames()
846
847
848    def getWikiPage(self, wikiWord):
849        """
850        Fetch a WikiPage for the wikiWord, throws WikiWordNotFoundException
851        if word doesn't exist
852        """
853        with self.pageRetrievingLock:
854            if not self.isDefinedWikiLink(wikiWord):
855                raise WikiWordNotFoundException(
856                        _(u"Word '%s' not in wiki") % wikiWord)
857   
858            return self.getWikiPageNoError(wikiWord)
859
860
861    def getWikiPageNoError(self, wikiWord):
862        """
863        fetch a WikiPage for the wikiWord. If it doesn't exist, return
864        one without throwing an error.
865
866        Asking for the same wikiWord twice returns the same object if
867        it wasn't garbage collected yet.
868        """
869        with self.pageRetrievingLock:
870#             value = self.wikiPageDict.get(wikiWord)
871#             
872#             if value is not None and isinstance(value, AliasWikiPage):
873#                 # Check if existing alias page is up to date
874#                 realWikiWord1 = value.getNonAliasPage().getWikiWord()
875#                 realWikiWord2 = self.wikiData.getUnAliasedWikiWord(wikiWord)
876#
877#                 if realWikiWord1 != realWikiWord2:
878#                     # if not, retrieve new page
879#                     value = None
880#
881#             if value is None:
882#                 # No active page available
883#                 realWikiWord = self.getUnAliasedWikiWordOrAsIs(wikiWord)
884#                 if wikiWord == realWikiWord:
885#                     # no alias
886#                     value = WikiPage(self, wikiWord)
887#                 else:
888#                     realpage = self.getWikiPageNoError(realWikiWord)
889#                     value = AliasWikiPage(self, wikiWord, realpage)
890
891
892            value = self._getWikiPageNoErrorNoCache(wikiWord)
893           
894            self.wikiPageDict[wikiWord] = value
895
896            if not value.getMiscEvent().hasListener(self):
897                value.getMiscEvent().addListener(self)
898
899        return value
900
901
902    def _getWikiPageNoErrorNoCache(self, wikiWord):
903        """
904        Similar to getWikiPageNoError, but does not save retrieved
905        page in cache if it isn't there yet.
906        """
907        if wikiWord is None:
908            raise InternalError("None as wikiWord for "
909                    "WikiDocument._getWikiPageNoErrorNoCache")
910
911        with self.pageRetrievingLock:
912            value = self.wikiPageDict.get(wikiWord)
913   
914            if value is not None and isinstance(value, AliasWikiPage):
915                # Check if existing alias page is up to date
916                realWikiWord1 = value.getNonAliasPage().getWikiWord()
917                realWikiWord2 = self.wikiData.getUnAliasedWikiWord(wikiWord)
918               
919                if realWikiWord1 != realWikiWord2:
920                    # if not, retrieve new page
921                    value = None
922           
923            if value is None:
924                # No active page available
925                realWikiWord = self.getUnAliasedWikiWordOrAsIs(wikiWord)
926#                 print "--_getWikiPageNoErrorNoCache13", repr((wikiWord, realWikiWord))
927                if wikiWord == realWikiWord:
928                    # no alias
929                    value = WikiPage(self, wikiWord)
930                else:
931                    realpage = self.getWikiPageNoError(realWikiWord)
932                    value = AliasWikiPage(self, wikiWord, realpage)
933   
934            return value
935
936
937
938    def createWikiPage(self, wikiWord, suggNewPageTitle=None):
939        """
940        Create a new wikiPage for the wikiWord.
941        suggNewPageTitle -- if not None contains the title of the page to create
942                (without syntax specific prefix).
943        """
944        with self.pageRetrievingLock:
945            page = self.getWikiPageNoError(wikiWord)
946            page.setSuggNewPageTitle(suggNewPageTitle)
947            return page
948
949
950    def getFuncPage(self, funcTag):
951        """
952        Retrieve a functional page
953        """
954        global _globalFuncPages
955
956        with self.pageRetrievingLock:
957            if funcTag.startswith(u"global/"):
958                value = getGlobalFuncPage(funcTag)
959            else:
960                value = self.funcPageDict.get(funcTag)
961                if value is None:
962                    value = FunctionalPage(self, funcTag)
963                    self.funcPageDict[funcTag] = value
964   
965            if not value.getMiscEvent().hasListener(self):
966                value.getMiscEvent().addListener(self)
967   
968            return value
969
970#     def getVersionOverview(self, unifName):
971#         """
972#         Get the version overview for an object with name unifName.
973#         """
974#         value = self.versionOverviewDict.get(unifName)
975#         if value is None:
976#             value = VersionOverview(self, unifName)
977#             value.readOverview()
978#             self.versionOverviewDict[unifName] = value
979#         
980#         return value
981#     
982#     def getExistingVersionOverview(self, unifName):
983#         """
984#         Get the version overview for an object with name unifName. If a
985#         version overview wasn't created already (in database or cache),
986#         None is returned.
987#         """
988#         value = self.versionOverviewDict.get(unifName)
989#         if value is None:
990#             value = VersionOverview(self, unifName)
991#             if value.isNotInDatabase():
992#                 return None
993#
994#             value.readOverview()
995#             self.versionOverviewDict[unifName] = value
996#         
997#         return value
998
999
1000    # Datablock function delegates
1001    def getDataBlockUnifNamesStartingWith(self, startingWith):
1002        """
1003        Return all unified names starting with startingWith (case sensitive)
1004        """
1005        return self.wikiData.getDataBlockUnifNamesStartingWith(startingWith)
1006
1007    def hasDataBlock(self, unifName):
1008        """
1009        Return if datablock exists.
1010
1011        This works also with wiki pages (unified name starting with "wikipage/")
1012        but does not return aliases in this case
1013        """
1014        if unifName.startswith(u"wikipage/"):
1015            return self.isDefinedWikiPage(unifName[9:])
1016           
1017        # TODO Create native method in WikiData classes
1018        return self.guessDataBlockStoreHint(unifName) is not None
1019
1020
1021    def retrieveDataBlock(self, unifName, default=""):
1022        """
1023        Retrieve data block as binary string.
1024        """
1025        return self.wikiData.retrieveDataBlock(unifName, default=default)
1026
1027    def retrieveDataBlockAsText(self, unifName, default=u""):
1028        """
1029        Retrieve data block as unicode string (assuming it was encoded properly)
1030        and with normalized line-ending (Un*x-style).
1031        """
1032        return self.wikiData.retrieveDataBlockAsText(unifName, default=default)
1033
1034    def storeDataBlock(self, unifName, newdata, storeHint=None):
1035        """
1036        Store newdata under unified name. If previously data was stored under the
1037        same name, it is deleted.
1038       
1039        unifName -- unistring. Unified name to store data under
1040        newdata -- Data to store, either bytestring or unistring. The latter one
1041            will be converted using utf-8 before storing and the file gets
1042            the appropriate line-ending of the OS for external data blocks .
1043        storeHint -- Hint if data should be stored intern in table or extern
1044            in a file (using DATABLOCK_STOREHINT_* constants from Consts.py).
1045        """
1046        return self.wikiData.storeDataBlock(unifName, newdata, storeHint)
1047
1048
1049    def guessDataBlockStoreHint(self, unifName):
1050        """
1051        Return a guess of the store hint used to store the block last time.
1052        Returns one of the DATABLOCK_STOREHINT_* constants from Consts.py.
1053        The function is allowed to return the wrong value (therefore a guess).
1054        It returns None for non-existing data blocks.
1055        """
1056        return self.wikiData.guessDataBlockStoreHint(unifName)
1057
1058
1059    def deleteDataBlock(self, unifName):
1060        """
1061        Delete data block with the associated unified name. If the unified name
1062        is not in database, nothing happens.
1063        """
1064        return self.wikiData.deleteDataBlock(unifName)
1065
1066
1067    def renameDataBlock(self, oldUnifName, newUnifName):
1068        """
1069        Renames data block with oldUnifName to newUnifName. Tries to preserve
1070        storage hint. If data block with newUnifName exists, it is overwritten.
1071        Currently if oldUnifName doesn't exist, the function does nothing
1072
1073        TODO: Native support in WikiData classes.
1074        """
1075        sh = self.guessDataBlockStoreHint(oldUnifName)
1076        if sh is None:
1077            return
1078
1079        content = self.retrieveDataBlock(oldUnifName, default=None)
1080        self.storeDataBlock(newUnifName, content, storeHint=sh)
1081        self.deleteDataBlock(oldUnifName)
1082
1083
1084
1085    # TODO Remove if not needed
1086    def checkFileSignatureForWikiWordAndMarkDirty(self, word):
1087        """
1088        First checks if file signature is valid, if not, the
1089        "metadataprocessed" field of the word is set to 0 to mark
1090        meta-data as not up-to-date. At last the signature is
1091        refreshed.
1092       
1093        This all is done inside the lock of the WikiData so it is
1094        somewhat atomically.
1095        """
1096        if self.isReadOnlyEffect():
1097            return True  # TODO Error message?
1098
1099        wikiData = self.getWikiData()
1100       
1101        proxyAccessLock = getattr(wikiData, "proxyAccessLock", None)
1102        if proxyAccessLock is not None:
1103            proxyAccessLock.acquire()
1104        try:
1105            valid = wikiData.validateFileSignatureForWord(word)
1106
1107            if not valid:
1108                wikiData.setMetaDataState(word,
1109                        Consts.WIKIWORDMETADATA_STATE_DIRTY)
1110                wikiData.refreshFileSignatureForWord(word)
1111
1112                wikiPage = self.wikiPageDict.get(word)
1113                if wikiPage is not None:
1114                    wikiPage.markTextChanged()
1115
1116            return valid
1117        finally:
1118            if proxyAccessLock is not None:
1119                proxyAccessLock.release()
1120
1121    def checkFileSignatureForAllWikiWordsAndMarkDirty(self):
1122        if self.isReadOnlyEffect():
1123            return True  # TODO Error message?
1124
1125        wikiData = self.getWikiData()
1126       
1127        proxyAccessLock = getattr(wikiData, "proxyAccessLock", None)
1128        if proxyAccessLock is not None:
1129            proxyAccessLock.acquire()
1130        try:
1131            for word in self.getAllDefinedWikiPageNames():
1132                if not wikiData.validateFileSignatureForWord(word):
1133                    wikiData.setMetaDataState(word,
1134                            Consts.WIKIWORDMETADATA_STATE_DIRTY)
1135                    wikiData.refreshFileSignatureForWord(word)
1136
1137                    wikiPage = self.wikiPageDict.get(word)
1138                    if wikiPage is not None:
1139                        wikiPage.markTextChanged()
1140        finally:
1141            if proxyAccessLock is not None:
1142                proxyAccessLock.release()
1143
1144
1145
1146    def initiateFullUpdate(self, progresshandler):
1147        self.updateExecutor.end(hardEnd=True)
1148        self.getWikiData().refreshDefinedContentNames()
1149
1150        # get all of the wikiWords
1151        wikiWords = self.getWikiData().getAllDefinedWikiPageNames()
1152
1153        progresshandler.open(len(wikiWords) + 1)
1154
1155        try:
1156            step = 0
1157
1158            # Update search terms which are generated synchronously.
1159            #   Some of them are essential to find anything or to follow
1160            #   links.
1161            for wikiWord in wikiWords:
1162                progresshandler.update(step, _(u"Update basic link info"))
1163                wikiPage = self._getWikiPageNoErrorNoCache(wikiWord)
1164                if isinstance(wikiPage, AliasWikiPage):
1165                    # This should never be an alias page, so fetch the
1166                    # real underlying page
1167                    # This can only happen if there is a real page with
1168                    # the same name as an alias
1169                    wikiPage = WikiPage(self, wikiWord)
1170
1171                wikiPage.refreshSyncUpdateMatchTerms()
1172               
1173                step += 1
1174           
1175            self.getWikiData().setDbSettingsValue(
1176                    "syncWikiWordMatchtermsUpToDate", "1")
1177           
1178            progresshandler.update(step, _(u"Starting update thread"))
1179
1180            self.updateExecutor.start()
1181
1182            self.getWikiData().fullyResetMetaDataState()
1183            self.pushDirtyMetaDataUpdate()
1184
1185        finally:
1186            progresshandler.close()
1187            self.updateExecutor.start()
1188
1189
1190
1191    def rebuildWiki(self, progresshandler, onlyDirty):
1192        """
1193        Rebuild  the wiki
1194
1195        progresshandler -- Object, fulfilling the
1196            PersonalWikiFrame.GuiProgressHandler protocol
1197        """
1198        self.updateExecutor.end(hardEnd=True)
1199        self.getWikiData().refreshDefinedContentNames()
1200
1201        if onlyDirty:
1202#             wikiWords = self.getWikiData().getWikiWordsForMetaDataState(
1203#                     Consts.WIKIWORDMETADATA_STATE_DIRTY) + \
1204#                     self.getWikiData().getWikiWordsForMetaDataState(
1205#                     Consts.WIKIWORDMETADATA_STATE_ATTRSPROCESSED)
1206            wikiWords = self.getWikiData().getWikiWordsForMetaDataState(
1207                    self.getFinalMetaDataState(), ">")
1208        else:
1209            # get all of the wikiWords
1210            wikiWords = self.getWikiData().getAllDefinedWikiPageNames()
1211
1212
1213        if self.isSearchIndexEnabled():
1214            progresshandler.open(len(wikiWords) * 4 + 1)
1215        else:
1216            progresshandler.open(len(wikiWords) * 3 + 1)
1217#         progresshandler.update(0, _(u"Waiting for update thread to end"))
1218
1219
1220        # re-save all of the pages
1221        try:
1222            step = 1
1223
1224            if not onlyDirty:
1225                self.getWikiData().setDbSettingsValue(
1226                        "syncWikiWordMatchtermsUpToDate", "0")
1227                self.getWikiData().clearCacheTables()
1228           
1229            # Step one: update search terms which are generated synchronously.
1230            #   Some of them are essential to find anything or to follow
1231            #   links.
1232            for wikiWord in wikiWords:
1233                progresshandler.update(step, _(u"Update basic link info"))
1234                wikiPage = self._getWikiPageNoErrorNoCache(wikiWord)
1235                if isinstance(wikiPage, AliasWikiPage):
1236                    # This should never be an alias page, so fetch the
1237                    # real underlying page
1238                    # This can only happen if there is a real page with
1239                    # the same name as an alias
1240                    wikiPage = WikiPage(self, wikiWord)
1241
1242                wikiPage.refreshSyncUpdateMatchTerms()
1243               
1244                step += 1
1245
1246            self.getWikiData().setDbSettingsValue(
1247                    "syncWikiWordMatchtermsUpToDate", "1")
1248
1249            # Step two: update attributes. There may be attributes which
1250            #   define how the rest has to be interpreted, therefore they
1251            #   must be processed first.
1252            for wikiWord in wikiWords:
1253                progresshandler.update(step, _(u"Update attributes of %s") % wikiWord)
1254                try:
1255                    wikiPage = self._getWikiPageNoErrorNoCache(wikiWord)
1256                    if isinstance(wikiPage, AliasWikiPage):
1257                        # This should never be an alias page, so fetch the
1258                        # real underlying page
1259                        # This can only happen if there is a real page with
1260                        # the same name as an alias
1261                        wikiPage = WikiPage(self, wikiWord)
1262
1263                    wikiPage.refreshSyncUpdateMatchTerms()
1264                    pageAst = wikiPage.getLivePageAst()
1265
1266                    self.getWikiData().refreshFileSignatureForWord(wikiWord)
1267                    wikiPage.refreshAttributesFromPageAst(pageAst)
1268                except:
1269                    traceback.print_exc()
1270
1271                step += 1
1272
1273            # Step three: update the rest of the syntax (todos, relations)
1274            for wikiWord in wikiWords:
1275                progresshandler.update(step, _(u"Update syntax of %s") % wikiWord)
1276                try:
1277                    wikiPage = self._getWikiPageNoErrorNoCache(wikiWord)
1278                    if isinstance(wikiPage, AliasWikiPage):
1279                        # This should never be an alias page, so fetch the
1280                        # real underlying page
1281                        # This can only happen if there is a real page with
1282                        # the same name as an alias
1283                        wikiPage = WikiPage(self, wikiWord)
1284
1285                    pageAst = wikiPage.getLivePageAst()
1286
1287                    wikiPage.refreshMainDbCacheFromPageAst(pageAst)
1288                except:
1289                    traceback.print_exc()
1290
1291                step += 1
1292           
1293            if self.isSearchIndexEnabled():
1294                # Step four: update index
1295                for wikiWord in wikiWords:
1296                    progresshandler.update(step, _(u"Update index of %s") % wikiWord)
1297                    try:
1298                        wikiPage = self._getWikiPageNoErrorNoCache(wikiWord)
1299                        if isinstance(wikiPage, AliasWikiPage):
1300                            # This should never be an alias page, so fetch the
1301                            # real underlying page
1302                            # This can only happen if there is a real page with
1303                            # the same name as an alias
1304                            wikiPage = WikiPage(self, wikiWord)
1305
1306                        wikiPage.putIntoSearchIndex()
1307
1308#                         writer.add_document(unifName="wikipage/"+wikiWord,
1309#                                 modTimestamp=wikiPage.getTimestamps()[0],
1310#                                 content=content)
1311                    except:
1312                        traceback.print_exc()
1313 
1314                    step += 1
1315
1316            progresshandler.update(step - 1, _(u"Final cleanup"))
1317            # Give possibility to do further reorganisation
1318            # specific to database backend
1319            self.getWikiData().cleanupAfterRebuild(progresshandler)
1320
1321            self.pushDirtyMetaDataUpdate()
1322
1323        finally:
1324            progresshandler.close()
1325            self.updateExecutor.start()
1326
1327
1328
1329    def getWikiWordSubpages(self, wikiWord):
1330        return self.getWikiData().getWikiLinksStartingWith(wikiWord + u"/")
1331
1332
1333    def buildRenameSeqWithSubpages(self, fromWikiWord, toWikiWord):
1334        """
1335        Returns a sequence of tuples (fromWikiWord, toWikiWord).
1336        May return None if one or more toWikiWords already exist and would be
1337        overwritten.
1338       
1339        It is (or will become) important that the renaming is processed
1340        in the order given by the returned sequence.
1341        """
1342        langHelper = GetApp().createWikiLanguageHelper(
1343                self.getWikiDefaultWikiLanguage())
1344
1345        errMsg = langHelper.checkForInvalidWikiWord(toWikiWord, self)
1346
1347        if errMsg:
1348            raise WikiDataException(_(u"'%s' is an invalid wiki word. %s") %
1349                    (toWikiWord, errMsg))
1350
1351        # Build dictionary of renames
1352        renameDict = {}
1353
1354        if self.isDefinedWikiPage(fromWikiWord):
1355            # If fromWikiWord exists (not mandatory) it must be renamed, too
1356            renameDict[fromWikiWord] = toWikiWord
1357
1358        for subPageName in self.getWikiWordSubpages(fromWikiWord):
1359            renameDict[subPageName] = toWikiWord + subPageName[len(fromWikiWord):]
1360
1361        # Check for renames with errors
1362        errorRenames = []
1363
1364        toSet = set()
1365        sameToSet = set()
1366       
1367        for key, value in renameDict.iteritems():
1368            if self.isDefinedWikiPage(value):
1369                errorRenames.append((key, value,
1370                        RenameWikiWordException.PRB_TO_ALREADY_EXISTS))
1371
1372            if value in toSet:
1373                sameToSet.add(value)
1374                continue
1375            toSet.add(value)
1376       
1377        if sameToSet:
1378            # Two or more words should be renamed to same word
1379            # List which ones
1380            errorRenames += [(key, value,
1381                    RenameWikiWordException.PRB_RENAME_TO_SAME)
1382                    for key, value in renameDict.iteritems()
1383                    if value in sameToSet]
1384
1385        if errorRenames:
1386            raise RenameWikiWordException(errorRenames)
1387
1388        return renameDict.items()
1389
1390
1391    def renameWikiWord(self, wikiWord, toWikiWord, modifyText):
1392        """
1393        modifyText -- Should the text of links to the renamed page be
1394                modified? This text replacement works unreliably
1395        """
1396        global _openDocuments
1397       
1398        langHelper = GetApp().createWikiLanguageHelper(
1399                self.getWikiDefaultWikiLanguage())
1400
1401        errMsg = langHelper.checkForInvalidWikiWord(toWikiWord, self)
1402
1403        if errMsg:
1404            raise WikiDataException(_(u"'%s' is an invalid wiki word. %s") %
1405                    (toWikiWord, errMsg))
1406
1407        if self.isDefinedWikiLink(toWikiWord):
1408            raise WikiDataException(
1409                    _(u"Cannot rename '%s' to '%s', '%s' already exists") %
1410                    (wikiWord, toWikiWord, toWikiWord))
1411
1412        try:
1413            oldWikiPage = self.getWikiPage(wikiWord)
1414        except WikiWordNotFoundException:
1415            # So create page first
1416            oldWikiPage = self.createWikiPage(wikiWord)
1417            oldWikiPage.writeToDatabase()
1418
1419        self.getWikiData().renameWord(wikiWord, toWikiWord)
1420       
1421        # TODO: Replace always?
1422       
1423        # Check if replacing previous title of page with new one
1424
1425        # Prefix is normally u"++"
1426        pageTitlePrefix = self.getPageTitlePrefix() + u" "
1427        wikiWordTitle = self.getWikiPageTitle(wikiWord)
1428       
1429        if wikiWordTitle is not None:
1430            prevTitle = pageTitlePrefix + self.getWikiPageTitle(wikiWord) + u"\n"
1431        else:
1432            prevTitle = None
1433
1434        # if the root was renamed we have a little more to do
1435        if wikiWord == self.getWikiName():
1436            wikiConfig = self.getWikiConfig()
1437            wikiConfig.set("main", "wiki_name", toWikiWord)
1438            wikiConfig.set("main", "last_wiki_word", toWikiWord)
1439            wikiConfig.save()
1440
1441            wikiConfigPath = wikiConfig.getConfigPath()
1442            # Unload wiki configuration file
1443            wikiConfig.loadConfig(None)
1444
1445            # Rename config file
1446            renamedConfigPath = os.path.join(
1447                    os.path.dirname(wikiConfigPath),
1448                    u"%s.wiki" % toWikiWord)
1449            os.rename(wikiConfigPath, renamedConfigPath)
1450
1451            # Load it again
1452            wikiConfig.loadConfig(renamedConfigPath)
1453            self.wikiName = toWikiWord
1454           
1455            # Update dict of open documents (= wiki data managers)
1456            del _openDocuments[wikiConfigPath]
1457            _openDocuments[renamedConfigPath] = self
1458
1459        oldWikiPage.renameVersionData(toWikiWord)
1460        oldWikiPage.removeFromSearchIndex()
1461        oldWikiPage.informRenamedWikiPage(toWikiWord)
1462        del self.wikiPageDict[wikiWord]
1463
1464        if modifyText:
1465            # now we have to search the wiki files and replace the old word with the new
1466            sarOp = SearchReplaceOperation()
1467            sarOp.wikiWide = True
1468            sarOp.wildCard = 'regex'
1469            sarOp.caseSensitive = True
1470            sarOp.searchStr = ur"\b" + re.escape(wikiWord) + ur"\b"
1471           
1472            for resultWord in self.searchWiki(sarOp):
1473                wikiPage = self.getWikiPage(resultWord)
1474                text = wikiPage.getLiveTextNoTemplate()
1475                if text is None:
1476                    continue
1477
1478                sarOp.replaceStr = re_sub_escape(toWikiWord)
1479                sarOp.replaceOp = True
1480                sarOp.cycleToStart = False
1481
1482                charStartPos = 0
1483   
1484                while True:
1485                    found = sarOp.searchText(text, charStartPos)
1486                    start, end = found[:2]
1487                   
1488                    if start is None: break
1489                   
1490                    repl = sarOp.replace(text, found)
1491                    text = text[:start] + repl + text[end:]  # TODO Faster?
1492                    charStartPos = start + len(repl)
1493
1494                wikiPage.replaceLiveText(text)
1495#                 wikiPage.update(text)
1496
1497
1498        # Now we modify the page heading if not yet done by text replacing
1499        page = self.getWikiPage(toWikiWord)
1500        # But first update the match terms which need synchronous updating
1501        page.refreshSyncUpdateMatchTerms()
1502
1503        content = page.getLiveText()
1504        if prevTitle is not None and content.startswith(prevTitle):
1505            # Replace previous title with new one
1506            content = pageTitlePrefix + self.getWikiPageTitle(toWikiWord) + \
1507                    u"\n" + content[len(prevTitle):]
1508            page.replaceLiveText(content)
1509#             page.update(content)
1510
1511
1512    # TODO threadstop?
1513    def getAutoLinkRelaxInfo(self):
1514        """
1515        Get regular expressions and words used to operate autoLink function in
1516        "relax" mode
1517        """
1518        if self.autoLinkRelaxInfo is None:
1519            langHelper = GetApp().createWikiLanguageHelper(
1520                    self.getWikiDefaultWikiLanguage())
1521
1522            self.autoLinkRelaxInfo = langHelper.buildAutoLinkRelaxInfo(self)
1523
1524        return self.autoLinkRelaxInfo
1525
1526
1527    def getWikiPageTitle(self, wikiWord):
1528        """
1529        Return a title for a newly created page. It may return None if no title
1530        should be shown.
1531        """
1532        creaMode = self.getWikiConfig().getint("main",
1533                "wikiPageTitle_creationMode", 1)
1534        if creaMode == 0:
1535            # Let wikiword untouched
1536            return wikiWord
1537        elif creaMode == 1:
1538            # Add spaces before uppercase letters,
1539            # e.g. NewWikiWord -> New Wiki Word
1540            title = re.sub(ur'([A-Z\xc0-\xde]+)([A-Z\xc0-\xde][a-z\xdf-\xff])',
1541                    r'\1 \2', wikiWord)
1542            title = re.sub(ur'([a-z\xdf-\xff])([A-Z\xc0-\xde])', r'\1 \2',
1543                    title)
1544            return title
1545        else:  # creaMode == 2: No title at all.
1546            return None
1547
1548
1549    def searchWiki(self, sarOp, applyOrdering=True, threadstop=DUMBTHREADSTOP):
1550        """
1551        Search all wiki pages using the SearchAndReplaceOperation sarOp and
1552        return list of all page names that match the search criteria.
1553        If applyOrdering is True, the ordering of the sarOp is applied before
1554        returning the list.
1555        """
1556        if sarOp.indexSearch == "no":
1557            wikiData = self.getWikiData()
1558            sarOp.beginWikiSearch(self)
1559            try:
1560                threadstop.testRunning()
1561                # First search currently cached pages
1562                exclusionSet = set()
1563                preResultSet = set()
1564               
1565                for k in self.wikiPageDict.keys():
1566                    wikiPage = self.wikiPageDict.get(k)
1567                    if wikiPage is None:
1568                        continue
1569                    if isinstance(wikiPage, AliasWikiPage):
1570                        # Avoid to process same page twice (alias and real) or more often
1571                        continue
1572                       
1573                    text = wikiPage.getLiveTextNoTemplate()
1574                    if text is None:
1575                        continue
1576   
1577                    if sarOp.testWikiPage(k, text) == True:
1578                        preResultSet.add(k)
1579   
1580                    exclusionSet.add(k)
1581   
1582                    threadstop.testRunning()
1583   
1584                # Now search database
1585                resultSet = self.getWikiData().search(sarOp, exclusionSet)
1586                threadstop.testRunning()
1587                resultSet |= preResultSet
1588                if applyOrdering:
1589                    result = sarOp.applyOrdering(resultSet, self.getCollator())
1590                else:
1591                    result = list(resultSet)
1592   
1593            finally:
1594                sarOp.endWikiSearch()
1595   
1596            threadstop.testRunning()
1597            return result
1598        else:
1599            # Processing index search
1600            threadstop.testRunning()
1601            if not self.isSearchIndexEnabled():
1602                return []
1603
1604            q = sarOp.getWhooshIndexQuery(self)
1605            s = self.getSearchIndex().searcher()
1606            threadstop.testRunning()
1607            resultList = s.search(q, limit=None)
1608           
1609#             docnumList = [(rd.docnum, rd["unifName"]) for rd in resultList]
1610#             
1611#             docnumList.sort()
1612#             docpp = "\n".join(["%3i %s" % rd for rd in docnumList])
1613#
1614#             print "--docResults"
1615#             print docpp.encode("mbcs", "replace")
1616
1617            result = [rd["unifName"][9:] for rd in resultList
1618                    if rd["unifName"].startswith(u"wikipage/")]
1619           
1620            threadstop.testRunning()
1621            return result
1622
1623
1624    @staticmethod
1625    def getWhooshIndexContentAnalyzer():
1626        from whoosh.analysis import StandardAnalyzer       
1627        return StandardAnalyzer(stoplist=None)
1628
1629
1630
1631    _REV_SEARCH_INDEX_SCHEMA = None
1632   
1633    @staticmethod
1634    def getWhooshIndexSchema():
1635        if WikiDataManager._REV_SEARCH_INDEX_SCHEMA is None:
1636            from whoosh.fields import Schema, ID, NUMERIC, TEXT
1637           
1638            WikiDataManager._REV_SEARCH_INDEX_SCHEMA = Schema(
1639                    unifName=ID(stored=True, unique=True),
1640                    modTimestamp=NUMERIC(), content=TEXT(
1641                    analyzer=WikiDataManager.getWhooshIndexContentAnalyzer()))
1642
1643        return WikiDataManager._REV_SEARCH_INDEX_SCHEMA
1644   
1645   
1646
1647
1648    def getFinalMetaDataState(self):
1649        if self.isSearchIndexEnabled():
1650            return Consts.WIKIWORDMETADATA_STATE_INDEXED
1651        else:
1652            return Consts.WIKIWORDMETADATA_STATE_SYNTAXPROCESSED
1653
1654    def isSearchIndexEnabled(self):
1655        return self.getWikiConfig().getboolean("main", "indexSearch_enabled",
1656                False)
1657
1658    def isSearchIndexPresent(self):
1659        import whoosh.index
1660
1661        indexPath = os.path.join(self.getWikiPath(), "indexsearch")
1662        return os.path.exists(indexPath) and whoosh.index.exists_in(indexPath)
1663
1664    def removeSearchIndex(self):
1665        if self.isSearchIndexEnabled():
1666            raise InternalError("Calling removeSearchIndex() while index is enabled")
1667       
1668        p = self.updateExecutor.pause(wait=True)
1669        self.updateExecutor.clearDeque(self.UEQUEUE_INDEX)
1670        self.updateExecutor.start()
1671
1672        if self.whooshIndex is not None:
1673            self.whooshIndex.close()
1674            self.whooshIndex = None
1675       
1676
1677        indexPath = os.path.join(self.getWikiPath(), "indexsearch")
1678        if os.path.exists(indexPath):
1679            # Warning!!! rmtree() is very dangerous, don't make a mistake here!
1680            shutil.rmtree(indexPath, ignore_errors=True)
1681
1682
1683    def getSearchIndex(self, clear=False):
1684        """
1685        Opens (or creates if necessary) the whoosh search index and returns it.
1686        It also automatically refreshes the index to the latest version if needed.
1687        """
1688#         print "--getSearchIndex1"
1689        if not self.isSearchIndexEnabled():
1690            return None
1691#         print "--getSearchIndex4"
1692       
1693        import whoosh.index, whoosh.writing
1694
1695        whoosh.writing.DOCLENGTH_TYPE = "l"
1696        whoosh.writing.DOCLENGTH_LIMIT = 2 ** 31 - 1
1697
1698        if self.whooshIndex is None:
1699            indexPath = os.path.join(self.getWikiPath(), "indexsearch")
1700            if not os.path.exists(indexPath):
1701                os.mkdir(indexPath)
1702
1703            if clear or not whoosh.index.exists_in(indexPath):
1704                schema = self.getWhooshIndexSchema()
1705                whoosh.index.create_in(indexPath, schema)
1706
1707            self.whooshIndex = whoosh.index.open_dir(indexPath)
1708
1709        self.whooshIndex = self.whooshIndex.refresh()
1710        return self.whooshIndex
1711
1712
1713#     def rebuildSearchIndex(self, progresshandler, onlyDirty=False):
1714#         """
1715#         progresshandler -- Object, fulfilling the
1716#             PersonalWikiFrame.GuiProgressHandler protocol
1717#         """
1718#         if not self.isSearchIndexEnabled():
1719#             return
1720#
1721#         self.updateExecutor.pause()
1722#         self.getWikiData().refreshDefinedContentNames()
1723#
1724#         # get all of the wikiWords
1725#         wikiWords = self.getWikiData().getAllDefinedWikiPageNames()
1726#
1727#         progresshandler.open(len(wikiWords))
1728#         
1729#         searchIdx = self.getSearchIndex(clear=True)
1730#         writer = searchIdx.writer()
1731#
1732#         try:
1733#             step = 1
1734#             
1735#             for wikiWord in wikiWords:
1736# # Disabled to remove from .pot                progresshandler.update(step, _(u"Update rev. index"))
1737#                 wikiPage = self._getWikiPageNoErrorNoCache(wikiWord)
1738#                 if isinstance(wikiPage, AliasWikiPage):
1739#                     # This should never be an alias page, so fetch the
1740#                     # real underlying page
1741#                     # This can only happen if there is a real page with
1742#                     # the same name as an alias
1743#                     wikiPage = WikiPage(self, wikiWord)
1744#
1745#                 content = wikiPage.getLiveText()
1746#                 
1747#                 writer.add_document(unifName="wikipage/"+wikiWord,
1748#                         modTimestamp=wikiPage.getTimestamps()[0],
1749#                         content=content)
1750#                 
1751#                 step += 1
1752#         finally:
1753#             writer.commit()
1754#             progresshandler.close()
1755#             self.updateExecutor.start()
1756
1757
1758    def removeFromSearchIndex(self, unifName):
1759        if not self.isSearchIndexEnabled():
1760            return
1761        try:
1762            searchIdx = self.getSearchIndex()
1763            writer = searchIdx.writer()
1764           
1765            writer.delete_by_term("unifName", unifName)
1766        except:
1767            writer.cancel()
1768            raise
1769
1770        writer.commit()
1771
1772
1773    def getWikiDefaultWikiPageFormatDetails(self):
1774        """
1775        According to currently stored settings and global attributes, returns a
1776        ParseUtilities.WikiPageFormatDetails object to describe
1777        default formatting details if a concrete wiki page is not available.
1778        """
1779        withCamelCase = strToBool(self.getGlobalAttributeValue(
1780                u"camelCaseWordsEnabled", True))
1781
1782#         footnotesAsWws = self.getWikiConfig().getboolean(
1783#                 "main", "footnotes_as_wikiwords", False)
1784
1785        autoLinkMode = self.getGlobalAttributeValue(
1786                u"auto_link", u"off").lower()
1787
1788        paragraphMode = strToBool(self.getGlobalAttributeValue(
1789                u"paragraph_mode", False))
1790
1791        langHelper = GetApp().createWikiLanguageHelper(
1792                self.getWikiDefaultWikiLanguage())
1793
1794        wikiLanguageDetails = langHelper.createWikiLanguageDetails(
1795                self, None)
1796
1797        return ParseUtilities.WikiPageFormatDetails(
1798                withCamelCase=withCamelCase,
1799                wikiDocument=self,
1800                basePage=None,
1801                autoLinkMode=autoLinkMode,
1802                paragraphMode=paragraphMode,
1803                wikiLanguageDetails=wikiLanguageDetails
1804                )
1805
1806
1807    @staticmethod
1808    def getUserDefaultWikiPageFormatDetails():
1809        """
1810        Return a ParseUtilities.WikiPageFormatDetails object to describe
1811        default formatting details if a concrete wiki document or wiki pages are
1812        not available. This method is static.
1813        """
1814        return ParseUtilities.WikiPageFormatDetails(
1815                withCamelCase=True,
1816#                 footnotesAsWws=False,
1817                wikiDocument=None,
1818                basePage=None,
1819                autoLinkMode=u"off",
1820                paragraphMode=False
1821                )
1822
1823
1824    def getWikiWordsModifiedWithin(self, startTime, endTime):
1825        """
1826        startTime and endTime are floating values as returned by time.time()
1827        startTime is inclusive, endTime is exclusive
1828        """
1829        return self.getWikiData().getWikiWordsModifiedWithin(startTime, endTime)
1830
1831
1832    def getWikiWordsModifiedLastDays(self, days):
1833        """
1834        Return wiki words modified during the last number of days.
1835        """
1836        endTime = time.time()
1837        startTime = float(endTime-(86400*days))
1838       
1839        return self.getWikiData().getWikiWordsModifiedWithin(startTime, endTime)
1840
1841
1842    def getCcWordBlacklist(self):
1843        return self.ccWordBlacklist
1844
1845    def _updateCcWordBlacklist(self):
1846        """
1847        Update the blacklist of camelcase words which should show up as normal
1848        text.
1849        """
1850        pg = self.getFuncPage("global/CCBlacklist")
1851        bls = set(pg.getLiveText().split("\n"))
1852        pg = self.getFuncPage("wiki/CCBlacklist")
1853        bls.update(pg.getLiveText().split("\n"))
1854        self.ccWordBlacklist = bls
1855
1856
1857    def getUnAliasedWikiWord(self, word):
1858        """
1859        Resolve links to wiki words. Returns None if it can't be resolved
1860        """
1861        # TODO: Resolve properly in caseless mode
1862        return self.getWikiData().getUnAliasedWikiWord(word)
1863
1864   
1865    def getUnAliasedWikiWordOrAsIs(self, word):
1866        """
1867        return the real word if word is an alias.
1868        returns word itself if word isn't an alias (may mean it's a real word
1869                or doesn't exist!)
1870        """
1871        result = self.getWikiData().getUnAliasedWikiWord(word)
1872
1873        if result is None:
1874            return word
1875
1876        return result
1877
1878
1879    def getTodos(self):
1880        """
1881        Return all todo entries as list of tuples (wikiword, todoEntry)
1882        """
1883        return self.getWikiData().getTodos()
1884
1885
1886    def getAttributeNamesStartingWith(self, beg, builtins=False):
1887        """
1888        Function must work for read-only wiki.
1889        Returns list or set (whatever is more efficient) of all attribute names
1890        starting with  beg.
1891        """
1892       
1893        if not builtins:
1894            return self.getWikiData().getAttributeNamesStartingWith(beg)
1895       
1896        biKeys = [k for k in AttributeHandling.getBuiltinKeys() if k.startswith(beg)]
1897       
1898        if len(biKeys) == 0:
1899            # Nothing to add
1900            return self.getWikiData().getAttributeNamesStartingWith(beg)
1901       
1902        attrs = set(self.getWikiData().getAttributeNamesStartingWith(beg))
1903        attrs.update(biKeys)
1904       
1905        return attrs
1906
1907
1908    def getDistinctAttributeValuesByKey(self, key, builtins=False):
1909        """
1910        Function must work for read-only wiki.
1911        Return a list of all distinct used attribute values for a given key.
1912        """
1913        if not builtins:
1914            return self.getWikiData().getDistinctAttributeValues(key)
1915       
1916        biVals = AttributeHandling.getBuiltinValuesForKey(key)
1917        if biVals is None or len(biVals) == 0:
1918            # Nothing to add
1919            return self.getWikiData().getDistinctAttributeValues(key)
1920       
1921        vals = set(self.getWikiData().getDistinctAttributeValues(key))
1922        vals.update(biVals)
1923       
1924        return list(vals)
1925       
1926       
1927           
1928       
1929#         s = set(v for w, k, v in
1930#                 self.getWikiData().getAttributeTriples(None, key, None))
1931#         return list(s)
1932
1933    getDistinctPropertyValuesByKey = getDistinctAttributeValuesByKey  # TODO remove "property"-compatibility
1934
1935
1936    def getAttributeTriples(self, word, key, value):
1937        """
1938        Function must work for read-only wiki.
1939        word, key and value can either be unistrings to search for or None as
1940        wildcard.
1941        """
1942        return self.getWikiData().getAttributeTriples(word, key, value)
1943
1944    getPropertyTriples = getAttributeTriples  # TODO remove "property"-compatibility
1945
1946
1947
1948    def getGlobalAttributeValue(self, attribute, default=None):
1949        """
1950        Function must work for read-only wiki.
1951        Finds the wiki-global setting of attribute, if any.
1952        Attribute itself must not contain the "global." prefix
1953        """
1954        return self.getWikiData().getGlobalAttributes().get(
1955                u"global." + attribute, default)
1956       
1957    getGlobalPropertyValue = getGlobalAttributeValue  # TODO remove "property"-compatibility
1958
1959
1960    def reconnect(self):
1961        """
1962        Closes current WikiData instance and opens a new one with the same
1963        settings. This should be called if connection was interrupted by a network
1964        problem or similar issues.
1965        """
1966        try:
1967            if self.wikiData is not None:
1968                self.wikiData.close()
1969        except:
1970            traceback.print_exc()
1971
1972        self.autoReconnectTriedFlag = True
1973           
1974        self.wikiData = None
1975        self.baseWikiData = None
1976        self.autoLinkRelaxInfo = None
1977
1978        wikiDataFactory, createWikiDbFunc = DbBackendUtils.getHandler(self.dbtype)
1979        if wikiDataFactory is None:
1980            raise NoDbHandlerException(
1981                    _(u"Data handler %s not available") % self.dbtype)
1982
1983        self.ensureWikiTempDir()
1984        wikiData = wikiDataFactory(self, self.dataDir, self.getWikiTempDir())
1985
1986        self.baseWikiData = wikiData
1987        self.wikiData = WikiDataSynchronizedProxy(self.baseWikiData)
1988       
1989        self.wikiData.connect()
1990       
1991        # Reset flag so program automatically tries reconnecting on next error
1992        self.autoReconnectTriedFlag = False
1993
1994        attrs = {"reconnected database": True,}
1995        self.fireMiscEventProps(attrs)
1996
1997
1998
1999    def _handleWikiConfigurationChanged(self, miscevt):
2000        if self.getWikiConfig().getboolean("main",
2001                "indexSearch_enabled", False):
2002            self.pushDirtyMetaDataUpdate()
2003        else:
2004            if strToBool(miscevt.get("old config settings")["indexSearch_enabled"]):
2005                # Index search was switched off
2006
2007                # Check for wiki pages with wrong metadata state and adjust
2008                # TODO: Faster?
2009                wikiData = self.getWikiData()
2010
2011                wikiData.commit()
2012                finalState = self.getFinalMetaDataState()
2013               
2014                for wikiWord in wikiData.getWikiWordsForMetaDataState(
2015                        finalState, "<"):
2016                    wikiData.setMetaDataState(wikiWord, finalState)
2017                wikiData.commit()
2018
2019                # Remove index
2020                self.removeSearchIndex()
2021
2022
2023
2024    def miscEventHappened(self, miscevt):
2025        """
2026        Handle misc events from DocPages
2027        """
2028        if miscevt.getSource() is self.wikiConfiguration:
2029            if miscevt.has_key("changed configuration"):
2030                attrs = miscevt.getProps().copy()
2031                attrs["changed wiki configuration"] = True
2032                self._handleWikiConfigurationChanged(miscevt)
2033                self.fireMiscEventProps(attrs)
2034        elif miscevt.getSource() is GetApp():
2035            if miscevt.has_key("reread cc blacklist needed"):
2036                self._updateCcWordBlacklist()
2037            elif miscevt.has_key("pause background threads"):
2038                self.updateExecutor.pause()
2039            elif miscevt.has_key("resume background threads"):
2040                self.updateExecutor.start()
2041        elif isinstance(miscevt.getSource(), DocPage):
2042            # These messages come from (classes derived from) DocPage,
2043            # they are mainly relayed
2044
2045            if miscevt.has_key_in(("deleted wiki page", "renamed wiki page",
2046                    "pseudo-deleted wiki page")):
2047                self.autoLinkRelaxInfo = None
2048                attrs = miscevt.getProps().copy()
2049                attrs["wikiPage"] = miscevt.getSource()
2050                self.fireMiscEventProps(attrs)
2051                miscevt.getSource().removeFromSearchIndex()  # TODO: Check for possible failure!!!
2052                # TODO: Add new on rename
2053            elif miscevt.has_key("updated wiki page"):
2054                self.autoLinkRelaxInfo = None
2055                attrs = miscevt.getProps().copy()
2056                attrs["wikiPage"] = miscevt.getSource()
2057                self.fireMiscEventProps(attrs)
2058                miscevt.getSource().putIntoSearchIndex()
2059            elif miscevt.has_key("saving new wiki page"):           
2060                self.autoLinkRelaxInfo = None
2061                miscevt.getSource().putIntoSearchIndex()
2062            elif miscevt.has_key("reread cc blacklist needed"):
2063                self._updateCcWordBlacklist()
2064
2065                attrs = miscevt.getProps().copy()
2066                attrs["funcPage"] = miscevt.getSource()
2067                self.fireMiscEventProps(attrs)
2068            elif miscevt.has_key("updated func page"):
2069                # This was send from a FuncPage object, send it again
2070                # The event also contains more specific information
2071                # handled by PersonalWikiFrame
2072                attrs = miscevt.getProps().copy()
2073                attrs["funcPage"] = miscevt.getSource()
2074
2075                self.fireMiscEventProps(attrs)
2076#         elif miscevt.getSource() is GetApp().getGlobalConfig():
2077#             if miscevt.has_key("changed configuration"):
2078#                 # TODO: On demand
2079#                 if SpellChecker.isSpellCheckSupported():
2080#                     self.onlineSpellCheckerSession = \
2081#                             SpellChecker.SpellCheckerSession(self)
2082
Note: See TracBrowser for help on using the browser.