NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

bookmarkSvc.py (30920B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\bookmarks\bookmarkSvc.py
      2 import heapq
      3 from collections import defaultdict
      4 import blue
      5 import telemetry
      6 import threadutils
      7 import uthread
      8 import locks
      9 import utillib
     10 from caching.memoize import Memoize
     11 from carbon.common.script.sys.service import Service
     12 from eve.client.script.ui.shared.bookmarks.linkedBookmarkFolderWindow import LinkedBookmarkFolderWindow
     13 from eve.client.script.util.bookmarkUtil import IsBookmarkAvailable, GetACLBookmarkMenuForSystem
     14 from eve.common.lib import appConst
     15 from eve.common.script.mgt import bookmarkConst
     16 from eve.common.script.mgt.bookmarkConst import SHARED_BOOKMARK_DELAY, MAX_BOOKMARKS_SENT_TO_CLIENT
     17 from eve.common.script.sys import idCheckers
     18 from eveexceptions import UserError
     19 from evetypes import GetCategoryID
     20 from localization import GetByLabel
     21 from player_messaging.client.ui_message import prompt_player_fsd_dialog
     22 from sensorsuite.overlay.bookmarkvisibilitymanager import ReleaseBookmarkFolderVisibilityManager
     23 CACHE_EXPIRATION_TIME = 5 * appConst.MIN
     24 MIN_DELAYED_UI_REFRESH_TIME = 5 * appConst.SEC
     25 
     26 class BookmarkSvc(Service):
     27     __guid__ = 'svc.bookmarkSvc'
     28     __startupdependencies__ = ['machoNet']
     29     __notifyevents__ = ['OnSharedBookmarksFolderUpdated',
     30      'OnSharedBookmarksFolderDeleted',
     31      'OnSharedBookmarksFoldersAccessLost',
     32      'OnSharedBookmarksFoldersAccessChanged',
     33      'OnSessionReset']
     34 
     35     def Run(self, memStream = None):
     36         Service.Run(self, memStream)
     37         self.Initialize()
     38 
     39     def Initialize(self):
     40         self.accessGroupBookmarkMgr = sm.RemoteSvc('accessGroupBookmarkMgr')
     41         self.lastUpdateTime = None
     42         self.validCategories = (appConst.categoryCelestial,
     43          appConst.categoryAsteroid,
     44          appConst.categoryStation,
     45          appConst.categoryShip,
     46          appConst.categoryStarbase,
     47          appConst.categoryPlanetaryInteraction)
     48         self.bookmarksPrimed = False
     49         self.foldersNew = {}
     50         self.bookmarkCache = {}
     51         self.bookmarkIDsBySolarystemID = defaultdict(set)
     52         self.subfolders = {}
     53         self.delayedUpdateQueue = []
     54         self.delayedUpdateThread = None
     55         self.onlyPartiallyLoaded = False
     56 
     57     def OnSessionReset(self):
     58         if self.delayedUpdateThread is not None:
     59             self.delayedUpdateThread.kill()
     60         ReleaseBookmarkFolderVisibilityManager()
     61         self.SearchFoldersWithAdminAccess.clear_memoized()
     62         self.Initialize()
     63 
     64     @telemetry.ZONE_METHOD
     65     def GetBookmarks(self):
     66         self.PrimeAndCleanupBookmarks()
     67         return self.bookmarkCache
     68 
     69     def PrimeAndCleanupBookmarks(self):
     70         if not self.bookmarksPrimed:
     71             self.ACLPrimeBookmarks()
     72         if self.DoesCacheNeedUpdating():
     73             self.RefreshACLBookmarks()
     74             self.lastUpdateTime = blue.os.GetWallclockTime()
     75 
     76     def GetBookmarksForLocationID(self, locationID):
     77         self.PrimeAndCleanupBookmarks()
     78         ret = []
     79         bookmarksForLocationID = self.bookmarkIDsBySolarystemID.get(locationID, set())
     80         if not bookmarksForLocationID:
     81             return ret
     82         for eachBookmarkID in bookmarksForLocationID:
     83             bm = self.GetBookmark(eachBookmarkID)
     84             if bm:
     85                 ret.append(bm)
     86 
     87         return ret
     88 
     89     def DoesCacheNeedUpdating(self):
     90         if self.lastUpdateTime is None:
     91             return True
     92         timeSinceLastUpdate = blue.os.GetWallclockTime() - self.lastUpdateTime
     93         return timeSinceLastUpdate > CACHE_EXPIRATION_TIME
     94 
     95     def GetBookmark(self, bookmarkID):
     96         return self.bookmarkCache[bookmarkID]
     97 
     98     @threadutils.Throttled(2)
     99     def RefreshUIOnUpdate(self):
    100         sm.ScatterEvent('OnRefreshBookmarks')
    101 
    102     def GetPersonalAndSharedFolders(self):
    103         personal = set()
    104         shared = set()
    105         for eachFolderID, eachFolder in self.GetFolders().iteritems():
    106             if eachFolder.isPersonal:
    107                 personal.add(eachFolderID)
    108             else:
    109                 shared.add(eachFolderID)
    110 
    111         return (personal, shared)
    112 
    113     def GetAgentBookmarks(self):
    114         bookmarks = {}
    115         agentMenu = sm.GetService('journal').GetMyAgentJournalBookmarks()
    116         if agentMenu:
    117             for missionNameID, bms, agentID in agentMenu:
    118                 missionName = unicode(missionNameID) + unicode(agentID)
    119                 for bm in bms:
    120                     c = bm.copy()
    121                     c.missionName = missionName
    122                     c.hint = bm.hint
    123                     c.memo = c.hint + '\t'
    124                     c.missionNameID = missionNameID
    125                     c.bookmarkID = ('agentmissions',
    126                      c.agentID,
    127                      c.locationType,
    128                      c.locationNumber)
    129                     c.expiry = None
    130                     bookmarks[c.bookmarkID] = c
    131 
    132         return bookmarks
    133 
    134     def GetAllBookmarks(self):
    135         bms = self.GetBookmarks().copy()
    136         bms.update(self.GetAgentBookmarks())
    137         return bms
    138 
    139     def UnzipMemo(self, s):
    140         if s is None:
    141             s = ''
    142         if '\t' in s:
    143             split = s.split('\t')
    144         else:
    145             split = [ each.replace('\\|', '|') for each in s.split('||') ]
    146         if len(split) == 1:
    147             return (split[0], '')
    148         return split[:2]
    149 
    150     @telemetry.ZONE_METHOD
    151     def ACLPrimeBookmarks(self):
    152         if self.bookmarksPrimed:
    153             return
    154         with locks.TempLock('PrimeACLBookmarks'):
    155             if self.bookmarksPrimed:
    156                 return
    157             folders, bookmarks, subfolders = self.accessGroupBookmarkMgr.GetMyActiveBookmarks()
    158             self.foldersNew = {folder.folderID:folder for folder in folders}
    159             for eachBookmark in bookmarks:
    160                 self.bookmarkCache[eachBookmark.bookmarkID] = eachBookmark
    161                 self.bookmarkIDsBySolarystemID[eachBookmark.locationID].add(eachBookmark.bookmarkID)
    162 
    163             self.subfolders = {subfolder.subfolderID:subfolder for subfolder in subfolders}
    164             self.lastUpdateTime = blue.os.GetWallclockTime()
    165             self.bookmarksPrimed = True
    166             if len(bookmarks) >= MAX_BOOKMARKS_SENT_TO_CLIENT:
    167                 self.onlyPartiallyLoaded = True
    168             if not self.foldersNew:
    169                 self.CreateDefaultFolder()
    170             self.CheckBookmarksForDelayedUpdate(bookmarks)
    171 
    172     @telemetry.ZONE_METHOD
    173     def RefreshACLBookmarks(self):
    174         self.LogNotice('RefreshACLBookmarks called')
    175         self.RemoveExpiredBookmarks()
    176         self.RefreshUIOnUpdate()
    177 
    178     def RemoveExpiredBookmarks(self):
    179         for bookmarkID, bookmark in self.bookmarkCache.items():
    180             if bookmark.expiry and bookmark.expiry < blue.os.GetWallclockTime():
    181                 del self.bookmarkCache[bookmarkID]
    182                 self.bookmarkIDsBySolarystemID[bookmark.locationID].discard(bookmark.bookmarkID)
    183 
    184     def CheckBookmarksForDelayedUpdate(self, bookmarks):
    185         refreshDelayedAt = []
    186         for bookmark in bookmarks:
    187             if not bookmark.creatorID or bookmark.creatorID == session.charid:
    188                 continue
    189             if bookmark.created + SHARED_BOOKMARK_DELAY > blue.os.GetWallclockTime():
    190                 refreshDelayedAt.append(bookmark.created + SHARED_BOOKMARK_DELAY)
    191 
    192         if refreshDelayedAt:
    193             self.RefreshUIDelayed(refreshDelayedAt)
    194 
    195     def RefreshUIDelayed(self, timeStamps):
    196         for timeStamp in timeStamps:
    197             heapq.heappush(self.delayedUpdateQueue, timeStamp)
    198 
    199         if self.delayedUpdateThread:
    200             return
    201         self.delayedUpdateThread = uthread.worker('bookmarkSvc::DelayedUpdateThread', self.DelayedUpdateThread)
    202 
    203     @telemetry.ZONE_METHOD
    204     def DelayedUpdateThread(self):
    205         time = 0
    206         while self.delayedUpdateQueue:
    207             while self.delayedUpdateQueue:
    208                 time = heapq.heappop(self.delayedUpdateQueue)
    209                 if time > blue.os.GetWallclockTime():
    210                     break
    211 
    212             sleep = int(max(MIN_DELAYED_UI_REFRESH_TIME, time - blue.os.GetWallclockTime()) / 10000)
    213             blue.pyos.synchro.SleepWallclock(sleep)
    214             self.RefreshUIOnUpdate()
    215 
    216         self.delayedUpdateThread = None
    217 
    218     @telemetry.ZONE_METHOD
    219     def OnSharedBookmarksFolderUpdated(self, updates):
    220         for target, updateType, updateArgs in updates:
    221             if updateType == bookmarkConst.BOOKMARKS_MOVED:
    222                 bookmarks, oldFolderID, newFolderID = updateArgs
    223                 if newFolderID not in self.foldersNew or not self.foldersNew[newFolderID].isActive:
    224                     if oldFolderID in self.foldersNew and self.foldersNew[oldFolderID].isActive:
    225                         bookmarkIDs = [ bm.bookmarkID for bm in bookmarks ]
    226                         self._RemoveBookmarksFromCache(bookmarkIDs)
    227                 else:
    228                     self._AddBookmarksToCache(bookmarks)
    229                 continue
    230             folderID = target[0]
    231             if folderID not in self.foldersNew or not self.foldersNew[folderID].isActive:
    232                 self.LogInfo('Tried to update folder, which is not an active folder', folderID)
    233                 continue
    234             if updateType == bookmarkConst.BOOKMARKS_ADDED:
    235                 bookmarks = updateArgs
    236                 self._AddBookmarksToCache(bookmarks)
    237             elif updateType == bookmarkConst.BOOKMARKS_REMOVED:
    238                 deletedBookmarkIDs = updateArgs
    239                 self._RemoveBookmarksFromCache(deletedBookmarkIDs)
    240             elif updateType == bookmarkConst.BOOKMARKS_UPDATED:
    241                 bookmarks = updateArgs
    242                 self._AddBookmarksToCache(bookmarks)
    243                 continue
    244             elif updateType == bookmarkConst.FOLDER_UPDATED:
    245                 name, description = updateArgs
    246                 self._UpdateFolderInCache(folderID, name, description)
    247             elif updateType == bookmarkConst.SUBFOLDER_ADDED:
    248                 subfolder = updateArgs
    249                 self._AddSubfolderToCache(subfolder)
    250             elif updateType == bookmarkConst.SUBFOLDER_UPDATED:
    251                 subfolderID, subfolderName = updateArgs
    252                 self._UpdateSubfolderName(subfolderID, subfolderName)
    253             elif updateType == bookmarkConst.SUBFOLDER_REMOVED:
    254                 subfolderID = updateArgs
    255                 self._RemoveSubfolderFromCache(subfolderID)
    256 
    257         self.RefreshUIOnUpdate()
    258 
    259     def _AddBookmarksToCache(self, bookmarks):
    260         for bm in bookmarks:
    261             self.bookmarkCache[bm.bookmarkID] = bm
    262             self.bookmarkIDsBySolarystemID[bm.locationID].add(bm.bookmarkID)
    263 
    264         self.CheckBookmarksForDelayedUpdate(bookmarks)
    265 
    266     def _RemoveBookmarksFromCache(self, deletedBookmarkIDs):
    267         for bookmarkID in deletedBookmarkIDs:
    268             bm = self.bookmarkCache.pop(bookmarkID, None)
    269             if bm:
    270                 self.bookmarkIDsBySolarystemID[bm.locationID].discard(bm.bookmarkID)
    271 
    272     def _UpdateFolderInCache(self, folderID, folderName, description):
    273         if folderID not in self.foldersNew:
    274             return
    275         self.foldersNew[folderID].folderName = folderName
    276         self.foldersNew[folderID].description = description
    277 
    278     def _AddSubfolderToCache(self, subfolder):
    279         self.subfolders[subfolder.subfolderID] = subfolder
    280 
    281     def _UpdateSubfolderName(self, subfolderID, subfolderName):
    282         if subfolderID not in self.subfolders:
    283             return
    284         self.subfolders[subfolderID].subfolderName = subfolderName
    285 
    286     def _RemoveSubfolderFromCache(self, subfolderID):
    287         self.subfolders.pop(subfolderID, None)
    288 
    289     def GetFolderNamesAndType(self, folderID, subfolderID):
    290         folder = self.foldersNew.get(folderID, None)
    291         isPersonal = folder.isPersonal if folder else True
    292         subfolder = self.subfolders.get(subfolderID, None)
    293         folderNames = []
    294         if folder:
    295             folderNames.append(folder.folderName)
    296         if subfolder:
    297             folderNames.append(subfolder.subfolderName)
    298         return (folderNames, isPersonal)
    299 
    300     def OnSharedBookmarksFolderDeleted(self, folderID):
    301         if folderID not in self.foldersNew:
    302             self.LogInfo('Tried to remove folder, which is not a known folder: ', folderID)
    303             return
    304         self.foldersNew.pop(folderID, None)
    305         self.RemoveFolderContentFromCache([folderID])
    306         self.RefreshUIOnUpdate()
    307 
    308     def OnSharedBookmarksFoldersAccessLost(self, folderIDs):
    309         for folderID in folderIDs:
    310             if folderID not in self.foldersNew:
    311                 self.LogInfo('Tried to remove folder, which is not a known folder: ', folderID)
    312                 continue
    313             self.foldersNew.pop(folderID, None)
    314 
    315         self.RemoveFolderContentFromCache(folderIDs)
    316         self.RefreshUIOnUpdate()
    317 
    318     def OnSharedBookmarksFoldersAccessChanged(self, accessChanged):
    319         for folderID, accessLevel in accessChanged.iteritems():
    320             if folderID in self.foldersNew:
    321                 self.foldersNew[folderID].accessLevel = accessLevel
    322 
    323     def AddToKnownFolders(self, folderID, isActive):
    324         maxKnownFolders = appConst.MAX_KNOWN_BOOKMARK_FOLDERS
    325         if folderID in self.foldersNew:
    326             self.LogWarn('Tried to add a folder, which has been added already: ', folderID)
    327             return
    328         if len(self.foldersNew) >= maxKnownFolders:
    329             raise UserError('TooManyKnownFolders', {'maxKnownFolders': maxKnownFolders})
    330         folder, bookmarks, subfolders = self.accessGroupBookmarkMgr.AddToKnownFolders(folderID, isActive)
    331         self.foldersNew[folderID] = folder
    332         if folder.isActive:
    333             for eachBookmark in bookmarks:
    334                 self.bookmarkCache[eachBookmark.bookmarkID] = eachBookmark
    335                 self.bookmarkIDsBySolarystemID[eachBookmark.locationID].add(eachBookmark.bookmarkID)
    336 
    337             self.subfolders.update({subfolder.subfolderID:subfolder for subfolder in subfolders})
    338             self.CheckBookmarksForDelayedUpdate(bookmarks)
    339             if len(bookmarks) >= MAX_BOOKMARKS_SENT_TO_CLIENT:
    340                 self.onlyPartiallyLoaded = True
    341         if isActive and not folder.isActive:
    342             prompt_player_fsd_dialog('CustomNotify', {'notify': GetByLabel('UI/AclBookmarks/FolderAddedAsInactiveFolder')})
    343         self.RefreshUIOnUpdate()
    344 
    345     @telemetry.ZONE_METHOD
    346     def GetActiveBookmarksFiltered(self, solarSystemID = None, usePersonalFilter = False, personal = True):
    347         filteredFolderIDs = set()
    348         if usePersonalFilter:
    349             personalFolderIDs, sharedFolderIDs = self.GetPersonalAndSharedFolders()
    350             if personal:
    351                 filteredFolderIDs = personalFolderIDs
    352             else:
    353                 filteredFolderIDs = sharedFolderIDs
    354         now = blue.os.GetWallclockTime()
    355         filteredBookmarks = {}
    356         for bmID, bm in self.GetBookmarks().iteritems():
    357             if solarSystemID and bm.locationID != solarSystemID:
    358                 continue
    359             if usePersonalFilter and bm.folderID not in filteredFolderIDs:
    360                 continue
    361             if IsBookmarkAvailable(bm.creatorID, bm.created, now):
    362                 filteredBookmarks[bmID] = bm
    363 
    364         return filteredBookmarks
    365 
    366     @telemetry.ZONE_METHOD
    367     def GetFilteredFoldersAndSubfolders(self, forcedFolderID = None):
    368         filteredFolders = []
    369         filteredFolderIDs = []
    370         for folder in self.foldersNew.itervalues():
    371             if folder.accessLevel == appConst.ACCESS_VIEW:
    372                 continue
    373             if not folder.isActive:
    374                 continue
    375             bmCount = sum((1 for bm in self.bookmarkCache.itervalues() if bm.folderID == folder.folderID))
    376             maxBookmarks = appConst.MAX_BOOKMARKS_IN_PERSONAL_FOLDER if folder.isPersonal else appConst.MAX_BOOKMARKS_IN_SHARED_FOLDER
    377             if bmCount >= maxBookmarks and (not forcedFolderID or forcedFolderID != folder.folderID):
    378                 continue
    379             filteredFolders.append(folder)
    380             filteredFolderIDs.append(folder.folderID)
    381 
    382         filteredSubfolders = [ subfolder for subfolder in self.subfolders.itervalues() if subfolder.folderID in filteredFolderIDs ]
    383         return (filteredFolders, filteredSubfolders)
    384 
    385     def GetBookmarksFoldersAndSubfolders(self):
    386         return (self.GetBookmarks(), self.foldersNew, self.subfolders)
    387 
    388     def GetBookmarksBySubfolderByFolder(self):
    389         bookmarksBySubfolderByFolder = defaultdict(lambda : defaultdict(set))
    390         for bookmark in self.GetBookmarks().itervalues():
    391             bookmarksBySubfolderByFolder[bookmark.folderID][bookmark.subfolderID].add(bookmark)
    392 
    393         return bookmarksBySubfolderByFolder
    394 
    395     def CreateBookmarkFolder(self, isPersonal, folderName, description, adminGroupID, manageGroupID, useGroupID, viewGroupID):
    396         folder = self.accessGroupBookmarkMgr.AddFolder(isPersonal, folderName, description, adminGroupID, manageGroupID, useGroupID, viewGroupID)
    397         if folder:
    398             self.foldersNew[folder.folderID] = folder
    399             self.RefreshUIOnUpdate()
    400         return folder
    401 
    402     def CreateDefaultFolder(self):
    403         name = GetByLabel('UI/AclBookmarks/DefaultFolderName')
    404         self.CreateBookmarkFolder(True, name, '', None, None, None, None)
    405 
    406     @telemetry.ZONE_METHOD
    407     def UpdateBookmarkFolder(self, folderID, folderName, description, adminGroupID, manageGroupID, useGroupID, viewGroupID):
    408         if folderID not in self.foldersNew:
    409             raise UserError('BookmarkFolderNoLongerThere')
    410         accessLevel = self.accessGroupBookmarkMgr.UpdateFolder(folderID, folderName, description, adminGroupID, manageGroupID, useGroupID, viewGroupID)
    411         if accessLevel == appConst.ACCESS_NONE:
    412             self.foldersNew.pop(folderID, None)
    413             self.RemoveFolderContentFromCache([folderID])
    414         else:
    415             self.foldersNew[folderID].update({'folderName': folderName,
    416              'description': description,
    417              'adminGroupID': adminGroupID,
    418              'manageGroupID': manageGroupID,
    419              'useGroupID': useGroupID,
    420              'viewGroupID': viewGroupID,
    421              'accessLevel': accessLevel})
    422         self.RefreshUIOnUpdate()
    423 
    424     def DeleteBookmarkFolder(self, folderID):
    425         self.accessGroupBookmarkMgr.DeleteFolder(folderID)
    426         self.foldersNew.pop(folderID, None)
    427         self.RemoveFolderContentFromCache([folderID])
    428         self.RefreshUIOnUpdate()
    429 
    430     def RemoveFromKnownFolders(self, folderID):
    431         self.accessGroupBookmarkMgr.RemoveFromKnownFolders(folderID)
    432         self.foldersNew.pop(folderID, None)
    433         self.RemoveFolderContentFromCache([folderID])
    434         self.RefreshUIOnUpdate()
    435 
    436     def RemoveFolderContentFromCache(self, folderIDs):
    437         for bookmarkID, bookmark in self.bookmarkCache.items():
    438             if bookmark.folderID in folderIDs:
    439                 del self.bookmarkCache[bookmarkID]
    440                 self.bookmarkIDsBySolarystemID[bookmark.locationID].discard(bookmarkID)
    441 
    442         for subfolderID, subfolder in self.subfolders.items():
    443             if subfolder.folderID in folderIDs:
    444                 del self.subfolders[subfolderID]
    445 
    446     @telemetry.ZONE_METHOD
    447     def UpdateFolderActiveState(self, folderID, isActive):
    448         if isActive:
    449             self.ValidateActiveFolderCount(folderID)
    450         try:
    451             folder, bookmarks, subfolders = self.accessGroupBookmarkMgr.UpdateKnownFolderState(folderID, isActive)
    452         except UserError as e:
    453             if e.msg in ('BookmarkFolderNoLongerThere', 'FolderAccessDenied'):
    454                 self.foldersNew.pop(folderID, None)
    455                 self.RefreshUIOnUpdate()
    456             raise
    457 
    458         self.foldersNew[folderID] = folder
    459         if isActive:
    460             for eachBookmark in bookmarks:
    461                 self.bookmarkCache[eachBookmark.bookmarkID] = eachBookmark
    462                 self.bookmarkIDsBySolarystemID[eachBookmark.locationID].add(eachBookmark.bookmarkID)
    463 
    464             self.subfolders.update({subfolder.subfolderID:subfolder for subfolder in subfolders})
    465             self.CheckBookmarksForDelayedUpdate(bookmarks)
    466             if len(bookmarks) >= MAX_BOOKMARKS_SENT_TO_CLIENT:
    467                 self.onlyPartiallyLoaded = True
    468         else:
    469             self.RemoveFolderContentFromCache([folderID])
    470         self.RefreshUIOnUpdate()
    471 
    472     def ValidateActiveFolderCount(self, folderID):
    473         folder = self.foldersNew[folderID]
    474         if folder.isPersonal:
    475             maxActiveFolders = appConst.MAX_ACTIVE_PERSONAL_BOOKMARK_FOLDERS
    476             activeFolder = sum((1 for f in self.foldersNew.itervalues() if f.isPersonal and f.isActive))
    477         else:
    478             maxActiveFolders = appConst.MAX_ACTIVE_SHARED_BOOKMARK_FOLDERS
    479             activeFolder = sum((1 for f in self.foldersNew.itervalues() if not f.isPersonal and f.isActive))
    480         if activeFolder >= maxActiveFolders:
    481             raise UserError('TooManyActiveFolders', {'maxPersonalFolders': appConst.MAX_ACTIVE_PERSONAL_BOOKMARK_FOLDERS,
    482              'maxSharedFolders': appConst.MAX_ACTIVE_SHARED_BOOKMARK_FOLDERS})
    483 
    484     def GetBookmarkFolder(self, folderID, refresh = False):
    485         if refresh:
    486             folder = self.accessGroupBookmarkMgr.GetFolderInfo(folderID)
    487             folder.isActive = self.foldersNew[folderID].isActive
    488             self.foldersNew[folderID] = folder
    489         return self.foldersNew[folderID]
    490 
    491     def GetFolderInfo(self, folderID):
    492         if folderID in self.foldersNew:
    493             return self.foldersNew[folderID]
    494         else:
    495             return self.accessGroupBookmarkMgr.GetFolderInfo(folderID)
    496 
    497     def GetFolders(self):
    498         return self.foldersNew
    499 
    500     def GetFilteredFolders(self):
    501         filteredFolders = []
    502         for folder in self.foldersNew.itervalues():
    503             if folder.accessLevel == appConst.ACCESS_VIEW:
    504                 continue
    505             if not folder.isActive:
    506                 continue
    507             filteredFolders.append(folder)
    508 
    509         return filteredFolders
    510 
    511     def ShowBookmarkFolderInfo(self, folderID):
    512         alreadyKnown = folderID in self.foldersNew
    513         try:
    514             folder = self.GetFolderInfo(folderID)
    515         except UserError as e:
    516             if e.msg == 'FolderAccessDenied':
    517                 prompt_player_fsd_dialog('CustomInfo', {'info': GetByLabel('UI/AclBookmarks/NoAccessToBookmarkFolder')})
    518                 return
    519             raise
    520 
    521         wnd = LinkedBookmarkFolderWindow.GetIfOpen()
    522         if wnd:
    523             if wnd.folder.folderID == folderID:
    524                 return wnd.Maximize()
    525             wnd.Close()
    526         LinkedBookmarkFolderWindow.Open(folder=folder, alreadyKnown=alreadyKnown)
    527 
    528     def CreateSubfolder(self, folderID, subfolderName):
    529         subfolder = self.accessGroupBookmarkMgr.CreateSubfolder(folderID, subfolderName)
    530         if subfolder:
    531             self.subfolders[subfolder.subfolderID] = subfolder
    532             self.RefreshUIOnUpdate()
    533 
    534     def UpdateSubfolder(self, folderID, subfolderID, subfolderName):
    535         if self.accessGroupBookmarkMgr.UpdateSubfolder(folderID, subfolderID, subfolderName):
    536             self.subfolders[subfolderID].subfolderName = subfolderName
    537             self.RefreshUIOnUpdate()
    538         else:
    539             raise UserError('BookmarkSubfolderNoLongerThere')
    540 
    541     def DeleteSubfolder(self, subfolderID):
    542         if subfolderID not in self.subfolders:
    543             raise UserError('BookmarkSubfolderNoLongerThere')
    544         subfolder = self.subfolders[subfolderID]
    545         folder = self.foldersNew[subfolder.folderID]
    546         if folder.accessLevel == appConst.ACCESS_USE:
    547             for bookmark in self.bookmarkCache.itervalues():
    548                 if bookmark.subfolderID == subfolderID and bookmark.creatorID != session.charid:
    549                     raise UserError('CouldNotDeleteBookmarksInSubfolder')
    550 
    551         deletedBookmarkIDs = self.accessGroupBookmarkMgr.DeleteSubfolder(subfolder.folderID, subfolderID)
    552         self.subfolders.pop(subfolderID, None)
    553         for bookmarkID in deletedBookmarkIDs:
    554             bm = self.bookmarkCache.pop(bookmarkID, None)
    555             if bm:
    556                 self.bookmarkIDsBySolarystemID[bm.locationID].discard(bm.bookmarkID)
    557 
    558         self.RefreshUIOnUpdate()
    559 
    560     def GetSubfolder(self, subfolderID):
    561         return self.subfolders[subfolderID]
    562 
    563     def GetSubfolders(self):
    564         return self.subfolders
    565 
    566     def GetSubfoldersInFolder(self, folderID):
    567         return {subfolder.subfolderID:subfolder for subfolder in self.subfolders.itervalues() if subfolder.folderID == folderID}
    568 
    569     @telemetry.ZONE_METHOD
    570     def ACLBookmarkLocation(self, itemID, folderID, name, comment, itemTypeID, expiry, subfolderID = None):
    571         categoryID = GetCategoryID(itemTypeID)
    572         if idCheckers.IsRegion(itemID) or idCheckers.IsConstellation(itemID) or idCheckers.IsSolarSystem(itemID) or idCheckers.IsStation(itemID) or idCheckers.IsStructure(categoryID):
    573             bookmarkID, itemID, typeID, x, y, z, locationID, expiryDate = self.accessGroupBookmarkMgr.BookmarkStaticLocation(itemID, folderID, name, comment, expiry, subfolderID=subfolderID)
    574         else:
    575             bp = sm.StartService('michelle').GetRemotePark()
    576             if not bp:
    577                 raise UserError('BkmMustBeInFlight')
    578             bookmarkID, itemID, typeID, x, y, z, locationID, expiryDate = bp.BookmarkLocation(itemID, folderID, name, comment, expiry, subfolderID=subfolderID)
    579         if bookmarkID:
    580             self.NewBookmarkCreated(bookmarkID, folderID, itemID, typeID, name, comment, expiryDate, locationID, x, y, z, subfolderID, itemTypeID)
    581 
    582     @telemetry.ZONE_METHOD
    583     def ACLBookmarkScanResult(self, locationID, name, comment, resultID, folderID, expiry, subfolderID = None):
    584         itemID, typeID = sm.GetService('sensorSuite').GetPositionalSiteItemIDFromTargetID(resultID)
    585         if itemID is not None:
    586             self.ACLBookmarkLocation(itemID, folderID, name, comment, typeID, expiry, subfolderID=subfolderID)
    587             return
    588         bookmarkID, itemID, typeID, x, y, z, locationID, expiryDate = sm.StartService('michelle').GetRemotePark().BookmarkScanResult(locationID, name, comment, resultID, folderID, expiry, subfolderID=subfolderID)
    589         if bookmarkID:
    590             self.NewBookmarkCreated(bookmarkID, folderID, None, typeID, name, comment, expiryDate, locationID, x, y, z, subfolderID, None)
    591 
    592     def NewBookmarkCreated(self, bookmarkID, folderID, itemID, typeID, name, comment, expiryDate, locationID, x, y, z, subfolderID, itemTypeID):
    593         bm = utillib.KeyVal(bookmarkID=bookmarkID, folderID=folderID, itemID=itemID, typeID=typeID, flag=None, memo=name, created=blue.os.GetWallclockTimeNow(), expiry=expiryDate, x=x, y=y, z=z, locationID=locationID, note=comment, subfolderID=subfolderID, creatorID=session.charid)
    594         self.bookmarkCache[bookmarkID] = bm
    595         self.bookmarkIDsBySolarystemID[bm.locationID].add(bm.bookmarkID)
    596         self.RefreshUIOnUpdate()
    597         sm.ScatterEvent('OnBookmarkCreated', bookmarkID, comment, itemTypeID)
    598 
    599     def UpdateACLBookmark(self, bookmarkID, folderID, name, note, subfolderID, newFolderID, expiryCancel):
    600         if bookmarkID not in self.bookmarkCache:
    601             raise UserError('BookmarkNotAvailable')
    602         bookmark = self.bookmarkCache[bookmarkID]
    603         self.accessGroupBookmarkMgr.UpdateBookmark(bookmarkID, folderID, name, note, subfolderID, newFolderID, expiryCancel)
    604         bookmark.folderID = newFolderID
    605         bookmark.memo = name
    606         bookmark.note = note
    607         bookmark.subfolderID = subfolderID
    608         if expiryCancel:
    609             bookmark.expiry = None
    610         self.RefreshUIOnUpdate()
    611 
    612     def DeleteBookmarks(self, bookmarkIDs):
    613         bookmarksByFolder = defaultdict(set)
    614         for bookmarkID in bookmarkIDs:
    615             bookmark = self.bookmarkCache[bookmarkID]
    616             bookmarksByFolder[bookmark.folderID].add(bookmarkID)
    617 
    618         deletedBookmarks = []
    619         for folderID, bookmarks in bookmarksByFolder.iteritems():
    620             deleted = self.accessGroupBookmarkMgr.DeleteBookmarks(folderID, bookmarks)
    621             deletedBookmarks.extend(deleted)
    622 
    623         if not deletedBookmarks:
    624             raise UserError('CouldNotDeleteBookmark')
    625         for bookmarkID in deletedBookmarks:
    626             bm = self.bookmarkCache.pop(bookmarkID, None)
    627             if bm:
    628                 self.bookmarkIDsBySolarystemID[bm.locationID].discard(bookmarkID)
    629 
    630         self.RefreshUIOnUpdate()
    631 
    632     @telemetry.ZONE_METHOD
    633     def MoveOrCopyBookmarks(self, folderID, subfolderID, bookmarkIDs, forceCopy):
    634         bookmarksToMoveBySourceFolder = defaultdict(set)
    635         bookmarksToCopyBySourceFolder = defaultdict(set)
    636         for bookmarkID in bookmarkIDs:
    637             bookmark = self.bookmarkCache[bookmarkID]
    638             if bookmark.subfolderID == subfolderID and bookmark.folderID == folderID:
    639                 continue
    640             if forceCopy or self.foldersNew[bookmark.folderID].accessLevel not in (appConst.ACCESS_PERSONAL, appConst.ACCESS_MANAGE, appConst.ACCESS_ADMIN):
    641                 bookmarksToCopyBySourceFolder[bookmark.folderID].add(bookmarkID)
    642             else:
    643                 bookmarksToMoveBySourceFolder[bookmark.folderID].add(bookmarkID)
    644 
    645         for oldFolderID, bookmarkIDs in bookmarksToMoveBySourceFolder.iteritems():
    646             rows, message = self.accessGroupBookmarkMgr.MoveBookmarksToFolderAndSubfolder(oldFolderID, folderID, subfolderID, bookmarkIDs)
    647             if message is not None:
    648                 uthread.new(prompt_player_fsd_dialog, *message)
    649             for row in rows:
    650                 try:
    651                     self.bookmarkCache[row.bookmarkID].folderID = row.folderID
    652                     self.bookmarkCache[row.bookmarkID].subfolderID = row.subfolderID
    653                 except KeyError:
    654                     self.LogInfo('Bookmark not found in cache after moving it:', row.bookmarkID)
    655 
    656         for oldFolderID, bookmarkIDs in bookmarksToCopyBySourceFolder.iteritems():
    657             newBookmarks, message = self.accessGroupBookmarkMgr.CopyBookmarksToFolderAndSubfolder(oldFolderID, folderID, subfolderID, bookmarkIDs)
    658             if message is not None:
    659                 uthread.new(prompt_player_fsd_dialog, *message)
    660             for eachBookmark in newBookmarks.itervalues():
    661                 self.bookmarkIDsBySolarystemID[eachBookmark.locationID].add(eachBookmark.bookmarkID)
    662 
    663             self.bookmarkCache.update(newBookmarks)
    664 
    665         self.RefreshUIOnUpdate()
    666 
    667     @Memoize(2)
    668     def SearchFoldersWithAdminAccess(self):
    669         result = self.accessGroupBookmarkMgr.SearchFoldersWithAdminAccess()
    670         return result
    671 
    672     def GetBookmarkMenuForNavigation(self, includeTopLevel = True):
    673         m = []
    674         bookmarks, folders, subfolders = self.GetBookmarksFoldersAndSubfolders()
    675         bookmarkMenu = GetACLBookmarkMenuForSystem(bookmarks, folders, subfolders, includeTopLevel)
    676         if bookmarkMenu:
    677             m += bookmarkMenu
    678         return m