swh:1:snp:7ce5f1105410d5ee1ad6abfdc873986c25b579e5
Raw File
Tip revision: 3b02c63aac917d8918752b6446771f177a7bbc3b authored by Dirk Roorda on 06 July 2018, 08:04:11 UTC
a little bit of refactoring; docs
Tip revision: 3b02c63
start.py
import sys
import os
from platform import system

import psutil
import webbrowser
from time import sleep
from subprocess import PIPE, Popen

from tf.server.common import getParam, getDebug, getConfig
from tf.server.data import TF_DONE

HELP = '''
USAGE

text-fabric --help

text-fabric datasource
text-fabric -d datasource

text-fabric -k
text-fabric -k datasource

EFFECT

If a datasource is given and the -k flag is not passed,
a TF data server for that data source is started.
When the data server is ready, a webserver is started
serving a local website that exposes the data through
a query interface.
It will open in the default browser.

-d  Debug mode. For developers of Text-Fabric itself.
    The webserver reloads when its code changes.
    The webserver is a bottle instance, started with reload=True.

CLEAN UP

If you press Ctrl-C the webserver is stopped, and after that the data server
as well.
Normally, you do not have to do any clean up.
But if the termination is done in an irregular way, you may end up with
stray processes.

-k  Kill mode. If a data source is given, the data server and webserver for that
    data source are killed.
    Without a data source, all text-fabric server processes are killed.
'''


def filterProcess(proc):
  procName = proc.info['name']
  commandName = '' if procName is None else procName.lower()
  # commandName = proc.info['name'].lower()

  found = False
  kind = None
  dataSource = None

  trigger = 'python'
  if commandName.endswith(trigger) or commandName.endswith(f'{trigger}.exe'):
    good = False
    parts = proc.cmdline()
    for part in parts:
      part = part.lower()
      if part == '-d':
        continue
      trigger = 'text-fabric'
      if part.endswith(trigger) or part.endswith(f'{trigger}.exe'):
        kind = 'tf'
        continue
      if part == 'tf.server.service':
        kind = 'data'
        good = True
        continue
      if part == 'tf.server.web':
        kind = 'web'
        good = True
        continue
      if part.endswith('web.py'):
        kind = 'web'
        good = True
        continue
      if kind in {'data', 'web', 'tf'}:
        if kind == 'tf' and part == '-k':
          break
        dataSource = part
        good = True
        break
    if good:
      found = True
  if found:
    return (kind, dataSource)
  return False


def killProcesses(dataSource, kill=False):
  tfProcs = {}
  for proc in psutil.process_iter(attrs=['pid', 'name']):
    test = filterProcess(proc)
    if test:
      (kind, ds) = test
      tfProcs.setdefault(ds, {}).setdefault(kind, []).append(proc.info['pid'])

  item = 'killed' if kill else 'terminated'
  myself = os.getpid()
  for (ds, kinds) in tfProcs.items():
    if dataSource is None or ds == dataSource:
      for kind in ('web', 'data', 'tf'):
        pids = kinds.get(kind, [])
        for pid in pids:
          if pid == myself:
            continue
          try:
            proc = psutil.Process(pid=pid)
            if kill:
              proc.kill()
            else:
              proc.terminate()
            print(f'Process {kind} server for {ds}: {item}')
          except psutil.NoSuchProcess:
            print(f'Process {kind} server for {ds}: already {item}')


def getKill():
  for arg in sys.argv[1:]:
    if arg == '-k':
      return True
  return False


def main():
  if len(sys.argv) >= 2 and sys.argv[1] in {'--help', '-help', '-h', '?', '-?'}:
    print(HELP)

  isWin = system().lower().startswith('win')

  kill = getKill()

  if kill:
    dataSource = getParam(interactive=False)
    if dataSource is False:
      return
    killProcesses(dataSource)
    return

  dataSource = getParam(interactive=True)

  ddataSource = ('-d', dataSource) if getDebug() else (dataSource,)
  if dataSource is not None:
    config = getConfig(dataSource)
    pService = None
    pWeb = None
    if config is not None:
      print(f'Cleaning up remnant processes, if any ...')
      killProcesses(dataSource, kill=True)
      pythonExe = 'python' if isWin else 'python3'
      try:

        pService = Popen(
            [pythonExe, '-m', 'tf.server.service', dataSource],
            stdout=PIPE, encoding='utf-8',
        )

        print(f'Loading data for {dataSource}. Please wait ...')
        with pService.stdout as ph:
          for line in ph:
            print(line)
            if line.rstrip() == TF_DONE:
              break
        sleep(1)

        print(f'Opening {dataSource} in browser')
        pWeb = Popen([pythonExe, '-m', 'tf.server.web', *ddataSource])

        sleep(1)
        webbrowser.open(
            f'{config.protocol}{config.host}:{config.webport}',
            new=2,
            autoraise=True,
        )
        if pWeb:
          pWeb.wait()
      except KeyboardInterrupt:
        if pWeb:
          pWeb.terminate()
          print('Web server has stopped')
        if pService:
          pService.terminate()
          print('TF service has stopped')


if __name__ == "__main__":
  main()
back to top