root/branches/mbutscher/work/lib/pwiki/DocPages.py @ 245

Revision 245, 85.3 kB (checked in by mbutscher, 2 years ago)

* Bug fixed: Autocompletion for anchors treated wiki

links as wiki words (no relative or absolute paths
supported)

* Bug fixed: Indexing of updated pages was called

directly in event handling instead of in the
update thread

Line 
1from __future__ import with_statement
2## import profilehooks
3## profile = profilehooks.profile(filename="profile.prf", immediate=False)
4
5
6import os.path, re, struct, time, traceback, threading, collections
7
8from .rtlibRepl import minidom
9
10import wx
11
12from MiscEvent import MiscEventSourceMixin, KeyFunctionSinkAR
13
14import Consts
15from WikiExceptions import *
16
17from StringOps import strToBool, fileContentToUnicode, lineendToInternal, \
18        loadEntireTxtFile, writeEntireFile
19
20import Utilities
21from Utilities import DUMBTHREADSTOP, FunctionThreadStop, TimeoutRLock, \
22        callInMainThread, callInMainThreadAsync
23
24from WikiPyparsing import buildSyntaxNode
25import ParseUtilities
26
27import Serialization
28
29
30
31# Dummy
32UNDEFINED = object()
33
34
35class DocPage(object, MiscEventSourceMixin):
36    """
37    Abstract common base class for WikiPage and FunctionalPage
38    """
39
40    def __init__(self, wikiDocument):
41        MiscEventSourceMixin.__init__(self)
42       
43        self.wikiDocument = wikiDocument
44        self.txtEditors = []  # List of all editors (views) showing this page
45        self.livePageAst = None   # Cached page AST of live text
46
47        # lock while building live AST
48        self.livePageAstBuildLock = TimeoutRLock(Consts.DEADBLOCKTIMEOUT)
49
50        # lock while setting, getting or saving self.editorText and some other ops
51        self.textOperationLock = TimeoutRLock(Consts.DEADBLOCKTIMEOUT)
52
53        # lock while changing or reading self.txtEditors list
54        self.txtEditorListLock = TimeoutRLock(Consts.DEADBLOCKTIMEOUT)
55        self.editorText = None  # Contains editor text,
56                # if no text editor is registered, this cache is invalid
57#         self.pageState = STATE_TEXTCACHE_MATCHES_EDITOR
58
59
60    def invalidate(self):
61        """
62        Make page invalid to prevent yet running threads from changing
63        database.
64        """
65        with self.textOperationLock:
66            with self.txtEditorListLock:
67                # self.wikiDocument = None
68                self.txtEditors = None
69                self.livePageAst = None
70                self.setEditorText(None)
71
72
73    def isInvalid(self):
74        return self.txtEditors is None
75
76    def getTextOperationLock(self):
77        return self.textOperationLock
78
79    def getWikiDocument(self):
80        return self.wikiDocument
81       
82    def setEditorText(self, text, dirty=True):
83        """
84        Just set editor text. Derived class overwrites this to set flags
85        """
86        with self.textOperationLock:
87            self.editorText = text
88            self.livePageAst = None
89
90
91    def getEditorText(self):
92        return self.editorText
93
94    def addTxtEditor(self, txted):
95        """
96        Add txted to the list of editors (views) showing this page.
97        """
98        # TODO Set text in editor if first editor is created?
99        with self.txtEditorListLock:
100            if not txted in self.txtEditors:
101                if len(self.txtEditors) == 0:
102                    with self.textOperationLock:
103                        # We are assuming that editor content came from
104                        # database
105                        self.setEditorText(txted.GetText(), dirty=False)
106
107                self.txtEditors.append(txted)
108
109
110    def removeTxtEditor(self, txted):
111        """
112        Remove txted from the list of editors (views) showing this page.
113        If the last is removed, text is saved to database.
114        """
115        with self.txtEditorListLock:
116            try:
117                idx = self.txtEditors.index(txted)
118                if len(self.txtEditors) == 1:
119                    self.setEditorText(None)
120
121                del self.txtEditors[idx]
122   
123            except ValueError:
124                # txted not in list
125                pass
126
127    def getTxtEditor(self):
128        """
129        Returns an arbitrary text editor associated with the page
130        or None if no editor is associated.
131        """
132        with self.txtEditorListLock:
133            if len(self.txtEditors) > 0:
134                return self.txtEditors[0]
135            else:
136                return None
137
138
139    def getLiveText(self):
140        """
141        Return current text of page, either from a text editor or
142        from the database
143        """
144        with self.textOperationLock:
145            if self.getEditorText() is not None:
146                return self.getEditorText()
147           
148            return self.getContent()
149
150
151
152    def getLiveTextNoTemplate(self):
153        """
154        Return None if page isn't existing instead of creating an automatic
155        live text (e.g. by template).
156        """
157        raise NotImplementedError   # abstract
158
159
160    def appendLiveText(self, text, fireEvent=True):
161        """
162        Append some text to page which is either loaded in one or more
163        editor(s) or only stored in the database (with automatic update).
164
165        fireEvent -- Send event if database was directly modified
166        """
167        with self.textOperationLock:
168            if self.isReadOnlyEffect():
169                return
170
171            self.setDirty(True)
172            txtEditor = self.getTxtEditor()
173            self.livePageAst = None
174            if txtEditor is not None:
175                # page is in text editor(s), so call AppendText on one of it
176                # TODO Call self.SetReadOnly(False) first?
177                txtEditor.AppendText(text)
178                return
179
180            # Modify database
181            text = self.getContent() + text
182            self.writeToDatabase(text, fireEvent=fireEvent)
183
184
185    def replaceLiveText(self, text, fireEvent=True):
186        with self.textOperationLock:
187            if self.isReadOnlyEffect():
188                return
189
190            self.setDirty(True)
191            txtEditor = self.getTxtEditor()
192            self.livePageAst = None
193            if txtEditor is not None:
194                # page is in text editor(s), so call replace on one of it
195                # TODO Call self.SetReadOnly(False) first?
196                txtEditor.replaceText(text)
197                return
198
199            self.writeToDatabase(text, fireEvent=fireEvent)
200
201
202    def informEditorTextChanged(self, changer):
203        """
204        Called by the txt editor control. Must be called in GUI(=main) thread
205        """
206        with self.textOperationLock:
207            txtEditor = self.getTxtEditor()
208            self.setEditorText(txtEditor.GetText())
209
210        self.fireMiscEventProps({"changed editor text": True,
211                "changed live text": True, "changer": changer})
212
213
214    def informVisited(self):
215        """
216        Called to inform the page that it was visited and should set
217        the "visited" entry in the database to current time.
218        """
219        pass
220
221    def getWikiLanguageName(self):
222        """
223        Returns the internal name of the wiki language of this page.
224        """
225        return self.wikiDocument.getWikiDefaultWikiLanguage()
226
227
228
229    def createWikiLanguageHelper(self):
230        return wx.GetApp().createWikiLanguageHelper(self.getWikiLanguageName())
231
232
233    def getContent(self):
234        """
235        Returns page content. If page doesn't exist already some content
236        is created automatically (may be empty string).
237        """
238        raise NotImplementedError #abstract
239
240    def setDirty(self, dirt):
241        raise NotImplementedError #abstract
242
243    def getDirty(self):
244        raise NotImplementedError #abstract
245
246
247    def getTitle(self):
248        """
249        Return human readable title of the page.
250        """
251        raise NotImplementedError #abstract
252
253
254    def getUnifiedPageName(self):
255        """
256        Return the name of the unified name of the page, which is
257        "wikipage/" + the wiki word for wiki pages or the functional tag
258        for functional pages.
259        """
260        raise NotImplementedError #abstract
261
262
263    def isReadOnlyEffect(self):
264        """
265        Return true if page is effectively read-only, this means
266        "for any reason", regardless if error or intention.
267        """
268        return (self.wikiDocument is None) or self.wikiDocument.isReadOnlyEffect()
269
270
271    def writeToDatabase(self, text=None, fireEvent=True):
272        """
273        Write current text to database and initiate update of meta-data.
274        """
275        with self.textOperationLock:
276            s, u = self.getDirty()
277            if s:
278                if text is None:
279                    text = self.getLiveText()
280                self._save(text, fireEvent=fireEvent)
281                self.initiateUpdate(fireEvent=fireEvent)
282            elif u:
283                self.initiateUpdate(fireEvent=fireEvent)
284            else:
285                if self.getMetaDataState(self.wikiWord) < \
286                        self.getWikiDocument().getFinalMetaDataState():
287                    self.updateDirtySince = time.time()
288                    self.initiateUpdate(fireEvent=fireEvent)
289
290
291    def _save(self, text, fireEvent=True):
292        """
293        Saves the content of current doc page.
294        """
295        raise NotImplementedError #abstract
296
297
298    def initiateUpdate(self, fireEvent=True):
299        """
300        Initiate update of page meta-data. This function may call update
301        directly if can be done fast
302        """
303        raise NotImplementedError #abstract
304
305
306#     def _update(self, fireEvent=True):
307#         """
308#         Update additional cached informations of doc page
309#         """
310#         raise NotImplementedError #abstract
311
312
313
314class AliasWikiPage(DocPage):
315    """
316    Fake page for an alias name of a wiki page. Most functions are delegated
317    to underlying real page
318    Fetched via the (WikiDocument=) WikiDataManager.getWikiPage method.
319    """
320    def __init__(self, wikiDocument, aliasWikiWord, realWikiPage):
321        self.wikiDocument = wikiDocument
322        self.aliasWikiWord = aliasWikiWord
323        self.realWikiPage = realWikiPage
324
325    def getWikiWord(self):
326        return self.aliasWikiWord
327
328    def getTitle(self):
329        """
330        Return human readable title of the page.
331        """
332        return self.aliasWikiWord
333       
334    def getUnifiedPageName(self):
335        """
336        Return the name of the unified name of the page, which is
337        "wikipage/" + the wiki word for wiki pages or the functional tag
338        for functional pages.
339        """
340        return u"wikipage/" + self.aliasWikiWord
341
342    def getNonAliasPage(self):
343        """
344        If this page belongs to an alias of a wiki word, return a page for
345        the real one, otherwise return self
346        """
347        return self.realWikiPage
348#         word = self.wikiDocument.getWikiData().getUnAliasedWikiWord(self.wikiWord)
349#         return self.wikiDocument.getWikiPageNoError(word)
350
351    def getContent(self):
352        """
353        Returns page content. If page doesn't exist already some content
354        is created automatically (may be empty string).
355        """
356        return self.realWikiPage.getContent()
357
358
359    def setDirty(self, dirt):
360        return self.realWikiPage.setDirty(dirt)
361
362    def _save(self, text, fireEvent=True):
363        """
364        Saves the content of current doc page.
365        """
366        return self.realWikiPage._save(text, fireEvent)
367
368    def initiateUpdate(self):
369        return self.realWikiPage.initiateUpdate()
370       
371#     def update(self, fireEvent=True):
372#         return self.realWikiPage.update(fireEvent)
373
374    def getLivePageAst(self, fireEvent=True, dieOnChange=False,
375            threadstop=DUMBTHREADSTOP):
376        return self.realWikiPage.getLivePageAst(fireEvent, dieOnChange,
377                threadstop)
378
379
380    # TODO A bit hackish, maybe remove
381    def __getattr__(self, attr):
382        return getattr(self.realWikiPage, attr)
383
384
385class DataCarryingPage(DocPage):
386    """
387    A page that carries data for itself (mainly everything except an alias page)
388    """
389    def __init__(self, wikiDocument):
390        DocPage.__init__(self, wikiDocument)
391       
392        # does this page need to be saved?
393       
394        # None, if not dirty or timestamp when it became dirty
395        # Inside self.textOperationLock, it is ensured that it is None iff
396        # the editorText is None or is in sync with the database.
397        # This applies not only to editorText, but also to the text returned
398        # by getLiveText().
399
400        self.saveDirtySince = None
401        self.updateDirtySince = None
402
403        # To not store the content of the page here, a placeholder
404        # object is stored instead. Each time, the live text may have changed,
405        # a new object is created. Functions running in a separate thread can
406        # keep a reference to the object at beginning and check for
407        # identity at the end of their work.
408        self.liveTextPlaceHold = object()
409
410
411#         # To not store the full DB content of the page here, a placeholder
412#         # object is stored instead. Each time, text is written to DB,
413#         # a new object is created. Functions running in a separate task can
414#         # keep a reference to the object at beginning and check for
415#         # identity at the end of their work.
416#         self.dbContentPlaceHold = object()
417       
418
419    def setDirty(self, dirt):
420        if self.isReadOnlyEffect():
421            return
422
423        if dirt:
424            if self.saveDirtySince is None:
425                ti = time.time()
426                self.saveDirtySince = ti
427                self.updateDirtySince = ti
428        else:
429            self.saveDirtySince = None
430            self.updateDirtySince = None
431
432    def getDirty(self):
433        return (self.saveDirtySince is not None,
434                self.updateDirtySince is not None)
435
436    def getDirtySince(self):
437        return (self.saveDirtySince, self.updateDirtySince)
438
439
440    def setEditorText(self, text, dirty=True):
441        with self.textOperationLock:
442            super(DataCarryingPage, self).setEditorText(text)
443            if text is None:
444                if self.saveDirtySince is not None:
445                    """
446                    Editor text was removed although it wasn't in sync with
447                    database, so the self.liveTextPlaceHold must be updated,
448                    but self.saveDirtySince is set to None because
449                    self.editorText isn't valid anymore
450                    """
451                    self.saveDirtySince = None
452                    self.liveTextPlaceHold = object()
453            else:
454                if dirty:
455                    self.setDirty(True)
456                    self.liveTextPlaceHold = object()
457
458    def checkFileSignatureAndMarkDirty(self, fireEvent=True):
459        return True
460   
461   
462    def markTextChanged(self):
463        """
464        Mark text as changed and cached pageAst as invalid.
465        Mainly called when an external file change is detected.
466        """
467        self.liveTextPlaceHold = object()
468
469
470class AbstractWikiPage(DataCarryingPage):
471    """
472    Abstract base for WikiPage and Versioning.WikiPageSnapshot
473    """
474
475    def __init__(self, wikiDocument, wikiWord):
476        DataCarryingPage.__init__(self, wikiDocument)
477
478        self.livePageBasePlaceHold = None   # liveTextPlaceHold object on which
479                # the livePageAst is based.
480                # This is needed to check for changes when saving
481        self.livePageBaseFormatDetails = None   # Cached format details on which the
482                # page-ast bases
483
484        # List of words unknown to spellchecker
485        self.liveSpellCheckerUnknownWords = None
486
487        # liveTextPlaceHold object on which the liveSpellCheckerUnknownWords is based.
488        self.liveSpellCheckerUnknownWordsBasePlaceHold = None
489
490        self.__sinkWikiDocumentSpellSession = KeyFunctionSinkAR((
491                ("modified spell checker session", self.onModifiedSpellCheckerSession),
492        ))
493
494#         self.metaDataProcessLock = threading.RLock()  # lock while processing
495#                 # meta-data
496
497        self.wikiWord = wikiWord
498        self.childRelations = None
499        self.childRelationSet = set()
500        self.todos = None
501        self.attrs = None
502        self.modified, self.created, self.visited = None, None, None
503        self.suggNewPageTitle = None  # Title to use for page if it is
504                # newly created
505
506#         if self.getWikiData().getMetaDataState(self.wikiWord) != 1:
507#             self.updateDirtySince = time.time()
508
509    def invalidate(self):
510        super(AbstractWikiPage, self).invalidate()
511        self.__sinkWikiDocumentSpellSession.setEventSource(None)
512
513    def getWikiWord(self):
514        return self.wikiWord
515
516    def getTitle(self):
517        """
518        Return human readable title of the page.
519        """
520        return self.getWikiWord()
521
522
523    def getUnifiedPageName(self):
524        """
525        Return the name of the unified name of the page, which is
526        "wikipage/" + the wiki word for wiki pages or the functional tag
527        for functional pages.
528        """
529        return u"wikipage/" + self.wikiWord
530
531    def getWikiDocument(self):
532        return self.wikiDocument
533
534    def getWikiData(self):
535        return self.wikiDocument.getWikiData()
536       
537    def getMetaDataState(self):
538        return self.getWikiData().getMetaDataState(self.wikiWord)
539
540    def addTxtEditor(self, txted):
541        """
542        Add txted to the list of editors (views) showing this page.
543        """
544        with self.txtEditorListLock:
545            if len(self.txtEditors) == 0:
546                with self.textOperationLock:
547                    if not self.checkFileSignatureAndMarkDirty():
548                        self.initiateUpdate()
549
550            super(AbstractWikiPage, self).addTxtEditor(txted)
551
552
553        # TODO Set text in editor here if first editor is created?
554
555#         with self.txtEditorListLock:
556            if not txted in self.txtEditors:
557                if len(self.txtEditors) == 0:
558                    with self.textOperationLock:
559                        # We are assuming that editor content came from
560                        # database
561                        self.setEditorText(txted.GetText(), dirty=False)
562
563                self.txtEditors.append(txted)
564
565
566    def getTimestamps(self):
567        """
568        Return tuple (<last mod. time>, <creation time>, <last visit time>)
569        of this page.
570        """
571        if self.modified is None:
572            self.modified, self.created, self.visited = \
573                    self.getWikiData().getTimestamps(self.wikiWord)
574                   
575        if self.modified is None:
576            ti = time.time()
577            self.modified, self.created, self.visited = ti, ti, ti
578       
579        return self.modified, self.created, self.visited
580
581    def setTimestamps(self, timestamps):
582        if self.isReadOnlyEffect():
583            return
584
585        timestamps = timestamps[:3]
586        self.modified, self.created, self.visited = timestamps
587       
588        self.getWikiData().setTimestamps(self.wikiWord, timestamps)
589
590
591    def getSuggNewPageTitle(self):
592        return self.suggNewPageTitle
593       
594    def setSuggNewPageTitle(self, suggNewPageTitle):
595        self.suggNewPageTitle = suggNewPageTitle
596
597    def getParentRelationships(self):
598        return self.getWikiData().getParentRelationships(self.wikiWord)
599
600
601    def getChildRelationships(self, existingonly=False, selfreference=True,
602            withFields=(), excludeSet=frozenset(),
603            includeSet=frozenset()):
604        """
605        get the child relations of this word
606        existingonly -- List only existing wiki words
607        selfreference -- List also wikiWord if it references itself
608        withFields -- Seq. of names of fields which should be included in
609            the output. If this is not empty, tuples are returned
610            (relation, ...) with ... as further fields in the order mentioned
611            in withfields.
612
613            Possible field names:
614                "firstcharpos": position of link in page (may be -1 to represent
615                    unknown)
616                "modified": Modification date
617        excludeSet -- set of words which should be excluded from the list
618        includeSet -- wikiWords to include in the result
619
620        Does not support caching
621        """
622        with self.textOperationLock:
623            wikiData = self.getWikiData()
624            wikiDocument = self.getWikiDocument()
625           
626            if withFields is None:
627                withFields = ()
628   
629            relations = wikiData.getChildRelationships(self.wikiWord,
630                    existingonly, selfreference, withFields=withFields)
631   
632            if len(excludeSet) > 0:
633                # Filter out members of excludeSet
634                if len(withFields) > 0:
635                    relations = [r for r in relations if not r[0] in excludeSet]
636                else:
637                    relations = [r for r in relations if not r in excludeSet]
638   
639            if len(includeSet) > 0:
640                # First unalias wiki pages and remove non-existing ones
641                clearedIncSet = set()
642                for w in includeSet:
643                    w = wikiDocument.getUnAliasedWikiWord(w)
644                    if w is None:
645                        continue
646
647#                     if not wikiDocument.isDefinedWikiLink(w):
648#                         continue
649   
650                    clearedIncSet.add(w)
651
652                # Then remove items already present in relations
653                if len(clearedIncSet) > 0:
654                    if len(withFields) > 0:
655                        for r in relations:
656                            clearedIncSet.discard(r[0])
657                    else:
658                        for r in relations:
659                            clearedIncSet.discard(r)
660   
661                # Now collect info
662                if len(clearedIncSet) > 0:
663                    relations += [wikiData.getExistingWikiWordInfo(r,
664                            withFields=withFields) for r in clearedIncSet]
665   
666            return relations
667
668
669    def getAttributes(self):
670        with self.textOperationLock:
671            if self.attrs is not None:
672                return self.attrs
673           
674            data = self.getWikiData().getAttributesForWord(self.wikiWord)
675
676#         with self.textOperationLock:
677#             if self.attrs is not None:
678#                 return self.attrs
679
680            self.attrs = {}
681            for (key, val) in data:
682                self._addAttribute(key, val)
683           
684            return self.attrs
685
686    getProperties = getAttributes  # TODO remove "property"-compatibility
687
688
689    def getAttribute(self, attrkey, default=None):
690        with self.textOperationLock:
691            attrs = self.getAttributes()
692            if attrs.has_key(attrkey):
693                return attrs[attrkey][-1]
694            else:
695                return default
696
697    def getAttributeOrGlobal(self, attrkey, default=None):
698        """
699        Tries to find an attribute on this page and returns the first value.
700        If it can't be found for page, it is searched for a global
701        attribute with this name. If this also can't be found,
702        default (normally None) is returned.
703        """
704        with self.textOperationLock:
705            attrs = self.getAttributes()
706            if attrs.has_key(attrkey):
707                return attrs[attrkey][-1]
708            else:
709                globalAttrs = self.getWikiData().getGlobalAttributes()     
710                return globalAttrs.get(u"global."+attrkey, default)
711
712    getPropertyOrGlobal = getAttributeOrGlobal # TODO remove "property"-compatibility
713   
714   
715    def _addAttribute(self, key, val):
716        values = self.attrs.get(key)
717        if not values:
718            values = []
719            self.attrs[key] = values
720        values.append(val)
721
722
723    def getTodos(self):
724        with self.textOperationLock:
725            if self.todos is None:
726                self.todos = self.getWikiData().getTodosForWord(self.wikiWord)
727                       
728            return self.todos
729
730
731    def getAnchors(self):
732        """
733        Return sequence of anchors in page
734        """
735        pageAst = self.getLivePageAst()
736        return [node.anchorLink
737                for node in pageAst.iterDeepByName("anchorDef")]
738
739
740    def getLiveTextNoTemplate(self):
741        """
742        Return None if page isn't existing instead of creating an automatic
743        live text (e.g. by template).
744        """
745        with self.textOperationLock:
746            if self.getTxtEditor() is not None:
747                return self.getLiveText()
748            else:
749                if self.isDefined():
750                    return self.getContent()
751                else:
752                    return None
753
754
755    def getFormatDetails(self):
756        """
757        According to currently stored settings, return a
758        ParseUtilities.WikiPageFormatDetails object to describe
759        formatting
760        """
761        with self.textOperationLock:
762            withCamelCase = strToBool(self.getAttributeOrGlobal(
763                    u"camelCaseWordsEnabled"), True)
764   
765#             footnotesAsWws = self.wikiDocument.getWikiConfig().getboolean(
766#                     "main", "footnotes_as_wikiwords", False)
767   
768            autoLinkMode = self.getAttributeOrGlobal(u"auto_link", u"off").lower()
769
770            paragraphMode = strToBool(self.getAttributeOrGlobal(
771                    u"paragraph_mode"), False)
772                   
773            langHelper = wx.GetApp().createWikiLanguageHelper(
774                    self.wikiDocument.getWikiDefaultWikiLanguage())
775
776            wikiLanguageDetails = langHelper.createWikiLanguageDetails(
777                    self.wikiDocument, self)
778
779            return ParseUtilities.WikiPageFormatDetails(
780                    withCamelCase=withCamelCase,
781                    wikiDocument=self.wikiDocument,
782                    basePage=self,
783                    autoLinkMode=autoLinkMode,
784                    paragraphMode=paragraphMode,
785                    wikiLanguageDetails=wikiLanguageDetails)
786
787
788    def isDefined(self):
789        return self.getWikiDocument().isDefinedWikiPage(self.getWikiWord())
790
791
792    @staticmethod
793    def extractAttributeNodesFromPageAst(pageAst):
794        """
795        Return an iterator of attribute nodes in pageAst. This does not return
796        attributes inside of todo entries.
797        """
798        # Complicated version for compatibility with old language plugins
799        # TODO remove "property"-compatibility
800        return Utilities.iterMergesort((
801                pageAst.iterUnselectedDeepByName("attribute",
802                frozenset(("todoEntry",))),
803                pageAst.iterUnselectedDeepByName("property",
804                frozenset(("todoEntry",))) ),
805                key=lambda n: n.pos)
806       
807        # Simple one for later
808#         return pageAst.iterUnselectedDeepByName("attribute",
809#                 frozenset(("todoEntry",)))
810
811
812    @staticmethod
813    def extractTodoNodesFromPageAst(pageAst):
814        """
815        Return an iterator of todo nodes in pageAst.
816        """
817        return pageAst.iterDeepByName("todoEntry")
818
819
820    def _save(self, text, fireEvent=True):
821        """
822        Saves the content of current doc page.
823        """
824        pass
825
826
827    def setPresentation(self, data, startPos):
828        """
829        Set (a part of) the presentation tuple. This is silently ignored
830        if the "write access failed" or "read access failed" flags are
831        set in the wiki document.
832        data -- tuple with new presentation data
833        startPos -- start position in the presentation tuple which should be
834                overwritten with data.
835        """
836        raise NotImplementedError   # abstract
837
838
839    def initiateUpdate(self, fireEvent=True):
840        """
841        Initiate update of page meta-data. This function may call update
842        directly if can be done fast
843        """
844        pass
845
846
847    def getLivePageAstIfAvailable(self):
848        """
849        Return the current, up-to-data page AST if available, None otherwise
850        """
851        with self.textOperationLock:
852            # Current state
853            text = self.getLiveText()
854            formatDetails = self.getFormatDetails()
855
856            # AST state
857            pageAst = self.livePageAst
858            baseFormatDetails = self.livePageBaseFormatDetails
859
860            if pageAst is not None and \
861                    baseFormatDetails is not None and \
862                    formatDetails.isEquivTo(baseFormatDetails) and \
863                    self.liveTextPlaceHold is self.livePageBasePlaceHold:
864                return pageAst
865
866            return None
867
868
869
870    def getLivePageAst(self, fireEvent=True, dieOnChange=False,
871            threadstop=DUMBTHREADSTOP, allowMetaDataUpdate=False):
872        """
873        Return PageAst of live text. In rare cases the live text may have
874        changed while method is running and the result is inaccurate.
875        """
876#         if self.livePageAstBuildLock.acquire(False):
877#             self.livePageAstBuildLock.release()
878#         else:
879#             if wx.Thread_IsMain(): traceback.print_stack()
880
881        with self.livePageAstBuildLock:   # TODO: Timeout?
882            threadstop.testRunning()
883
884            with self.textOperationLock:
885                text = self.getLiveText()
886                liveTextPlaceHold = self.liveTextPlaceHold
887                formatDetails = self.getFormatDetails()
888
889                pageAst = self.getLivePageAstIfAvailable()
890
891            if pageAst is not None:
892                return pageAst
893
894            if dieOnChange:
895                if threadstop is DUMBTHREADSTOP:
896                    threadstop = FunctionThreadStop(
897                            lambda: liveTextPlaceHold is self.liveTextPlaceHold)
898                else:
899                    origThreadstop = threadstop
900                    threadstop = FunctionThreadStop(
901                            lambda: origThreadstop.isRunning() and 
902                            liveTextPlaceHold is self.liveTextPlaceHold)
903
904            if len(text) == 0:
905                pageAst = buildSyntaxNode([], 0)
906            else:
907                pageAst = self.parseTextInContext(text, formatDetails=formatDetails,
908                        threadstop=threadstop)
909
910            with self.textOperationLock:
911                threadstop.testRunning()
912
913                self.livePageAst = pageAst
914                self.livePageBasePlaceHold = liveTextPlaceHold
915                self.livePageBaseFormatDetails = formatDetails
916
917
918        if self.isReadOnlyEffect():
919            threadstop.testRunning()
920            return pageAst
921
922#         if False and allowMetaDataUpdate:   # TODO: Option
923#             self._refreshMetaData(pageAst, formatDetails, fireEvent=fireEvent,
924#                     threadstop=threadstop)
925
926        with self.textOperationLock:
927            threadstop.testRunning()
928            return pageAst
929
930
931    def onModifiedSpellCheckerSession(self, miscevt):
932        """
933        Invalidate spell checker data when e.g. new words are added to
934        dictionary
935        """
936        with self.textOperationLock:
937            self.__sinkWikiDocumentSpellSession.setEventSource(None)
938
939            self.liveSpellCheckerUnknownWords = None
940            self.liveSpellCheckerUnknownWordsBasePlaceHold = None
941
942        self.fireMiscEventKeys(("modified spell checker session",))
943
944
945    def getSpellCheckerUnknownWordsIfAvailable(self):
946        """
947        Return the current, up-to-data page AST if available, None otherwise
948        """
949        with self.textOperationLock:
950            # unknown words
951            unknownWords = self.liveSpellCheckerUnknownWords
952
953            if unknownWords is not None and \
954                    self.liveTextPlaceHold is \
955                    self.liveSpellCheckerUnknownWordsBasePlaceHold:
956                return unknownWords
957
958            return None
959
960
961
962    def getSpellCheckerUnknownWords(self, dieOnChange=False,
963            threadstop=DUMBTHREADSTOP):
964        """
965        Return list of unknown words as list of WikiPyparsing.TerminalNode of live text.
966        In rare cases the live text may have changed while method is running and
967        the result is inaccurate.
968        """
969#         with self.livePageAstBuildLock:   # TODO: Timeout?
970        threadstop.testRunning()
971       
972        spellSession = self.getWikiDocument().createOnlineSpellCheckerSessionClone()
973        if spellSession is None:
974            return
975           
976        spellSession.setCurrentDocPage(self)
977
978        with self.textOperationLock:
979            text = self.getLiveText()
980            liveTextPlaceHold = self.liveTextPlaceHold
981
982            unknownWords = self.getSpellCheckerUnknownWordsIfAvailable()
983
984        if unknownWords is not None:
985            return unknownWords
986
987        if dieOnChange:
988            if threadstop is DUMBTHREADSTOP:
989                threadstop = FunctionThreadStop(
990                        lambda: liveTextPlaceHold is self.liveTextPlaceHold)
991            else:
992                origThreadstop = threadstop
993                threadstop = FunctionThreadStop(
994                        lambda: origThreadstop.isRunning() and 
995                        liveTextPlaceHold is self.liveTextPlaceHold)
996
997        if len(text) == 0:
998            unknownWords = []
999        else:
1000            unknownWords = spellSession.buildUnknownWordList(text,
1001                    threadstop=threadstop)
1002
1003        with self.textOperationLock:
1004            threadstop.testRunning()
1005
1006            self.liveSpellCheckerUnknownWords = unknownWords
1007            self.liveSpellCheckerUnknownWordsBasePlaceHold = liveTextPlaceHold
1008           
1009            self.__sinkWikiDocumentSpellSession.setEventSource(
1010                    self.getWikiDocument().getOnlineSpellCheckerSession())
1011
1012
1013        if self.isReadOnlyEffect():
1014            threadstop.testRunning()
1015            return unknownWords
1016
1017#         if False and allowMetaDataUpdate:   # TODO: Option
1018#             self._refreshMetaData(pageAst, formatDetails, fireEvent=fireEvent,
1019#                     threadstop=threadstop)
1020
1021        with self.textOperationLock:
1022            threadstop.testRunning()
1023            return unknownWords
1024
1025
1026##     @profile
1027    def parseTextInContext(self, text, formatDetails=None,
1028            threadstop=DUMBTHREADSTOP):
1029        """
1030        Return PageAst of text in the context of this page (wiki language and
1031        format details).
1032
1033        text: unistring with text
1034        """
1035        parser = wx.GetApp().createWikiParser(self.getWikiLanguageName()) # TODO debug mode  , True
1036
1037        if formatDetails is None:
1038            formatDetails = self.getFormatDetails()
1039
1040        try:
1041            pageAst = parser.parse(self.getWikiLanguageName(), text,
1042                    formatDetails, threadstop=threadstop)
1043        finally:
1044            wx.GetApp().freeWikiParser(parser)
1045
1046        threadstop.testRunning()
1047
1048        return pageAst
1049
1050
1051    _DEFAULT_PRESENTATION = (0, 0, 0, 0, 0, None)
1052
1053    def getPresentation(self):
1054        """
1055        Get the presentation tuple (<cursor pos>, <editor scroll pos x>,
1056            <e.s.p. y>, <preview s.p. x>, <p.s.p. y>, <folding list>)
1057        The folding list may be None or a list of UInt32 numbers
1058        containing fold level, header flag and expand flag for each line
1059        in editor.
1060        """
1061        wikiData = self.wikiDocument.getWikiData()
1062
1063        if wikiData is None:
1064            return AbstractWikiPage._DEFAULT_PRESENTATION
1065
1066        datablock = wikiData.getPresentationBlock(
1067                self.getWikiWord())
1068
1069        if datablock is None or datablock == "":
1070            return AbstractWikiPage._DEFAULT_PRESENTATION
1071
1072        try:
1073            # TODO: On next file format change: Change '=' to '>'
1074            if len(datablock) == struct.calcsize("=iiiii"):
1075                # Version 0
1076                return struct.unpack("=iiiii", datablock) + (None,)
1077            else:
1078                ss = Serialization.SerializeStream(stringBuf=datablock)
1079                rcVer = ss.serUint8(1)
1080                if rcVer == 1:
1081                    # Compatible to version 1               
1082                    ver = ss.serUint8(1)
1083                    pt = [ss.serInt32(0), ss.serInt32(0), ss.serInt32(0),
1084                            ss.serInt32(0), ss.serInt32(0), None]
1085   
1086                    # Fold list
1087                    fl = ss.serArrUint32([])
1088                    if len(fl) == 0:
1089                        fl = None
1090   
1091                    pt[5] = fl
1092   
1093                    return tuple(pt)
1094                else:
1095                    return AbstractWikiPage._DEFAULT_PRESENTATION
1096        except struct.error:
1097            return AbstractWikiPage._DEFAULT_PRESENTATION
1098
1099
1100
1101
1102class WikiPage(AbstractWikiPage):
1103    """
1104    holds the data for a real wikipage (no alias).
1105
1106    Fetched via the WikiDataManager.getWikiPage method.
1107    """
1108    def __init__(self, wikiDocument, wikiWord):
1109        AbstractWikiPage.__init__(self, wikiDocument, wikiWord)
1110
1111        self.versionOverview = UNDEFINED
1112
1113
1114    def getVersionOverview(self):
1115        """
1116        Return Versioning.VersionOverview object. If necessary create one.
1117        """
1118        with self.textOperationLock:
1119            if self.versionOverview is UNDEFINED or self.versionOverview is None:
1120                from .timeView.Versioning import VersionOverview
1121               
1122                versionOverview = VersionOverview(self.getWikiDocument(),
1123                        self)
1124                versionOverview.readOverview()
1125                self.versionOverview = versionOverview
1126   
1127            return self.versionOverview
1128
1129
1130    def getExistingVersionOverview(self):
1131        """
1132        Return Versioning.VersionOverview object.
1133        If not existing already return None.
1134        """
1135        with self.textOperationLock:
1136            if self.versionOverview is UNDEFINED:
1137                from .timeView.Versioning import VersionOverview
1138
1139                versionOverview = VersionOverview(self.getWikiDocument(),
1140                        self)
1141
1142                if versionOverview.isNotInDatabase():
1143                    self.versionOverview = None
1144                else:
1145                    versionOverview.readOverview()
1146                    self.versionOverview = versionOverview
1147
1148            return self.versionOverview
1149
1150    def releaseVersionOverview(self):
1151        """
1152        Should only be called by VersionOverview.invalidate()
1153        """
1154        self.versionOverview = UNDEFINED
1155
1156
1157    def getNonAliasPage(self):
1158        """
1159        If this page belongs to an alias of a wiki word, return a page for
1160        the real one, otherwise return self.
1161        This class always returns self
1162        """
1163        return self
1164
1165
1166    def setPresentation(self, data, startPos):
1167        """
1168        Set (a part of) the presentation tuple. This is silently ignored
1169        if the "write access failed" or "read access failed" flags are
1170        set in the wiki document.
1171        data -- tuple with new presentation data
1172        startPos -- start position in the presentation tuple which should be
1173                overwritten with data.
1174        """
1175        if self.isReadOnlyEffect():
1176            return
1177
1178        if self.wikiDocument.getReadAccessFailed() or \
1179                self.wikiDocument.getWriteAccessFailed():
1180            return
1181
1182        try:
1183            pt = self.getPresentation()
1184            pt = pt[:startPos] + data + pt[startPos+len(data):]
1185   
1186            wikiData = self.wikiDocument.getWikiData()
1187            if wikiData is None:
1188                return
1189               
1190            if pt[5] is None:
1191                # Write it in old version 0
1192                # TODO: On next file format change: Change '=' to '>'
1193                wikiData.setPresentationBlock(self.getWikiWord(),
1194                        struct.pack("=iiiii", *pt[:5]))
1195            else:
1196                # Write it in new version 1
1197                ss = Serialization.SerializeStream(stringBuf=True, readMode=False)
1198                ss.serUint8(1)  # Read compatibility version
1199                ss.serUint8(1)  # Real version
1200                # First five numbers
1201                for n in pt[:5]:
1202                    ss.serInt32(n)
1203                # Folding tuple
1204                ft = pt[5]
1205                if ft is None:
1206                    ft = ()
1207                ss.serArrUint32(ft)
1208
1209                wikiData.setPresentationBlock(self.getWikiWord(),
1210                        ss.getBytes())
1211
1212        except AttributeError:
1213            traceback.print_exc()
1214
1215
1216    def informVisited(self):
1217        """
1218        Called to inform the page that it was visited and should set
1219        the "visited" entry in the database to current time.
1220        """
1221        with self.textOperationLock:
1222            if self.isReadOnlyEffect():
1223                return
1224   
1225            if not self.isDefined():
1226                return
1227
1228            wikiData = self.wikiDocument.getWikiData()
1229            word = self.getWikiWord()
1230            ts = wikiData.getTimestamps(word)
1231            ts = ts[:2] + (time.time(),) + ts[3:]
1232            wikiData.setTimestamps(word, ts)
1233
1234
1235    def _changeHeadingForTemplate(self, content):
1236        """
1237        Modify the heading of a template page's content to match the page
1238        created from the template.
1239        """
1240        # Prefix is normally u"++"
1241        pageTitlePrefix = \
1242                self.getWikiDocument().getPageTitlePrefix() + u" "
1243               
1244        if self.suggNewPageTitle is None:
1245            wikiWordHead = self.getWikiDocument().getWikiPageTitle(
1246                    self.getWikiWord())
1247        else:
1248            wikiWordHead = self.suggNewPageTitle
1249
1250        if wikiWordHead is None:
1251            return content
1252
1253        wikiWordHead = pageTitlePrefix + wikiWordHead + u"\n"
1254
1255        # Remove current heading, if present. removeFirst holds number of
1256        # characters to remove at beginning when prepending new title
1257
1258        removeFirst = 0
1259        if content.startswith(pageTitlePrefix):
1260            try:
1261                removeFirst = content.index(u"\n", len(pageTitlePrefix)) + 1
1262            except ValueError:
1263                pass
1264
1265        return wikiWordHead + content[removeFirst:]
1266
1267
1268    def getContentOfTemplate(self, templatePage, parentPage):
1269        # getLiveText() would be more logical, but this may
1270        # mean that content is up to date, while attributes
1271        # are not updated.
1272        content = templatePage.getContent()
1273       
1274        # Check if template title should be changed
1275        tplHeading = parentPage.getAttributeOrGlobal(
1276                u"template_head", u"auto")
1277        if tplHeading in (u"auto", u"automatic"):
1278            content = self._changeHeadingForTemplate(content)
1279
1280        return content
1281
1282
1283    def setMetaDataFromTemplate(self, templatePage):
1284        # Load attributes from template page
1285        self.attrs = templatePage._cloneDeepAttributes()
1286       
1287
1288    def getContent(self):
1289        """
1290        Returns page content. If page doesn't exist already the template
1291        creation is done here. After calling this function, attributes
1292        are also accessible for a non-existing page
1293        """
1294        content = None
1295        try:
1296            content = self.getWikiData().getContent(self.wikiWord)
1297        except WikiFileNotFoundException, e:
1298            # Create initial content of new page
1299
1300            # Check for "template" attribute
1301            parents = self.getParentRelationships()
1302            if len(parents) > 0:
1303                langHelper = wx.GetApp().createWikiLanguageHelper(
1304                        self.getWikiLanguageName())
1305
1306                templateSource = None
1307                templateParent = None
1308                conflict = False
1309                for parent in parents:
1310                    try:
1311                        parentPage = self.wikiDocument.getWikiPage(parent)
1312                        templateWord = langHelper.resolveWikiWordLink(
1313                                parentPage.getAttribute("template"),
1314                                parentPage)
1315                        if templateWord is not None and \
1316                                self.wikiDocument.isDefinedWikiLink(templateWord):
1317                            if templateSource is None:
1318                                templateSource = templateWord
1319                                templateParentPage = parentPage
1320                            elif templateSource != templateWord:
1321                                # More than one possible template source
1322                                # -> no template, stop here
1323                                templateSource = None
1324                                templateParentPage = None
1325                                conflict = True
1326                                break
1327                    except (WikiWordNotFoundException, WikiFileNotFoundException):
1328                        continue
1329
1330                if templateSource is None and not conflict:
1331                    # No individual template attributes, try to find global one
1332                    globalAttrs = self.getWikiData().getGlobalAttributes()     
1333                   
1334                    templateWord = globalAttrs.get(u"global.template")
1335                    if templateWord is not None and \
1336                            self.wikiDocument.isDefinedWikiLink(templateWord):
1337                        templateSource = templateWord
1338                        templateParentPage = self.wikiDocument.getWikiPage(
1339                                parents[0])
1340
1341               
1342                if templateSource is not None:
1343                    templatePage = self.wikiDocument.getWikiPage(templateSource)
1344
1345                    content = self.getContentOfTemplate(templatePage,
1346                            templateParentPage)
1347                    self.setMetaDataFromTemplate(templatePage)
1348
1349
1350#             if len(parents) == 1:
1351#                 # Check if there is a template page
1352#                 try:
1353#                     parentPage = self.wikiDocument.getWikiPage(parents[0])
1354#
1355#
1356#                     # TODO Error checking
1357# #                     templateWord = parentPage.getAttributeOrGlobal("template")
1358#                     templateWord = langHelper.resolveWikiWordLink(
1359#                             parentPage.getAttributeOrGlobal("template"),
1360#                             parentPage)
1361#
1362#                     templatePage = self.wikiDocument.getWikiPage(templateWord)
1363#                     
1364#                     content = self.getContentOfTemplate(templatePage, parentPage)
1365#                     self.setMetaDataFromTemplate(templatePage)
1366#
1367#                 except (WikiWordNotFoundException, WikiFileNotFoundException):
1368#                     pass
1369
1370            if content is None:
1371                if self.suggNewPageTitle is None:
1372                    title = self.getWikiDocument().getWikiPageTitle(
1373                            self.getWikiWord())
1374                else:
1375                    title = self.suggNewPageTitle
1376
1377                if title is not None:
1378                    content = u"%s %s\n\n" % \
1379                            (self.wikiDocument.getPageTitlePrefix(),
1380                            title)
1381                else:
1382                    content = u""
1383
1384        return content
1385
1386
1387#     def isDefined(self):
1388#         return self.getWikiDocument().isDefinedWikiPage(self.getWikiWord())
1389
1390
1391    def pseudoDeletePage(self):
1392        """
1393        Delete a page which doesn't really exist.
1394        Just sends an appropriate event.
1395        """
1396        wx.CallAfter(self.fireMiscEventKeys,
1397                ("pseudo-deleted page", "pseudo-deleted wiki page"))
1398
1399
1400    def deletePage(self):
1401        """
1402        Deletes the page from database
1403        """
1404        with self.textOperationLock:
1405            if self.isReadOnlyEffect():
1406                return
1407   
1408            if self.isDefined():
1409                self.getWikiData().deleteWord(self.getWikiWord())
1410
1411            vo = self.getExistingVersionOverview()
1412            if vo is not None:
1413                vo.delete()
1414                self.versionOverview = UNDEFINED
1415       
1416            self.removeFromSearchIndex()   # TODO: Check for (dead-)locks
1417
1418            wx.CallAfter(self.fireMiscEventKeys,
1419                    ("deleted page", "deleted wiki page"))
1420
1421
1422    def renameVersionData(self, newWord):
1423        """
1424        This is called by WikiDocument(=WikiDataManager) during
1425        WikiDocument.renameWikiWord() and shouldn't be called elsewhere.
1426        """
1427        with self.textOperationLock:
1428            vo = self.getExistingVersionOverview()
1429            if vo is None:
1430                return
1431           
1432            vo.renameTo(u"wikipage/" + newWord)
1433            self.versionOverview = UNDEFINED
1434
1435
1436    def informRenamedWikiPage(self, newWord):
1437        """
1438        Informs object that the page was renamed to newWord.
1439        This page object itself does not change its name but becomes invalid!
1440
1441        This function should be called by WikiDocument(=WikiDataManager) only,
1442        use WikiDocument.renameWikiWord() to rename a page.
1443        """
1444
1445        p = {}
1446        p["renamed page"] = True
1447        p["renamed wiki page"] = True
1448        p["newWord"] = newWord
1449       
1450        callInMainThreadAsync(self.fireMiscEventProps, p)
1451
1452
1453    def _cloneDeepAttributes(self):
1454        with self.textOperationLock:
1455            result = {}
1456            for key, value in self.getAttributes().iteritems():
1457                result[key] = value[:]
1458               
1459            return result
1460
1461
1462    def checkFileSignatureAndMarkDirty(self, fireEvent=True):
1463        """
1464        First checks if file signature is valid, if not, the
1465        "metadataprocessed" field of the word is set to 0 to mark
1466        meta-data as not up-to-date. At last the signature is
1467        refreshed.
1468       
1469        This all is done inside the lock of the WikiData so it is
1470        somewhat atomically.
1471        """
1472        with self.textOperationLock:
1473            if self.wikiDocument.isReadOnlyEffect():
1474                return True  # TODO Error message?
1475   
1476            if not self.isDefined():
1477                return True  # TODO Error message?
1478   
1479            wikiData = self.getWikiData()
1480            word = self.wikiWord
1481
1482            proxyAccessLock = getattr(wikiData, "proxyAccessLock", None)
1483            if proxyAccessLock is not None:
1484                proxyAccessLock.acquire()
1485            try:
1486                valid = wikiData.validateFileSignatureForWord(word)
1487               
1488                if valid:
1489                    return True
1490   
1491                wikiData.setMetaDataState(word,
1492                        Consts.WIKIWORDMETADATA_STATE_DIRTY)
1493                wikiData.refreshFileSignatureForWord(word)
1494                self.markTextChanged()
1495            finally:
1496                if proxyAccessLock is not None:
1497                    proxyAccessLock.release()
1498
1499            editor = self.getTxtEditor()
1500       
1501        if editor is not None:
1502            # TODO Check for deadlocks
1503            callInMainThread(editor.handleInvalidFileSignature, self)
1504
1505        if fireEvent:
1506            wx.CallAfter(self.fireMiscEventKeys,
1507                    ("checked file signature invalid",))
1508
1509        return False
1510
1511
1512    def markMetaDataDirty(self):
1513        self.getWikiData().setMetaDataState(self.wikiWord,
1514                Consts.WIKIWORDMETADATA_STATE_DIRTY)
1515
1516
1517    def _refreshMetaData(self, pageAst, formatDetails, fireEvent=True,
1518            threadstop=DUMBTHREADSTOP):
1519
1520        # Step 1: Refresh attributes
1521        self.refreshAttributesFromPageAst(pageAst, threadstop=threadstop)
1522
1523        # Some attributes control format details so check if attribute
1524        # refresh changed the details
1525        formatDetails2 = self.getFormatDetails()
1526        if not formatDetails.isEquivTo(formatDetails2):
1527            # Formatting details have changed -> stop and wait for
1528            # new round to update
1529            return False
1530
1531        # Step 2: Refresh todos, link structure ...
1532        self.refreshMainDbCacheFromPageAst(pageAst, fireEvent=fireEvent,
1533                threadstop=threadstop)
1534
1535        # Step 3: Update index search data
1536        self.putIntoSearchIndex(threadstop=threadstop)
1537       
1538        return True
1539
1540
1541
1542    def refreshSyncUpdateMatchTerms(self):
1543        """
1544        Refresh those match terms which must be refreshed synchronously
1545        """
1546        if self.isReadOnlyEffect():
1547            return
1548
1549        WORD_TYPE = Consts.WIKIWORDMATCHTERMS_TYPE_ASLINK | \
1550                Consts.WIKIWORDMATCHTERMS_TYPE_FROM_WORD | \
1551                Consts.WIKIWORDMATCHTERMS_TYPE_SYNCUPDATE
1552
1553        matchTerms = [(self.wikiWord, WORD_TYPE, self.wikiWord, -1, 0)]
1554        self.getWikiData().updateWikiWordMatchTerms(self.wikiWord, matchTerms,
1555                syncUpdate=True)
1556
1557
1558    def refreshAttributesFromPageAst(self, pageAst, threadstop=DUMBTHREADSTOP):
1559        """
1560        Update properties (aka attributes) only.
1561        This is step one in update/rebuild process.
1562        """
1563        if self.isReadOnlyEffect():
1564            return True  # TODO Error?
1565
1566        langHelper = wx.GetApp().createWikiLanguageHelper(
1567                self.getWikiLanguageName())
1568
1569        attrs = {}
1570
1571        def addAttribute(key, value):
1572            threadstop.testRunning()
1573            values = attrs.get(key)
1574            if not values:
1575                values = []
1576                attrs[key] = values
1577            values.append(value)
1578
1579
1580        attrNodes = self.extractAttributeNodesFromPageAst(pageAst)
1581        for node in attrNodes:
1582            for attrKey, attrValue in \
1583                    (getattr(node, "attrs", []) + getattr(node, "props", [])):  # TODO remove "property"-compatibility
1584                addAttribute(attrKey, attrValue)
1585
1586        with self.textOperationLock:
1587            threadstop.testRunning()
1588
1589            self.attrs = None
1590
1591        try:
1592            self.getWikiData().updateAttributes(self.wikiWord, attrs)
1593        except WikiWordNotFoundException:
1594            return False
1595
1596        valid = False
1597
1598        with self.textOperationLock:
1599            if self.saveDirtySince is None and \
1600                    self.livePageBasePlaceHold is self.liveTextPlaceHold and \
1601                    self.livePageBaseFormatDetails is not None and \
1602                    self.getFormatDetails().isEquivTo(self.livePageBaseFormatDetails) and \
1603                    pageAst is self.livePageAst:
1604
1605                threadstop.testRunning()
1606                # clear the dirty flag
1607
1608                self.getWikiData().setMetaDataState(self.wikiWord,
1609                        Consts.WIKIWORDMETADATA_STATE_ATTRSPROCESSED)
1610
1611                valid = True
1612
1613        return valid
1614
1615
1616
1617    def refreshMainDbCacheFromPageAst(self, pageAst, fireEvent=True,
1618            threadstop=DUMBTHREADSTOP):
1619        """
1620        Update everything else (todos, relations).
1621        This is step two in update/rebuild process.
1622        """
1623        if self.isReadOnlyEffect():
1624            return True   # return True or False?
1625
1626        todos = []
1627        childRelations = []
1628        childRelationSet = set()
1629
1630        def addTodo(todoKey, todoValue):
1631            threadstop.testRunning()
1632            todo = (todoKey, todoValue)
1633            if todo not in todos:
1634                todos.append(todo)
1635
1636        def addChildRelationship(toWord, pos):
1637            threadstop.testRunning()
1638            if toWord not in childRelationSet:
1639                childRelations.append((toWord, pos))
1640                childRelationSet.add(toWord)
1641
1642        # Add todo entries
1643        todoNodes = pageAst.iterDeepByName("todoEntry")
1644        for node in todoNodes:
1645            for todoKey, todoValueNode in node.todos:
1646                addTodo(todoKey, todoValueNode.getString())
1647
1648        threadstop.testRunning()
1649
1650        # Add child relations
1651        wwTokens = pageAst.iterDeepByName("wikiWord")
1652        for t in wwTokens:
1653            addChildRelationship(t.wikiWord, t.pos)
1654
1655        threadstop.testRunning()
1656       
1657        # Add aliases to match terms
1658        matchTerms = []
1659
1660        ALIAS_TYPE = Consts.WIKIWORDMATCHTERMS_TYPE_EXPLICIT_ALIAS | \
1661                Consts.WIKIWORDMATCHTERMS_TYPE_ASLINK | \
1662                Consts.WIKIWORDMATCHTERMS_TYPE_FROM_ATTRIBUTES
1663
1664        langHelper = wx.GetApp().createWikiLanguageHelper(
1665                self.getWikiLanguageName())
1666
1667        for w, k, v in self.getWikiDocument().getAttributeTriples(
1668                self.wikiWord, u"alias", None):
1669            threadstop.testRunning()
1670            if not langHelper.checkForInvalidWikiLink(v,
1671                    self.getWikiDocument()):
1672#                 matchTerms.append((v, ALIAS_TYPE, self.wikiWord, -1, -1))
1673                matchTerms.append((langHelper.resolveWikiWordLink(v, self),
1674                        ALIAS_TYPE, self.wikiWord, -1, -1))
1675
1676        # Add headings to match terms if wanted
1677        depth = self.wikiDocument.getWikiConfig().getint(
1678                "main", "headingsAsAliases_depth")
1679
1680        if depth > 0:
1681            HEADALIAS_TYPE = Consts.WIKIWORDMATCHTERMS_TYPE_FROM_CONTENT
1682            for node in pageAst.iterFlatByName("heading"):
1683                threadstop.testRunning()
1684                if node.level > depth:
1685                    continue
1686
1687                title = node.getString()
1688                if title.endswith(u"\n"):
1689                    title = title[:-1]
1690               
1691                matchTerms.append((title, HEADALIAS_TYPE, self.wikiWord,
1692                        node.pos + node.strLength, 0))
1693
1694        with self.textOperationLock:
1695            threadstop.testRunning()
1696
1697            self.todos = None
1698            self.childRelations = None
1699            self.childRelationSet = set()
1700        try:
1701            self.getWikiData().updateTodos(self.wikiWord, todos)
1702            threadstop.testRunning()
1703            self.getWikiData().updateChildRelations(self.wikiWord, childRelations)
1704            threadstop.testRunning()
1705            self.getWikiData().updateWikiWordMatchTerms(self.wikiWord, matchTerms)
1706            threadstop.testRunning()
1707        except WikiWordNotFoundException:
1708            return False
1709#             self.modified = None   # ?
1710#             self.created = None
1711
1712
1713            # Now we check the whole chain if flags can be set:
1714            # db content is identical to liveText
1715            # liveText is basis of current livePageAst
1716            # formatDetails are same as the ones used for livePageAst
1717            # and livePageAst is identical to pageAst processed in this method
1718
1719        valid = False
1720        with self.textOperationLock:
1721#             print "--refreshMainDbCacheFromPageAst43", repr((self.wikiWord, self.saveDirtySince,
1722#                     self.livePageBasePlaceHold is self.liveTextPlaceHold,
1723#                     self.livePageBaseFormatDetails is not None,
1724# #                     self.getFormatDetails().isEquivTo(self.livePageBaseFormatDetails),
1725#                     pageAst is self.livePageAst))
1726            if self.saveDirtySince is None and \
1727                    self.livePageBasePlaceHold is self.liveTextPlaceHold and \
1728                    self.livePageBaseFormatDetails is not None and \
1729                    self.getFormatDetails().isEquivTo(self.livePageBaseFormatDetails) and \
1730                    pageAst is self.livePageAst:
1731
1732                threadstop.testRunning()
1733                # clear the dirty flag
1734                self.updateDirtySince = None
1735
1736                self.getWikiData().setMetaDataState(self.wikiWord,
1737                        Consts.WIKIWORDMETADATA_STATE_SYNTAXPROCESSED)
1738                valid = True
1739
1740        if fireEvent:
1741            callInMainThreadAsync(self.fireMiscEventKeys,
1742                    ("updated wiki page", "updated page"))
1743
1744        return valid
1745
1746
1747    def putIntoSearchIndex(self, threadstop=DUMBTHREADSTOP):
1748        """
1749        Add or update the index for the given docPage
1750        """
1751        with self.textOperationLock:
1752            threadstop.testRunning()
1753
1754            if not self.getWikiDocument().isSearchIndexEnabled():
1755                return True  # Or false?
1756           
1757            liveTextPlaceHold = self.liveTextPlaceHold
1758            content = self.getLiveText()
1759
1760        writer = None
1761        try:
1762            searchIdx = self.getWikiDocument().getSearchIndex()
1763            writer = searchIdx.writer()
1764
1765            unifName = self.getUnifiedPageName()
1766
1767            writer.delete_by_term("unifName", unifName)
1768           
1769            writer.add_document(unifName=unifName,
1770                    modTimestamp=self.getTimestamps()[0],
1771                    content=content)
1772        except:
1773            if writer is not None:
1774                writer.cancel()
1775            raise
1776
1777        # Check within lock if data is current yet
1778        with self.textOperationLock:
1779            if not liveTextPlaceHold is self.liveTextPlaceHold:
1780                writer.cancel()
1781                return False
1782            else:
1783                writer.commit()
1784                self.getWikiData().setMetaDataState(self.wikiWord,
1785                        Consts.WIKIWORDMETADATA_STATE_INDEXED)
1786                return True
1787
1788    def removeFromSearchIndex(self):
1789        unifName = self.getUnifiedPageName()
1790       
1791        if not self.getWikiDocument().isSearchIndexEnabled():
1792            return
1793        try:
1794            searchIdx = self.getWikiDocument().getSearchIndex()
1795            writer = searchIdx.writer()
1796           
1797            writer.delete_by_term("unifName", unifName)
1798        except:
1799            writer.cancel()
1800            raise
1801
1802        writer.commit()
1803
1804
1805#     def update(self):
1806#         return self.runDatabaseUpdate(step=-2)
1807
1808    def runDatabaseUpdate(self, step=-1, threadstop=DUMBTHREADSTOP):
1809        with self.textOperationLock:
1810            if not self.isDefined():
1811                return False
1812            if self.isReadOnlyEffect():
1813                return False
1814
1815            liveTextPlaceHold = self.liveTextPlaceHold
1816            formatDetails = self.getFormatDetails()
1817
1818        try:
1819            pageAst = self.getLivePageAst(dieOnChange=True,
1820                    threadstop=threadstop)
1821
1822            # Check within lock if data is current yet
1823            with self.textOperationLock:
1824                if not liveTextPlaceHold is self.liveTextPlaceHold:
1825                    return False
1826   
1827   
1828            if step == -1:
1829                self._refreshMetaData(pageAst, formatDetails, threadstop=threadstop)
1830
1831                with self.textOperationLock:
1832                    if not liveTextPlaceHold is self.liveTextPlaceHold:
1833                        return False
1834                    if not formatDetails.isEquivTo(self.getFormatDetails()):
1835                        self.initiateUpdate()
1836                        return False
1837#             elif step == -2:
1838#                 for i in range(15):   # while True  is too dangerous
1839#                     metaState = self.getWikiData().getMetaDataState(self.wikiWord)
1840#
1841#                     if not liveTextPlaceHold is self.liveTextPlaceHold:
1842#                         return False
1843#                     if not formatDetails.isEquivTo(self.getFormatDetails()):
1844#                         self.initiateUpdate()
1845#                         return False
1846#
1847#                     if metaState == Consts.WIKIWORDMETADATA_STATE_SYNTAXPROCESSED:
1848#                         return True
1849#
1850#                     elif metaState == Consts.WIKIWORDMETADATA_STATE_ATTRSPROCESSED:
1851#                         self.refreshMainDbCacheFromPageAst(pageAst,
1852#                                 threadstop=threadstop)
1853#                         continue
1854#
1855#                     else: # step == Consts.WIKIWORDMETADATA_STATE_DIRTY
1856#                         self.refreshAttributesFromPageAst(pageAst,
1857#                                 threadstop=threadstop)
1858#                         continue
1859            else:
1860                metaState = self.getMetaDataState()
1861
1862                if metaState >= self.getWikiDocument().getFinalMetaDataState() or \
1863                        metaState != step:
1864                    return False
1865   
1866                if step == Consts.WIKIWORDMETADATA_STATE_SYNTAXPROCESSED:
1867                    #
1868                    return self.putIntoSearchIndex(threadstop=threadstop)
1869
1870                elif step == Consts.WIKIWORDMETADATA_STATE_ATTRSPROCESSED:
1871                    return self.refreshMainDbCacheFromPageAst(pageAst,
1872                            threadstop=threadstop)
1873                else: # step == Consts.WIKIWORDMETADATA_STATE_DIRTY
1874                    return self.refreshAttributesFromPageAst(pageAst,
1875                            threadstop=threadstop)
1876
1877        except NotCurrentThreadException:
1878            return False
1879
1880
1881
1882    def initiateUpdate(self, fireEvent=True):
1883        """
1884        Initiate update of page meta-data. This function may call update
1885        directly if it can be done fast
1886        """
1887        with self.textOperationLock:
1888            self.wikiDocument.pushUpdatePage(self)
1889
1890
1891    def _save(self, text, fireEvent=True):
1892        """
1893        Saves the content of current wiki page.
1894        """
1895        if self.isReadOnlyEffect():
1896            return
1897       
1898        with self.textOperationLock:
1899            if not self.getWikiDocument().isDefinedWikiPage(self.wikiWord):
1900                # Pages isn't yet in database  -> fire event
1901                # The event may be needed to invalidate a cache
1902                self.fireMiscEventKeys(("saving new wiki page",))
1903
1904            self.getWikiData().setContent(self.wikiWord, text)
1905            self.refreshSyncUpdateMatchTerms()
1906            self.saveDirtySince = None
1907#             self.dbContentPlaceHold = object()
1908            if self.getEditorText() is None:
1909                self.liveTextPlaceHold = object()
1910
1911
1912            # Clear timestamp cache
1913            self.modified = None
1914
1915
1916
1917
1918    # ----- Advanced functions -----
1919
1920    def getChildRelationshipsTreeOrder(self, existingonly=False,
1921            excludeSet=frozenset(), includeSet=frozenset()):
1922        """
1923        Return a list of children wiki words of the page, ordered as they would
1924        appear in tree. Some children may be missing if they e.g.
1925        are set as hidden.
1926        existingonly -- true iff non-existing words should be hidden
1927        excludeSet -- set of words which should be excluded from the list
1928        includeSet -- wikiWords to include in the result
1929        """
1930       
1931        wikiDocument = self.wikiDocument
1932       
1933        # get the sort order for the children
1934        childSortOrder = self.getAttributeOrGlobal(u'child_sort_order',
1935                u"ascending")
1936           
1937        # Apply sort order
1938        if childSortOrder == u"natural":
1939            # TODO: Do it right
1940            # Retrieve relations as list of tuples (child, firstcharpos)
1941            relations = self.getChildRelationships(existingonly,
1942                    selfreference=False, withFields=("firstcharpos",),
1943                    excludeSet=excludeSet, includeSet=includeSet)
1944
1945            relations.sort(_cmpNumbersItem1)
1946            # Remove firstcharpos
1947            relations = [r[0] for r in relations]
1948        elif childSortOrder == u"mod_oldest":
1949            # Retrieve relations as list of tuples (child, modifTime)
1950            relations = self.getChildRelationships(existingonly,
1951                    selfreference=False, withFields=("modified",),
1952                    excludeSet=excludeSet, includeSet=includeSet)
1953            relations.sort(_cmpNumbersItem1)
1954            # Remove firstcharpos
1955            relations = [r[0] for r in relations]
1956        elif childSortOrder == u"mod_newest":
1957            # Retrieve relations as list of tuples (child, modifTime)
1958            relations = self.getChildRelationships(existingonly,
1959                    selfreference=False, withFields=("modified",),
1960                    excludeSet=excludeSet, includeSet=includeSet)
1961            relations.sort(_cmpNumbersItem1Rev)
1962            # Remove firstcharpos
1963            relations = [r[0] for r in relations]           
1964        else:
1965            # Retrieve relations as list of children words
1966            relations = self.getChildRelationships(existingonly,
1967                    selfreference=False, withFields=(),
1968                    excludeSet=excludeSet, includeSet=includeSet)
1969            if childSortOrder.startswith(u"desc"):
1970                coll = wikiDocument.getCollator()
1971
1972                def cmpLowerDesc(a, b):
1973                    return coll.strcoll(
1974                            b.lower(), a.lower())
1975                           
1976                # TODO Python 3.0 supports only key argument, no cmp. function
1977                relations.sort(cmpLowerDesc) # sort alphabetically
1978            elif childSortOrder.startswith(u"asc"):
1979                coll = wikiDocument.getCollator()
1980
1981                def cmpLowerAsc(a, b):
1982                    return coll.strcoll(
1983                            a.lower(), b.lower())
1984
1985                relations.sort(cmpLowerAsc)
1986
1987
1988
1989        priorized = []
1990        positioned = []
1991        other = []
1992
1993        # Put relations into their appropriate arrays
1994        for relation in relations:
1995            relationPage = wikiDocument.getWikiPageNoError(relation)
1996            attrs = relationPage.getAttributes()
1997            try:
1998                if (attrs.has_key(u'tree_position')):
1999                    positioned.append((int(attrs[u'tree_position'][-1]) - 1, relation))
2000                elif (attrs.has_key(u'priority')):
2001                    priorized.append((int(attrs[u'priority'][-1]), relation))
2002                else:
2003                    other.append(relation)
2004            except:
2005                other.append(relation)
2006               
2007        # Sort special arrays
2008        priorized.sort(key=lambda t: t[0])
2009        positioned.sort(key=lambda t: t[0])
2010
2011
2012        result = []
2013        ipr = 0
2014        ipo = 0
2015        iot = 0
2016
2017        for i in xrange(len(relations)):
2018            if ipo < len(positioned) and positioned[ipo][0] <= i:
2019                result.append(positioned[ipo][1])
2020                ipo += 1
2021                continue
2022           
2023            if ipr < len(priorized):
2024                result.append(priorized[ipr][1])
2025                ipr += 1
2026                continue
2027           
2028            if iot < len(other):
2029                result.append(other[iot])
2030                iot += 1
2031                continue
2032           
2033            # When reaching this, only positioned can have elements yet
2034            if ipo < len(positioned):
2035                result.append(positioned[ipo][1])
2036                ipo += 1
2037                continue
2038           
2039            raise InternalError("Empty relation sorting arrays")
2040       
2041
2042        return result
2043
2044
2045#         # TODO Remove aliases?
2046#     def _flatTreeHelper(self, page, deepness, excludeSet, includeSet, result,
2047#             unalias):
2048#         """
2049#         Recursive part of getFlatTree
2050#         """
2051# #         print "_flatTreeHelper1", repr((page.getWikiWord(), deepness, len(excludeSet)))
2052#
2053#         word = page.getWikiWord()
2054#         nonAliasWord = page.getNonAliasPage().getWikiWord()
2055#         excludeSet.add(nonAliasWord)
2056#
2057#         children = page.getChildRelationshipsTreeOrder(existingonly=True)
2058#
2059#         for word in children:
2060#             subpage = self.wikiDocument.getWikiPage(word)
2061#             nonAliasWord = subpage.getNonAliasPage().getWikiWord()
2062#             if nonAliasWord in excludeSet:
2063#                 continue
2064#             if unalias:
2065#                 result.append((nonAliasWord, deepness + 1))
2066#             else:
2067#                 result.append((word, deepness + 1))
2068#             
2069#             if includeSet is not None:
2070#                 includeSet.discard(word)
2071#                 includeSet.discard(nonAliasWord)
2072#                 if len(includeSet) == 0:
2073#                     return
2074#             
2075#             self._flatTreeHelper(subpage, deepness + 1, excludeSet, includeSet,
2076#                     result, unalias)
2077#
2078#
2079#     def getFlatTree(self, unalias=False, includeSet=None):
2080#         """
2081#         Returns a sequence of tuples (word, deepness) where the current
2082#         word is the first one with deepness 0.
2083#         The words may contain aliases, but no word appears twice neither
2084#         will both a word and its alias appear in the list.
2085#         unalias -- replace all aliases by their real word
2086#         TODO EXPLAIN FUNCTION !!!
2087#         """
2088#         word = self.getWikiWord()
2089#         nonAliasWord = self.getNonAliasPage().getWikiWord()
2090#
2091#         if unalias:
2092#             result = [(nonAliasWord, 0)]
2093#         else:
2094#             result = [(word, 0)]
2095#
2096#         if includeSet is not None:
2097#             includeSet.discard(word)
2098#             includeSet.discard(nonAliasWord)
2099#             if len(includeSet) == 0:
2100#                 return result
2101#
2102#         excludeSet = set()   # set((self.getWikiWord(),))
2103#
2104#         self._flatTreeHelper(self, 0, excludeSet, includeSet, result, unalias)
2105#
2106# #         print "getFlatTree", repr(result)
2107#
2108#         return result
2109
2110
2111    def getFlatTree(self, unalias=False, includeSet=None, maxdepth=-1,
2112            resetdepth=0):
2113        """
2114        Returns a sequence of tuples (word, deepness) where the current
2115        word is the first one with deepness 0.
2116        The words may contain aliases, but no word appears twice neither
2117        will both a word and its alias appear in the list.
2118       
2119        unalias -- if to replace all aliases by their real word
2120        maxdepth -- don't create entries deeper than that level (-1: no limit)
2121        resetdepth -- if entry outreaches maxdepth it is inserted later
2122                with level  resetdepth  (-1: entry isn't inserted anymore)
2123        """
2124        getUnAliasedWikiWord = self.getWikiDocument().getUnAliasedWikiWord
2125
2126        checkList = [(self.getWikiWord(), self.getNonAliasPage().getWikiWord(),
2127                0)]
2128
2129        mixins = collections.deque()
2130        resultSet = set()
2131        result = []
2132
2133        while True:
2134            if len(checkList) > 0:
2135                word, nonAliasWord, chLevel = checkList[-1]
2136
2137                if len(mixins) > 0 and mixins[-1][2] >= chLevel:
2138                    word, nonAliasWord, chLevel = mixins.pop()
2139                else:
2140                    del checkList[-1]
2141            else:
2142                if len(mixins) == 0:
2143                    break # Everything empty -> terminate
2144                else:
2145                    mixList = list(mixins)
2146                    mixList.reverse()
2147                    checkList.extend((w, naw, 0) for w, naw, l in mixList)
2148                    mixins.clear()
2149                    continue
2150
2151            if nonAliasWord in resultSet:
2152                continue
2153
2154            if maxdepth > -1 and chLevel > maxdepth:
2155                # Don't go deeper
2156                if resetdepth > -1:
2157                    mixins.appendleft((word, nonAliasWord, resetdepth))
2158                continue
2159
2160            if unalias:
2161                result.append((nonAliasWord, chLevel))
2162            else:
2163                result.append((word, chLevel))
2164
2165            resultSet.add(nonAliasWord)
2166
2167            if includeSet is not None:
2168                includeSet.discard(word)
2169                includeSet.discard(nonAliasWord)
2170                if len(includeSet) == 0:
2171                    return result
2172
2173
2174            page = self.getWikiDocument().getWikiPage(nonAliasWord)
2175            children = page.getChildRelationshipsTreeOrder(existingonly=True)
2176
2177            children = [(c, getUnAliasedWikiWord(c), chLevel + 1)
2178                    for c in children]
2179            children.reverse()
2180            checkList += children
2181
2182        return result
2183
2184
2185
2186    def getDependentDataBlocks(self):
2187        vo = self.getExistingVersionOverview()
2188       
2189        if vo is None:
2190            return []
2191       
2192        return vo.getDependentDataBlocks()
2193
2194
2195
2196
2197
2198# TODO: Maybe split into single classes for each tag
2199
2200class FunctionalPage(DataCarryingPage):
2201    """
2202    holds the data for a functional page. Such a page controls the behavior
2203    of the application or a special wiki
2204    """
2205    def __init__(self, wikiDocument, funcTag):
2206        DataCarryingPage.__init__(self, wikiDocument)
2207       
2208        if not isFuncTag(funcTag):
2209            raise BadFuncPageTagException(
2210                    _(u"Func. tag %s does not exist") % funcTag)
2211
2212        self.funcTag = funcTag
2213
2214        # does this page need to be saved?
2215        self.saveDirtySince = None  # None if not dirty or timestamp when it became dirty
2216        self.updateDirtySince = None
2217
2218
2219    def getWikiWord(self):
2220        return None
2221
2222    def getTitle(self):
2223        """
2224        Return human readable title of the page.
2225        """
2226        return u"<" + getHrNameForFuncTag(self.funcTag) + u">"
2227
2228
2229    def getFuncTag(self):
2230        """
2231        Return the functional tag of the page (a kind of filepath
2232        for the page)
2233        """
2234        return self.funcTag
2235
2236    def getUnifiedPageName(self):
2237        """
2238        Return the name of the unified name of the page, which is
2239        "wikipage/" + the wiki word for wiki pages or the functional tag
2240        for functional pages.
2241        """
2242        return self.funcTag
2243
2244
2245    def _loadGlobalPage(self, subtag):
2246        tbLoc = os.path.join(wx.GetApp().getGlobalConfigSubDir(),
2247                "[%s].wiki" % subtag)
2248        try:
2249            tbContent = loadEntireTxtFile(tbLoc)
2250            return fileContentToUnicode(lineendToInternal(tbContent))
2251        except:
2252            return u""
2253
2254
2255    def _loadDbSpecificPage(self, funcTag):
2256        content = self.wikiDocument.getWikiData().retrieveDataBlockAsText(funcTag)
2257        if content is None:
2258            return u""
2259       
2260        return content
2261
2262#         if self.wikiDocument.isDefinedWikiWord(subtag):
2263#             return self.wikiDocument.getWikiData().getContent(subtag)
2264#         else:
2265#             return u""
2266
2267
2268    def getLiveTextNoTemplate(self):
2269        """
2270        Return None if page isn't existing instead of creating an automatic
2271        live text (e.g. by template).
2272        Functional pages by definition exist always
2273        """
2274        return self.getLiveText()
2275
2276
2277    def getContent(self):
2278        if self.funcTag in (u"global/TextBlocks", u"global/PWL",
2279                u"global/CCBlacklist", u"global/FavoriteWikis"):
2280            return self._loadGlobalPage(self.funcTag[7:])
2281        elif self.funcTag in (u"wiki/TextBlocks", u"wiki/PWL",
2282                u"wiki/CCBlacklist"):
2283            return self._loadDbSpecificPage(self.funcTag)
2284
2285
2286    def getFormatDetails(self):
2287        """
2288        According to currently stored settings, return a
2289        ParseUtilities.WikiPageFormatDetails object to describe
2290        formatting.
2291       
2292        For functional pages this is normally no formatting
2293        """
2294        return ParseUtilities.WikiPageFormatDetails(noFormat=True)
2295
2296
2297    def getLivePageAstIfAvailable(self):
2298        return self.getLivePageAst()
2299
2300
2301    # TODO Checking with dieOnChange == True
2302    def getLivePageAst(self, fireEvent=True, dieOnChange=False,
2303            threadstop=DUMBTHREADSTOP):
2304        """
2305        The PageAst of a func. page is always a single "default" token
2306        containing the whole text.
2307        """
2308        with self.livePageAstBuildLock:
2309            threadstop.testRunning()
2310   
2311            pageAst = self.livePageAst
2312           
2313            if pageAst is not None:
2314                return pageAst
2315
2316            with self.textOperationLock:
2317                pageAst = buildSyntaxNode([buildSyntaxNode(
2318                        self.getLiveText(), 0, "plainText")], 0, "text")
2319
2320                threadstop.testRunning()
2321
2322                self.livePageAst = pageAst
2323
2324                return pageAst
2325
2326
2327
2328    def _saveGlobalPage(self, text, subtag):
2329        tbLoc = os.path.join(wx.GetApp().getGlobalConfigSubDir(),
2330                "[%s].wiki" % subtag)
2331
2332        writeEntireFile(tbLoc, text, True)
2333
2334
2335    def _saveDbSpecificPage(self, text, funcTag):
2336        if self.isReadOnlyEffect():
2337            return
2338
2339        wikiData = self.wikiDocument.getWikiData()
2340       
2341        if text == u"":
2342            wikiData.deleteDataBlock(funcTag)
2343        else:
2344            wikiData.storeDataBlock(funcTag, text,
2345                    storeHint=Consts.DATABLOCK_STOREHINT_EXTERN)
2346
2347
2348#         if self.wikiDocument.isDefinedWikiWord(subtag) and text == u"":
2349#             # Delete content
2350#             wikiData.deleteContent(subtag)
2351#         else:
2352#             if text != u"":
2353#                 wikiData.setContent(subtag, text)
2354
2355
2356    def _save(self, text, fireEvent=True):
2357        """
2358        Saves the content of current wiki page.
2359        """
2360        if self.isReadOnlyEffect():
2361            return
2362       
2363        with self.textOperationLock:
2364            # text = self.getLiveText()
2365   
2366            if self.funcTag in (u"global/TextBlocks", u"global/PWL",
2367                    u"global/CCBlacklist", u"global/FavoriteWikis"):
2368                self._saveGlobalPage(text, self.funcTag[7:])
2369            elif self.funcTag in (u"wiki/TextBlocks", u"wiki/PWL",
2370                    u"wiki/CCBlacklist"):
2371                self._saveDbSpecificPage(text, self.funcTag)
2372
2373            self.saveDirtySince = None
2374
2375
2376
2377    def initiateUpdate(self, fireEvent=True):
2378        """
2379        Update additional cached informations (attributes, todos, relations).
2380        Here it is done directly in initiateUpdate() because it doesn't need
2381        much work.
2382        """
2383        if self.isReadOnlyEffect():
2384            return
2385
2386        with self.textOperationLock:
2387            # clear the dirty flag
2388            self.updateDirtySince = None
2389   
2390            if fireEvent:
2391                if self.funcTag.startswith(u"wiki/"):
2392                    evtSource = self
2393                else:
2394                    evtSource = wx.GetApp()
2395   
2396                if self.funcTag in (u"global/TextBlocks", u"wiki/TextBlocks"):
2397                    # The text blocks for the text blocks submenu was updated
2398                    evtSource.fireMiscEventKeys(("updated func page", "updated page",
2399                            "reread text blocks needed"))
2400                elif self.funcTag in (u"global/PWL", u"wiki/PWL"):
2401                    # The personal word list (words to ignore by spell checker)
2402                    # was updated
2403                    evtSource.fireMiscEventKeys(("updated func page", "updated page",
2404                            "reread personal word list needed"))
2405                elif self.funcTag in (u"global/CCBlacklist", u"wiki/CCBlacklist"):
2406                    # The blacklist of camelcase words not to mark as wiki links
2407                    # was updated
2408                    evtSource.fireMiscEventKeys(("updated func page", "updated page",
2409                            "reread cc blacklist needed"))
2410                elif self.funcTag == u"global/FavoriteWikis":
2411                    # The list of favorite wikis was updated (there is no
2412                    # wiki-bound version of favorite wikis
2413                    evtSource.fireMiscEventKeys(("updated func page", "updated page",
2414                            "reread favorite wikis needed"))
2415
2416    def isReadOnlyEffect(self):
2417        """
2418        Return true if page is effectively read-only, this means
2419        "for any reason", regardless if error or intention.
2420        Global func. pages do not depend on the wiki state so they are writable.
2421        """
2422        if self.funcTag.startswith(u"global/"):
2423            # Global pages are not stored in the wiki and are always writable
2424            return False
2425        else:
2426            return DataCarryingPage.isReadOnlyEffect(self)
2427
2428
2429    def getPresentation(self):
2430        """Dummy"""
2431        return (0, 0, 0, 0, 0)
2432
2433    def setPresentation(self, data, startPos):
2434        """Dummy"""
2435        pass
2436       
2437
2438
2439
2440# Two search helpers for WikiPage.getChildRelationshipsTreeOrder
2441
2442def _floatToCompInt(f):
2443    if f > 0:
2444        return 1
2445    elif f < 0:
2446        return -1
2447    else:
2448        return 0
2449
2450
2451
2452# TODO: Remove for Python 3.0
2453def _cmpNumbersItem1(a, b):
2454    """
2455    Compare "natural", means using the char. positions or moddates of
2456    the links in page.
2457    """
2458    return _floatToCompInt(a[1] - b[1])
2459
2460
2461def _cmpNumbersItem1Rev(a, b):
2462    """
2463    Compare "natural", means using the char. positions or moddates of
2464    the links in page.
2465    """
2466    return _floatToCompInt(b[1] - a[1])
2467
2468
2469
2470_FUNCTAG_TO_HR_NAME_MAP = {
2471            u"global/TextBlocks": N_(u"Global text blocks"),
2472            u"wiki/TextBlocks": N_(u"Wiki text blocks"),
2473            u"global/PWL": N_(u"Global spell list"),
2474            u"wiki/PWL": N_(u"Wiki spell list"),
2475            u"global/CCBlacklist": N_(u"Global cc. blacklist"),
2476            u"wiki/CCBlacklist": N_(u"Wiki cc. blacklist"),
2477            u"global/FavoriteWikis": N_(u"Favorite wikis"),
2478        }
2479
2480
2481def getHrNameForFuncTag(funcTag):
2482    """
2483    Return the human readable name of functional page with tag funcTag.
2484    """
2485    return _(_FUNCTAG_TO_HR_NAME_MAP.get(funcTag, funcTag))
2486   
2487
2488def getFuncTags():
2489    """
2490    Return all available func tags
2491    """
2492    return _FUNCTAG_TO_HR_NAME_MAP.keys()
2493
2494
2495def isFuncTag(funcTag):
2496    return _FUNCTAG_TO_HR_NAME_MAP.has_key(funcTag)
Note: See TracBrowser for help on using the browser.