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

Revision 263, 76.8 kB (checked in by mbutscher, 2 years ago)

branches/stable-2.1:
* Support for URL appendix "prnr" to create a relative link which is

not relocated (modified) when exported as HTML to a different
destination (backported to 2.1 to provide some backward compatibility
to old behavior)

* Bug fixed: Favorite wiki icons may open wrong wiki
* Bug fixed: Misleading error message and bad error handling for

corrupted wiki config file

* Internal: Default maximum length of compatible filename reduced from

250 to 120

branches/mbutscher/work:
* Less jumping around of selection in doc structure window when adding

text (thanks to Christian Ziemski)

* Support for spaces in bracketed URLs
* Option to control type of URL (bracketed or not) on drag&drop
* Support for URL appendix "prnr" to create a relative link which is

not relocated (modified) when exported as HTML to a different
destination

* Option to sort list in "Open Wiki Word" dialog in reverse

alphabetical order

* Shortcuts introduced to move one or more selected logical lines one

line up or down

* Bug fixed: Favorite wiki icons may open wrong wiki
* Bug fixed: Misleading error message and bad error handling for

corrupted wiki config file

* Internal: Deprecated makeRelUrlAbsolute() and makeAbsPathRelUrl() in

PersonalWikiFrame?, call functions in WikiDocument? instead

* Internal: Default maximum length of compatible filename reduced from

250 to 120

* File cleanup now in usable state (orphaned files complete, missing

files need work yet)

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