command.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. """
  2. BitBake 'Command' module
  3. Provide an interface to interact with the bitbake server through 'commands'
  4. """
  5. # Copyright (C) 2006-2007 Richard Purdie
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License version 2 as
  9. # published by the Free Software Foundation.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License along
  17. # with this program; if not, write to the Free Software Foundation, Inc.,
  18. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  19. """
  20. The bitbake server takes 'commands' from its UI/commandline.
  21. Commands are either synchronous or asynchronous.
  22. Async commands return data to the client in the form of events.
  23. Sync commands must only return data through the function return value
  24. and must not trigger events, directly or indirectly.
  25. Commands are queued in a CommandQueue
  26. """
  27. from collections import OrderedDict, defaultdict
  28. import bb.event
  29. import bb.cooker
  30. import bb.remotedata
  31. class DataStoreConnectionHandle(object):
  32. def __init__(self, dsindex=0):
  33. self.dsindex = dsindex
  34. class CommandCompleted(bb.event.Event):
  35. pass
  36. class CommandExit(bb.event.Event):
  37. def __init__(self, exitcode):
  38. bb.event.Event.__init__(self)
  39. self.exitcode = int(exitcode)
  40. class CommandFailed(CommandExit):
  41. def __init__(self, message):
  42. self.error = message
  43. CommandExit.__init__(self, 1)
  44. class CommandError(Exception):
  45. pass
  46. class Command:
  47. """
  48. A queue of asynchronous commands for bitbake
  49. """
  50. def __init__(self, cooker):
  51. self.cooker = cooker
  52. self.cmds_sync = CommandsSync()
  53. self.cmds_async = CommandsAsync()
  54. self.remotedatastores = bb.remotedata.RemoteDatastores(cooker)
  55. # FIXME Add lock for this
  56. self.currentAsyncCommand = None
  57. def runCommand(self, commandline, ro_only = False):
  58. command = commandline.pop(0)
  59. if hasattr(CommandsSync, command):
  60. # Can run synchronous commands straight away
  61. command_method = getattr(self.cmds_sync, command)
  62. if ro_only:
  63. if not hasattr(command_method, 'readonly') or False == getattr(command_method, 'readonly'):
  64. return None, "Not able to execute not readonly commands in readonly mode"
  65. try:
  66. if getattr(command_method, 'needconfig', False):
  67. self.cooker.updateCacheSync()
  68. result = command_method(self, commandline)
  69. except CommandError as exc:
  70. return None, exc.args[0]
  71. except (Exception, SystemExit):
  72. import traceback
  73. return None, traceback.format_exc()
  74. else:
  75. return result, None
  76. if self.currentAsyncCommand is not None:
  77. return None, "Busy (%s in progress)" % self.currentAsyncCommand[0]
  78. if command not in CommandsAsync.__dict__:
  79. return None, "No such command"
  80. self.currentAsyncCommand = (command, commandline)
  81. self.cooker.configuration.server_register_idlecallback(self.cooker.runCommands, self.cooker)
  82. return True, None
  83. def runAsyncCommand(self):
  84. try:
  85. if self.cooker.state in (bb.cooker.state.error, bb.cooker.state.shutdown, bb.cooker.state.forceshutdown):
  86. # updateCache will trigger a shutdown of the parser
  87. # and then raise BBHandledException triggering an exit
  88. self.cooker.updateCache()
  89. return False
  90. if self.currentAsyncCommand is not None:
  91. (command, options) = self.currentAsyncCommand
  92. commandmethod = getattr(CommandsAsync, command)
  93. needcache = getattr( commandmethod, "needcache" )
  94. if needcache and self.cooker.state != bb.cooker.state.running:
  95. self.cooker.updateCache()
  96. return True
  97. else:
  98. commandmethod(self.cmds_async, self, options)
  99. return False
  100. else:
  101. return False
  102. except KeyboardInterrupt as exc:
  103. self.finishAsyncCommand("Interrupted")
  104. return False
  105. except SystemExit as exc:
  106. arg = exc.args[0]
  107. if isinstance(arg, str):
  108. self.finishAsyncCommand(arg)
  109. else:
  110. self.finishAsyncCommand("Exited with %s" % arg)
  111. return False
  112. except Exception as exc:
  113. import traceback
  114. if isinstance(exc, bb.BBHandledException):
  115. self.finishAsyncCommand("")
  116. else:
  117. self.finishAsyncCommand(traceback.format_exc())
  118. return False
  119. def finishAsyncCommand(self, msg=None, code=None):
  120. if msg or msg == "":
  121. bb.event.fire(CommandFailed(msg), self.cooker.expanded_data)
  122. elif code:
  123. bb.event.fire(CommandExit(code), self.cooker.expanded_data)
  124. else:
  125. bb.event.fire(CommandCompleted(), self.cooker.expanded_data)
  126. self.currentAsyncCommand = None
  127. self.cooker.finishcommand()
  128. class CommandsSync:
  129. """
  130. A class of synchronous commands
  131. These should run quickly so as not to hurt interactive performance.
  132. These must not influence any running synchronous command.
  133. """
  134. def stateShutdown(self, command, params):
  135. """
  136. Trigger cooker 'shutdown' mode
  137. """
  138. command.cooker.shutdown(False)
  139. def stateForceShutdown(self, command, params):
  140. """
  141. Stop the cooker
  142. """
  143. command.cooker.shutdown(True)
  144. def getAllKeysWithFlags(self, command, params):
  145. """
  146. Returns a dump of the global state. Call with
  147. variable flags to be retrieved as params.
  148. """
  149. flaglist = params[0]
  150. return command.cooker.getAllKeysWithFlags(flaglist)
  151. getAllKeysWithFlags.readonly = True
  152. def getVariable(self, command, params):
  153. """
  154. Read the value of a variable from data
  155. """
  156. varname = params[0]
  157. expand = True
  158. if len(params) > 1:
  159. expand = (params[1] == "True")
  160. return command.cooker.data.getVar(varname, expand)
  161. getVariable.readonly = True
  162. def setVariable(self, command, params):
  163. """
  164. Set the value of variable in data
  165. """
  166. varname = params[0]
  167. value = str(params[1])
  168. command.cooker.extraconfigdata[varname] = value
  169. command.cooker.data.setVar(varname, value)
  170. def getSetVariable(self, command, params):
  171. """
  172. Read the value of a variable from data and set it into the datastore
  173. which effectively expands and locks the value.
  174. """
  175. varname = params[0]
  176. result = self.getVariable(command, params)
  177. command.cooker.data.setVar(varname, result)
  178. return result
  179. def setConfig(self, command, params):
  180. """
  181. Set the value of variable in configuration
  182. """
  183. varname = params[0]
  184. value = str(params[1])
  185. setattr(command.cooker.configuration, varname, value)
  186. def enableDataTracking(self, command, params):
  187. """
  188. Enable history tracking for variables
  189. """
  190. command.cooker.enableDataTracking()
  191. def disableDataTracking(self, command, params):
  192. """
  193. Disable history tracking for variables
  194. """
  195. command.cooker.disableDataTracking()
  196. def setPrePostConfFiles(self, command, params):
  197. prefiles = params[0].split()
  198. postfiles = params[1].split()
  199. command.cooker.configuration.prefile = prefiles
  200. command.cooker.configuration.postfile = postfiles
  201. setPrePostConfFiles.needconfig = False
  202. def getCpuCount(self, command, params):
  203. """
  204. Get the CPU count on the bitbake server
  205. """
  206. return bb.utils.cpu_count()
  207. getCpuCount.readonly = True
  208. getCpuCount.needconfig = False
  209. def matchFile(self, command, params):
  210. fMatch = params[0]
  211. return command.cooker.matchFile(fMatch)
  212. matchFile.needconfig = False
  213. def generateNewImage(self, command, params):
  214. image = params[0]
  215. base_image = params[1]
  216. package_queue = params[2]
  217. timestamp = params[3]
  218. description = params[4]
  219. return command.cooker.generateNewImage(image, base_image,
  220. package_queue, timestamp, description)
  221. def ensureDir(self, command, params):
  222. directory = params[0]
  223. bb.utils.mkdirhier(directory)
  224. ensureDir.needconfig = False
  225. def setVarFile(self, command, params):
  226. """
  227. Save a variable in a file; used for saving in a configuration file
  228. """
  229. var = params[0]
  230. val = params[1]
  231. default_file = params[2]
  232. op = params[3]
  233. command.cooker.modifyConfigurationVar(var, val, default_file, op)
  234. setVarFile.needconfig = False
  235. def removeVarFile(self, command, params):
  236. """
  237. Remove a variable declaration from a file
  238. """
  239. var = params[0]
  240. command.cooker.removeConfigurationVar(var)
  241. removeVarFile.needconfig = False
  242. def createConfigFile(self, command, params):
  243. """
  244. Create an extra configuration file
  245. """
  246. name = params[0]
  247. command.cooker.createConfigFile(name)
  248. createConfigFile.needconfig = False
  249. def setEventMask(self, command, params):
  250. handlerNum = params[0]
  251. llevel = params[1]
  252. debug_domains = params[2]
  253. mask = params[3]
  254. return bb.event.set_UIHmask(handlerNum, llevel, debug_domains, mask)
  255. setEventMask.needconfig = False
  256. setEventMask.readonly = True
  257. def setFeatures(self, command, params):
  258. """
  259. Set the cooker features to include the passed list of features
  260. """
  261. features = params[0]
  262. command.cooker.setFeatures(features)
  263. setFeatures.needconfig = False
  264. # although we change the internal state of the cooker, this is transparent since
  265. # we always take and leave the cooker in state.initial
  266. setFeatures.readonly = True
  267. def updateConfig(self, command, params):
  268. options = params[0]
  269. environment = params[1]
  270. command.cooker.updateConfigOpts(options, environment)
  271. updateConfig.needconfig = False
  272. def parseConfiguration(self, command, params):
  273. """Instruct bitbake to parse its configuration
  274. NOTE: it is only necessary to call this if you aren't calling any normal action
  275. (otherwise parsing is taken care of automatically)
  276. """
  277. command.cooker.parseConfiguration()
  278. parseConfiguration.needconfig = False
  279. def getLayerPriorities(self, command, params):
  280. ret = []
  281. # regex objects cannot be marshalled by xmlrpc
  282. for collection, pattern, regex, pri in command.cooker.bbfile_config_priorities:
  283. ret.append((collection, pattern, regex.pattern, pri))
  284. return ret
  285. getLayerPriorities.readonly = True
  286. def getRecipes(self, command, params):
  287. try:
  288. mc = params[0]
  289. except IndexError:
  290. mc = ''
  291. return list(command.cooker.recipecaches[mc].pkg_pn.items())
  292. getRecipes.readonly = True
  293. def getRecipeDepends(self, command, params):
  294. try:
  295. mc = params[0]
  296. except IndexError:
  297. mc = ''
  298. return list(command.cooker.recipecaches[mc].deps.items())
  299. getRecipeDepends.readonly = True
  300. def getRecipeVersions(self, command, params):
  301. try:
  302. mc = params[0]
  303. except IndexError:
  304. mc = ''
  305. return command.cooker.recipecaches[mc].pkg_pepvpr
  306. getRecipeVersions.readonly = True
  307. def getRuntimeDepends(self, command, params):
  308. ret = []
  309. try:
  310. mc = params[0]
  311. except IndexError:
  312. mc = ''
  313. rundeps = command.cooker.recipecaches[mc].rundeps
  314. for key, value in rundeps.items():
  315. if isinstance(value, defaultdict):
  316. value = dict(value)
  317. ret.append((key, value))
  318. return ret
  319. getRuntimeDepends.readonly = True
  320. def getRuntimeRecommends(self, command, params):
  321. ret = []
  322. try:
  323. mc = params[0]
  324. except IndexError:
  325. mc = ''
  326. runrecs = command.cooker.recipecaches[mc].runrecs
  327. for key, value in runrecs.items():
  328. if isinstance(value, defaultdict):
  329. value = dict(value)
  330. ret.append((key, value))
  331. return ret
  332. getRuntimeRecommends.readonly = True
  333. def getRecipeInherits(self, command, params):
  334. try:
  335. mc = params[0]
  336. except IndexError:
  337. mc = ''
  338. return command.cooker.recipecaches[mc].inherits
  339. getRecipeInherits.readonly = True
  340. def getBbFilePriority(self, command, params):
  341. try:
  342. mc = params[0]
  343. except IndexError:
  344. mc = ''
  345. return command.cooker.recipecaches[mc].bbfile_priority
  346. getBbFilePriority.readonly = True
  347. def getDefaultPreference(self, command, params):
  348. try:
  349. mc = params[0]
  350. except IndexError:
  351. mc = ''
  352. return command.cooker.recipecaches[mc].pkg_dp
  353. getDefaultPreference.readonly = True
  354. def getSkippedRecipes(self, command, params):
  355. # Return list sorted by reverse priority order
  356. import bb.cache
  357. skipdict = OrderedDict(sorted(command.cooker.skiplist.items(),
  358. key=lambda x: (-command.cooker.collection.calc_bbfile_priority(bb.cache.virtualfn2realfn(x[0])[0]), x[0])))
  359. return list(skipdict.items())
  360. getSkippedRecipes.readonly = True
  361. def getOverlayedRecipes(self, command, params):
  362. return list(command.cooker.collection.overlayed.items())
  363. getOverlayedRecipes.readonly = True
  364. def getFileAppends(self, command, params):
  365. fn = params[0]
  366. return command.cooker.collection.get_file_appends(fn)
  367. getFileAppends.readonly = True
  368. def getAllAppends(self, command, params):
  369. return command.cooker.collection.bbappends
  370. getAllAppends.readonly = True
  371. def findProviders(self, command, params):
  372. return command.cooker.findProviders()
  373. findProviders.readonly = True
  374. def findBestProvider(self, command, params):
  375. pn = params[0]
  376. return command.cooker.findBestProvider(pn)
  377. findBestProvider.readonly = True
  378. def allProviders(self, command, params):
  379. try:
  380. mc = params[0]
  381. except IndexError:
  382. mc = ''
  383. return list(bb.providers.allProviders(command.cooker.recipecaches[mc]).items())
  384. allProviders.readonly = True
  385. def getRuntimeProviders(self, command, params):
  386. rprovide = params[0]
  387. try:
  388. mc = params[1]
  389. except IndexError:
  390. mc = ''
  391. all_p = bb.providers.getRuntimeProviders(command.cooker.recipecaches[mc], rprovide)
  392. if all_p:
  393. best = bb.providers.filterProvidersRunTime(all_p, rprovide,
  394. command.cooker.data,
  395. command.cooker.recipecaches[mc])[0][0]
  396. else:
  397. best = None
  398. return all_p, best
  399. getRuntimeProviders.readonly = True
  400. def dataStoreConnectorFindVar(self, command, params):
  401. dsindex = params[0]
  402. name = params[1]
  403. datastore = command.remotedatastores[dsindex]
  404. value = datastore._findVar(name)
  405. if value:
  406. content = value.get('_content', None)
  407. if isinstance(content, bb.data_smart.DataSmart):
  408. # Value is a datastore (e.g. BB_ORIGENV) - need to handle this carefully
  409. idx = command.remotedatastores.check_store(content, True)
  410. return {'_content': DataStoreConnectionHandle(idx), '_connector_origtype': 'DataStoreConnectionHandle'}
  411. elif isinstance(content, set):
  412. return {'_content': list(content), '_connector_origtype': 'set'}
  413. return value
  414. dataStoreConnectorFindVar.readonly = True
  415. def dataStoreConnectorGetKeys(self, command, params):
  416. dsindex = params[0]
  417. datastore = command.remotedatastores[dsindex]
  418. return list(datastore.keys())
  419. dataStoreConnectorGetKeys.readonly = True
  420. def dataStoreConnectorGetVarHistory(self, command, params):
  421. dsindex = params[0]
  422. name = params[1]
  423. datastore = command.remotedatastores[dsindex]
  424. return datastore.varhistory.variable(name)
  425. dataStoreConnectorGetVarHistory.readonly = True
  426. def dataStoreConnectorExpandPythonRef(self, command, params):
  427. config_data_dict = params[0]
  428. varname = params[1]
  429. expr = params[2]
  430. config_data = command.remotedatastores.receive_datastore(config_data_dict)
  431. varparse = bb.data_smart.VariableParse(varname, config_data)
  432. return varparse.python_sub(expr)
  433. def dataStoreConnectorRelease(self, command, params):
  434. dsindex = params[0]
  435. if dsindex <= 0:
  436. raise CommandError('dataStoreConnectorRelease: invalid index %d' % dsindex)
  437. command.remotedatastores.release(dsindex)
  438. def parseRecipeFile(self, command, params):
  439. """
  440. Parse the specified recipe file (with or without bbappends)
  441. and return a datastore object representing the environment
  442. for the recipe.
  443. """
  444. fn = params[0]
  445. appends = params[1]
  446. appendlist = params[2]
  447. if len(params) > 3:
  448. config_data_dict = params[3]
  449. config_data = command.remotedatastores.receive_datastore(config_data_dict)
  450. else:
  451. config_data = None
  452. if appends:
  453. if appendlist is not None:
  454. appendfiles = appendlist
  455. else:
  456. appendfiles = command.cooker.collection.get_file_appends(fn)
  457. else:
  458. appendfiles = []
  459. # We are calling bb.cache locally here rather than on the server,
  460. # but that's OK because it doesn't actually need anything from
  461. # the server barring the global datastore (which we have a remote
  462. # version of)
  463. if config_data:
  464. # We have to use a different function here if we're passing in a datastore
  465. # NOTE: we took a copy above, so we don't do it here again
  466. envdata = bb.cache.parse_recipe(config_data, fn, appendfiles)['']
  467. else:
  468. # Use the standard path
  469. parser = bb.cache.NoCache(command.cooker.databuilder)
  470. envdata = parser.loadDataFull(fn, appendfiles)
  471. idx = command.remotedatastores.store(envdata)
  472. return DataStoreConnectionHandle(idx)
  473. parseRecipeFile.readonly = True
  474. class CommandsAsync:
  475. """
  476. A class of asynchronous commands
  477. These functions communicate via generated events.
  478. Any function that requires metadata parsing should be here.
  479. """
  480. def buildFile(self, command, params):
  481. """
  482. Build a single specified .bb file
  483. """
  484. bfile = params[0]
  485. task = params[1]
  486. if len(params) > 2:
  487. hidewarning = params[2]
  488. else:
  489. hidewarning = False
  490. command.cooker.buildFile(bfile, task, hidewarning)
  491. buildFile.needcache = False
  492. def buildTargets(self, command, params):
  493. """
  494. Build a set of targets
  495. """
  496. pkgs_to_build = params[0]
  497. task = params[1]
  498. command.cooker.buildTargets(pkgs_to_build, task)
  499. buildTargets.needcache = True
  500. def generateDepTreeEvent(self, command, params):
  501. """
  502. Generate an event containing the dependency information
  503. """
  504. pkgs_to_build = params[0]
  505. task = params[1]
  506. command.cooker.generateDepTreeEvent(pkgs_to_build, task)
  507. command.finishAsyncCommand()
  508. generateDepTreeEvent.needcache = True
  509. def generateDotGraph(self, command, params):
  510. """
  511. Dump dependency information to disk as .dot files
  512. """
  513. pkgs_to_build = params[0]
  514. task = params[1]
  515. command.cooker.generateDotGraphFiles(pkgs_to_build, task)
  516. command.finishAsyncCommand()
  517. generateDotGraph.needcache = True
  518. def generateTargetsTree(self, command, params):
  519. """
  520. Generate a tree of buildable targets.
  521. If klass is provided ensure all recipes that inherit the class are
  522. included in the package list.
  523. If pkg_list provided use that list (plus any extras brought in by
  524. klass) rather than generating a tree for all packages.
  525. """
  526. klass = params[0]
  527. pkg_list = params[1]
  528. command.cooker.generateTargetsTree(klass, pkg_list)
  529. command.finishAsyncCommand()
  530. generateTargetsTree.needcache = True
  531. def findCoreBaseFiles(self, command, params):
  532. """
  533. Find certain files in COREBASE directory. i.e. Layers
  534. """
  535. subdir = params[0]
  536. filename = params[1]
  537. command.cooker.findCoreBaseFiles(subdir, filename)
  538. command.finishAsyncCommand()
  539. findCoreBaseFiles.needcache = False
  540. def findConfigFiles(self, command, params):
  541. """
  542. Find config files which provide appropriate values
  543. for the passed configuration variable. i.e. MACHINE
  544. """
  545. varname = params[0]
  546. command.cooker.findConfigFiles(varname)
  547. command.finishAsyncCommand()
  548. findConfigFiles.needcache = False
  549. def findFilesMatchingInDir(self, command, params):
  550. """
  551. Find implementation files matching the specified pattern
  552. in the requested subdirectory of a BBPATH
  553. """
  554. pattern = params[0]
  555. directory = params[1]
  556. command.cooker.findFilesMatchingInDir(pattern, directory)
  557. command.finishAsyncCommand()
  558. findFilesMatchingInDir.needcache = False
  559. def findConfigFilePath(self, command, params):
  560. """
  561. Find the path of the requested configuration file
  562. """
  563. configfile = params[0]
  564. command.cooker.findConfigFilePath(configfile)
  565. command.finishAsyncCommand()
  566. findConfigFilePath.needcache = False
  567. def showVersions(self, command, params):
  568. """
  569. Show the currently selected versions
  570. """
  571. command.cooker.showVersions()
  572. command.finishAsyncCommand()
  573. showVersions.needcache = True
  574. def showEnvironmentTarget(self, command, params):
  575. """
  576. Print the environment of a target recipe
  577. (needs the cache to work out which recipe to use)
  578. """
  579. pkg = params[0]
  580. command.cooker.showEnvironment(None, pkg)
  581. command.finishAsyncCommand()
  582. showEnvironmentTarget.needcache = True
  583. def showEnvironment(self, command, params):
  584. """
  585. Print the standard environment
  586. or if specified the environment for a specified recipe
  587. """
  588. bfile = params[0]
  589. command.cooker.showEnvironment(bfile)
  590. command.finishAsyncCommand()
  591. showEnvironment.needcache = False
  592. def parseFiles(self, command, params):
  593. """
  594. Parse the .bb files
  595. """
  596. command.cooker.updateCache()
  597. command.finishAsyncCommand()
  598. parseFiles.needcache = True
  599. def compareRevisions(self, command, params):
  600. """
  601. Parse the .bb files
  602. """
  603. if bb.fetch.fetcher_compare_revisions(command.cooker.data):
  604. command.finishAsyncCommand(code=1)
  605. else:
  606. command.finishAsyncCommand()
  607. compareRevisions.needcache = True
  608. def triggerEvent(self, command, params):
  609. """
  610. Trigger a certain event
  611. """
  612. event = params[0]
  613. bb.event.fire(eval(event), command.cooker.data)
  614. command.currentAsyncCommand = None
  615. triggerEvent.needcache = False
  616. def resetCooker(self, command, params):
  617. """
  618. Reset the cooker to its initial state, thus forcing a reparse for
  619. any async command that has the needcache property set to True
  620. """
  621. command.cooker.reset()
  622. command.finishAsyncCommand()
  623. resetCooker.needcache = False
  624. def clientComplete(self, command, params):
  625. """
  626. Do the right thing when the controlling client exits
  627. """
  628. command.cooker.clientComplete()
  629. command.finishAsyncCommand()
  630. clientComplete.needcache = False