Raw File
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.fabric import NAME, VERSION
from tf.server.common import getParam, getDebug, getNoweb, getDocker, getConfig, getLocalClones
from tf.server.kernel import TF_DONE, TF_ERROR

HELP = '''
USAGE

text-fabric --help
text-fabric --version

text-fabric datasource
text-fabric [-lgc] [-d] [-noweb] [-docker] datasource

text-fabric -k
text-fabric -k datasource

EFFECT

If a datasource is given and the -k flag is not passed,
a TF kernel for that data source is started.
When the TF kernel 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.

DATA LOADING

Text-Fabric looks for data in ~/text-fabric-data.
If data is not found there, it first downloads the relevant data from
github.

-lgc Look for data first in local github clones under ~/github.
  If data is not found there, get data in the normal way. 


MISCELLANEOUS

-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.

-noweb Do not start the web browser

-docker Assume you are on docker. The web server is passed 0.0.0.0 as host.


CLEAN UP

If you press Ctrl-C the webserver is stopped, and after that the TF kernel
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 TF kernel and webserver for that
    data source are killed.
    Without a data source, all web-interface related processes are killed.
'''


FLAGS = set('''
    -d
    -lgc
    -noweb
    -docker
'''.strip().split())

BANNER = f'This is {NAME} {VERSION}'


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 in FLAGS:
        continue
      trigger = 'text-fabric'
      if part.endswith(trigger) or part.endswith(f'{trigger}.exe'):
        kind = 'tf'
        continue
      if part == 'tf.server.kernel':
        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(cargs=sys.argv):
  for arg in cargs[1:]:
    if arg == '-k':
      return True
  return False


def main(cargs=sys.argv):
  print(BANNER)
  if len(cargs) >= 2 and any(arg in {'--help', '-help', '-h', '?', '-?'} for arg in cargs[1:]):
    print(HELP)
    return
  if len(cargs) >= 2 and any(arg in {'--version', '-version', '-v'} for arg in cargs[1:]):
    return

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

  kill = getKill(cargs=cargs)

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

  dataSource = getParam(cargs=cargs, interactive=True)

  noweb = getNoweb(cargs=cargs)

  ddataSource = ('-d', dataSource) if getDebug(cargs=cargs) else (dataSource,)
  ddataSource = ('-docker', *ddataSource) if getDocker(cargs=cargs) else ddataSource
  ddataSource = ('-lgc', *ddataSource) if getLocalClones(cargs=cargs) else ddataSource
  kdataSource = ('-lgc', dataSource) if getLocalClones(cargs=cargs) else (dataSource,)
  if dataSource is not None:
    config = getConfig(dataSource)
    pKernel = 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'

      pKernel = Popen(
          [pythonExe, '-m', 'tf.server.kernel', *kdataSource],
          stdout=PIPE, bufsize=1, encoding='utf-8',
      )

      print(f'Loading data for {dataSource}. Please wait ...')
      for line in pKernel.stdout:
        sys.stdout.write(line)
        if line.rstrip() == TF_ERROR:
          return
        if line.rstrip() == TF_DONE:
          break
      sleep(1)

      pWeb = Popen(
          [pythonExe, '-m', 'tf.server.web', *ddataSource],
          bufsize=0,
      )

      if not noweb:
        sleep(1)
        print(f'Opening {dataSource} in browser')
        webbrowser.open(
            f'{config.protocol}{config.host}:{config.webport}',
            new=2,
            autoraise=True,
        )

      if pWeb and pKernel:
        try:
          for line in pKernel.stdout:
            sys.stdout.write(line)
        except KeyboardInterrupt:
          print('')
          if pWeb:
            pWeb.terminate()
            print('TF webserver has stopped')
          if pKernel:
            pKernel.terminate()
            for line in pKernel.stdout:
              sys.stdout.write(line)
            print('TF kernel has stopped')


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