#!/usr/bin/env python import os, os.path, time, threading, subprocess, sys from stat import * ################################################################################ # Some configuration variables set by the main() bit: DEST = None VIDEO_DEV = None # This is the cur file. (The file that points to the most recent pic) # It also gets a lock, since several threads use this file: CURFILE = "cur.jpg" CURFILE_LOCK = threading.Lock() # These statistics are maintained by the various threads: class Stats: cam_present = False started = "" class images: count = 0 time = "" output = "" class upload: count = 0 time = "" output = "" class archive: count = 0 time = "" output = "" # Used to signal that the program is done: RUNNING = True ################################################################################ # Will create a directory if it doesn't exist: def check_dir(path): try: os.makedirs(path) except: pass # We don't care if the directory already exists. # Will link the cur file to the given argument if that argument exists: def link_if_exists(path): if not os.path.isfile(path): return CURFILE_LOCK.acquire() if os.path.isfile(CURFILE): os.unlink(CURFILE) os.symlink(path, CURFILE) CURFILE_LOCK.release() # Will check to see if a file is a character device: def is_char_dev(path): try: return S_ISCHR(os.stat(path)[ST_MODE]) except: return False # Will check to see if a file is a folder/directory: def is_folder(path): try: return S_ISDIR(os.stat(path)[ST_MODE]) except: return False # Used by the stat/output function below: TEXT_LEN = 25 def section(name): out = "=== " out += name + " " out += "="*(TEXT_LEN-len(name)) + "\n" return out # Will generate a string describing the current stats: def stats_string(): def value(name, value): leftover = TEXT_LEN - len(name) - 1 return name + ":" + " "*leftover + str(value) + "\n" out = "" if Stats.cam_present else "** CAMERA DEVICE IS MISSING **\n\n" out += section("ARCHIVE") out += value("Total folders archived", Stats.archive.count) out += value("Most recent timestamp", Stats.archive.time) out += "\n" out += section("IMAGES") out += value("Total saved", Stats.images.count) out += value("Most recent timestamp", Stats.images.time) out += "\n" out += section("PROCESS") out += value("Time started", Stats.started) out += value("Current time", timestamp()) out += "\n" out += section("UPLOADS") out += value("Total uploaded", Stats.upload.count) out += value("Most recent timestamp", Stats.upload.time) out += "\n" return out # Will dump the most recent output from all programs into a string: def command_string(): out = "" if Stats.cam_present else "** CAMERA DEVICE IS MISSING **\n\n" out += section("ARCHIVE") out += Stats.archive.output + "\n" out += section("IMAGES") out += Stats.images.output + "\n" out += section("UPLOADS") out += Stats.upload.output + "\n" return out # Will generate a timestamp: def timestamp(): return time.strftime("%c %Z") # Will run a program and return (RETVAL, STDOUT+STDERR): def run_prog(cmd): try: p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out = p.communicate() return (p.returncode, out[0]) except: return (255, "-- Error executing --") ################################################################################ class ImageCapture(threading.Thread): def run(self): # Get a filename based on the time: filename = str(int(time.time() * 1000000)) + ".jpg" # Get a folder based on the time: folder = time.strftime("%Y-%m-%d/%H:%M/") check_dir(folder) if is_char_dev(VIDEO_DEV): Stats.cam_present = True fullpath = os.path.join(folder, filename) cmd = [ "fswebcam", "-d", VIDEO_DEV, "-r", "640x480", "--top-banner", "--line-colour", "#00000000", "--banner-colour", "#DD444444", # I hope this font is available on your box: "--font", "/usr/share/fonts/truetype/freefont/FreeSans.ttf", "--title", "Ant Farm", "--timestamp", "%Y-%m-%d %H:%M:%S (%Z)", "--jpeg", "95", fullpath ] out,Stats.images.output = run_prog(cmd) if out == 0: Stats.images.count += 1 Stats.images.time = timestamp() link_if_exists(fullpath) else: Stats.cam_present = False class ImageUpload(threading.Thread): SSH_USER = "<>" SSH_SERVER = "<>" SSH_TARGET = "<>" SSH_STATS = "< '%s'" % where ] p = subprocess.Popen(cmd, stdin=subprocess.PIPE) p.communicate(input=stats) def run(self): CURFILE_LOCK.acquire() if os.path.exists(CURFILE): cmd = [ "scp", "-o", "ConnectTimeout=5" ] cmd += [ CURFILE, ImageUpload.SSH_DEST ] out, Stats.upload.output = run_prog(cmd) if out == 0: Stats.upload.count += 1 Stats.upload.time = timestamp() CURFILE_LOCK.release() # Image is the real important thing. We care less about this: self.send_text(stats_string(), ImageUpload.SSH_STATS) class FolderArchiver(threading.Thread): def run(self): l = os.listdir(".") l.sort() # Only folders, please. l = filter(lambda x: is_folder(x), l) for path in l[:-2]: name = path + ".tar.lzma" # lzma kicks bzip2's butt in compression: cmd = [ "tar", "-c", "--lzma", "--totals", "--remove-files" ] cmd += [ "-f", name, path ] out, Stats.archive.output = run_prog(cmd) if out == 0: Stats.archive.count += 1 Stats.archive.time = timestamp() ################################################################################ class NotRunningException(Exception): pass class GenericRunner(threading.Thread): def __init__(self, newthread, sleeptime): self.newthread, self.sleeptime = newthread, sleeptime threading.Thread.__init__(self) def oursleep(self): for i in xrange(self.sleeptime): if not RUNNING: raise NotRunningException() time.sleep(1) def run(self): try: while True: self.newthread().start() self.oursleep() except NotRunningException: pass class ImageCaptureRunner(GenericRunner): def __init__(self): GenericRunner.__init__(self, ImageCapture, 5) class ImageUploadRunner(GenericRunner): def __init__(self): GenericRunner.__init__(self, ImageUpload, 10) class FolderArchiverRunner(GenericRunner): def __init__(self): GenericRunner.__init__(self, FolderArchiver, 3600) ################################################################################ class Interface(threading.Thread): def _exit(self, args): """usage: exit Stops all monitoring, and exits the program.""" print "Waiting for all threads to complete...", sys.stdout.flush() global RUNNING RUNNING = False self.saver.join() self.uploader.join() self.archiver.join() print "done" print "Goodbye!" sys.exit(1) def _stats(self, args): """usage: stats Prints some info on the monitoring process.""" print stats_string() def _outputs(self, args): """usage: outputs Prints the stdout/stderr for the 3 main tasks. (Image/Upload/Archive)""" print command_string() def _help(self, args): """usage: help [command] Describes a given command, or all commands if none is provided.""" if len(args) > 0: todo = [ args[0] ] else: todo = Interface.COMMANDS for i in todo: if i in Interface.COMMANDS: print "===", i.upper(), "="*15 print Interface.COMMANDS[i].__doc__ else: print "No documentation for:", i COMMANDS = { "exit" : _exit, "stats" : _stats, "outputs" : _outputs, "help" : _help } def __init__(self): self.saver = ImageCaptureRunner() self.uploader = ImageUploadRunner() self.archiver = FolderArchiverRunner() threading.Thread.__init__(self) def run(self): self.saver.start() self.uploader.start() self.archiver.start() while True: cmd = map( str.lower, raw_input(">> ").split() ) if len(cmd) == 0: continue if cmd[0] in Interface.COMMANDS: Interface.COMMANDS[cmd[0]](self, cmd[1:]) elif cmd[0] in [ "fuck", "shit", "damn", "crap", "piss" ]: print "Tell me about it... :X" else: print "Command: '%s' not found." % cmd[0] ################################################################################ if __name__ == "__main__": # Check inputs: if len(sys.argv) != 3: print "usage: %s CAMERA_DEVICE OUTPUT_DIRECTORY" % sys.argv[0] sys.exit(1) # Copy in the provided data: VIDEO_DEV, DEST = sys.argv[1:] # Prepare our directory, and move into it check_dir(DEST) os.chdir(DEST) # Get the timestamp, and get going: Stats.started = timestamp() Interface().start()