#! /usr/bin/python3 # -*- coding: UTF-8 -*- """Make the fillpdf program available on this computer.""" import os import os.path import argparse import shutil import sys import subprocess # for cloning from constants import (CONFIGFILE, CONFIG_INSTALLDIR, CONFIG_FORMDIR, CONFIG_SEPARATOR) FORMS_DIRNAME = "TU-Dokumente" FORMS_URL = ("https://gitlab.math.tu-dresden.de/s1140568/" + FORMS_DIRNAME + ".git") def getPath(): """Get list of directories that are in the PATH environment variable. Returns: List of directories as strings. If loading path variable did not work, this list is empty. """ return os.environ.get('PATH', "").split(':') # "" is the default value def checkLinkAccess(dir): """Tell if I can create a link there. Try to create a link. If there was no error and indeed there is a link, remove the link and return True. Otherwise return False. """ # find a filename that is not used yet: linkloc = os.path.join(dir, "z") while os.path.lexists(linkloc): linkloc = linkloc + "z" if len(linkloc) > len(dir) + 100: print("Found files with names with 1 to 100 z's in " + "directory " + dir + " Probably error.") return False try: # try to create a link to the arbitrary position . os.symlink(os.curdir, linkloc) except FileNotFoundError as fnfe: # the directory dir does not exist return False except PermissionError as pe: # a cannot create a link there return False else: try: os.remove(linkloc) except FileNotFoundError as fnfe: print("In order to check if I have write access to the " + "directory " + dir + " I created a link " + linkloc + " there." + " I cannot remove it so do it yourself.") except PermissionError as pe: print("In order to check if I have write access to the " + "directory " + dir + " I created a link " + linkloc + " there." + " I cannot remove it so do it yourself.") return True def defaultPath(): """Choose a suitable default directory for installing. Try to access the path variable. If possible use first entry where I can create a symlink and is of the form: /usr/local/bin ~/bin ~/.config/bin If not possible use first where I can create a link. """ pathdirs = getPath() # check if sudo/ root: rootdir = os.path.join("/", "usr", "local", "bin") if rootdir in pathdirs and checkLinkAccess(rootdir): return rootpath for userdir in [os.path.join(os.environ.get("HOME", ""), ".local", "bin"), os.path.join(os.environ.get("HOME", ""), "bin")]: if userdir in pathdirs: if checkLinkAccess(userdir): return userdir try: os.mkdirs(userdir) except OSError: # already exists # some other error, do not use this # print("Debug: userdir link creation failed") pass else: if checkLinkAccess(userdir): return userdir else: # something did not work print("Debug: userdir link creation check failed " + "after creating dir") else: print("Debug: " + userdir + " is not in the path. It should be" + " on a typical system.") # print("Debug: non of the typical directories worked. " + # "Use the first possible.") for directory in pathdirs: if checkLinkAccess(directory): return directory print("""No suitable path for installing was found. Do one of the following: • execute this script as sudo/ root • add a directory where you have write access in the path • specify a directory via the --path option and add this directory""" + " to the path.") raise FileNotFoundError("No directory for installation found.") def createProgramSymlinks(where): """Create symlinks to some of scripts in this project. Arguments: where: the directory where to create the symlinks. Raises: multiple possibilities of error """ here = os.path.dirname(__file__) origloc = 0 linkloc = 1 # dict { script : (where it is, where the link should be) } # print("Debug: here: ", here) scripts = {script : (os.path.abspath( os.path.join(here, script + ".py")), os.path.join(where, script)) for script in ["fillform", "reise"]} # print("Debug: scripts: ", scripts) todoscripts = [] # check if the scripts exist, i.e. I am at the right place for script in scripts: # F_OK = does file exist? if not os.access(scripts[script][origloc], os.F_OK): raise FileNotFoundError("I cannot find the script " + script + " in " + scripts[script][origloc] + ".") if os.access(scripts[script][linkloc], os.F_OK, follow_symlinks=False): if os.path.islink(scripts[script][linkloc]): if not os.path.exists(scripts[script][linkloc]): # os.path.exists says False on broken symlinks raise FileExistsError("The symlink " + scripts[script][linkloc] + " already exists." + " It is a broken symlink." + " You may like to delete it " + "and start " + " this install script again.") elif os.path.samefile(os.readlink(scripts[script][linkloc]), scripts[script][origloc]): # print("Debug: link " + script + # " already links correctly.") # nothing to do: link already links correctly pass else: raise FileExistsError("The symlink " + scripts[script][linkloc] + " already exists and points " + "somewhere else.") else: raise FileExistsError("The file " + scripts[script][linkloc] + " already exists.") else: todoscripts.append(script) for script in todoscripts: try: os.symlink(scripts[script][origloc], scripts[script][linkloc]) except NotImplementedError: print("The python doc says this happens only on Windows" + " previous to Windows 6.0 (Vista). You should really " + "know better than using Windows.") raise except OSError as e: print("Apparently you do not have the permissions to create" + " a symlink in the directory " + where + ". Call this install script with --path " + " with a where you have the permissions and" + " that is in the path.") raise def writeConfig(installdir, formdir): """Write the config file. Try to write to the file $HOME/.config/.fillpdfrc. arguments: installdir: where the programs are installed formdir: where the form files are write one line per info, no line if info is None """ try: with open(CONFIGFILE, "w+") as configfile: # w = write # + = create if not exist if installdir is not None: print(CONFIG_INSTALLDIR + CONFIG_SEPARATOR + os.path.abspath(installdir), file=configfile) else: print("Debug: No installdir in config saved.") if formdir is not None: print(CONFIG_FORMDIR + CONFIG_SEPARATOR + os.path.abspath(formdir), file=configfile) else: print("Debug: No directory for the form files " + "in config saved.") except: print("Debug: Error at writing configfile:") raise else: pass # print("Debug: config file written") def parse(): """Create the argument parser (including the help) and parse the arguments. Options/ Arguments: --path directory --formdir directory """ parser = argparse.ArgumentParser( description=("Install fillpdf on this computer " + "and show help how to start using it.")) # formatter_class=argparse.RawDescriptionHelpFormatter, # epilog=("To start go to the new directory and run " + # "'./fillform Antrag.pdf'")) parser.add_argument("--path", default=None, help=("The directory where the scripts should " + "be installed. Should be in the path. " + "If not specified, you will be asked " + "where to install.")) parser.add_argument("--formdir", default=None, help=("The directory where the config (form) files " + "for the pdf forms are. " + "If not specified, look next to the " + "parent directory of this script " + "and if it is not there pull from gitlab.")) return vars(parser.parse_args()) def defaultConfig(): """Check if ../TU-Dokumente exists. If not, try to clone TU-Dokumente here and return this new directory. """ here = os.path.dirname(__file__) nextTo = os.path.join(here, "..", FORMS_DIRNAME) if os.path.exists(nextTo): return nextTo nextTo = os.path.join(here, "..", FORMS_DIRNAME) if os.path.exists(nextTo): # already in current dir return nextTo try: clone = subprocess.Popen([ "git", "clone", FORMS_URL], stderr=subprocess.PIPE, stdout=subprocess.PIPE) try: errorcode = clone.wait(timeout=30) except TimeoutExpired: print("Cloning the form files from " + FORMS_URL + " took too long.") else: if errorcode != 0: print("Debug: cloning the form files from " + FORMS_URL + " did not work with errorcode " + str(errorcode) + ".") else: # return newly cloned and created directory return os.path.normpath(os.path.join(here, FORMS_DIRNAME)) except Exception as e: print("Trying to clone the form file repo failed in the" + " following way:", e) # no sensible default found return None if __name__ == "__main__": args = parse() installdir = args["path"] if installdir is None: installdir = defaultPath() formdir = args["formdir"] if formdir is None: formdir = defaultConfig() # getting None again is OK print("Chose directory " + installdir + " for installing. ", "(Installing is just creating symlinks to the scripts.)") createProgramSymlinks(installdir) writeConfig(installdir, formdir) print("Everything successfully installed. You should be able to " + "start fillform and reise now.")