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

Revision 247, 76.4 kB (checked in by mbutscher, 2 years ago)

branches/stable-2.0:
2.0final

branches/mbutscher/work:
2.1beta11
* Option to show iframe content from external

sources inside the HTML IE preview

* Remove plus signs in front of headings in page

structure view, use indentation instead

* Translating of menu accelerators enhanced

(scanning for all 16 bit unicode characters)

* Internal, index search: Store a format number for

search index and rebuild index if number doesn't
match the number for current WikidPad version

* Internal: Moved system detection functions from

"Configuration" to new module "SystemInfo?", made
more imports relative

* Experimental, Windows: Option to control if to

scroll control under pointer with mouse wheel on
Windows (instead of focused control)

* Reordered some options (on global "Advanced" page

and below)

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