#!/usr/bin/env python # ovine.py version 2.0.2 # dated 8 December 2000 # written by Julius Welby # object oriented version of my queue manager module "rc5", with new e-mail options. # for info see http://www.outwardlynormal.com/python/ovine.htm # # *** EDIT CONFIG AND (optionally) THE MAIL VARIABLES TO USE THIS MODULE *** # config is a tuple of tuples to hold project configuration details. Each tuple contains: # 1) project name # 2) folder path (the r prefix makes this a raw string, avoiding \ problems) # 3) in buffer name # 4) out buffer name # 5) empty size (bytes) # 6) e-mail options: 0=no e-mail 1=flush and fetch when a buffer is promoted # 7) number of blocks to be requested when fetching work (3000 rc5 blocks gives about 5 days work on a P3 600) # 8) Status: 1 = active 0 = ignore this project # The redundancy is deliberate, as I want to preserve the generality of the script. config = (('rc5', r'C:\Crypto2', 'buff-in.rc5', 'buff-out.rc5', 8, 1, 3000, 1), ('ogr', r'C:\Crypto2', 'buff-in.ogr', 'buff-out.ogr', 8, 1, 3000, 0)) # MAIL VARIABLES: IF you are using e-mail for one or more projects please edit the following: # outserver is the SMTP server used for outgoing mail (check your mail account settings to see what you use). outserver = 'yoursmtpserver' # mailaddress is the e-mail address to which alerts will be sent, if you request them mailaddress = 'you@yourdomain.com' # fetchaddress is the address from which work is fetched fetchaddress = 'fetch@distributed.net' # flushaddress is the address to which work is flushed flushaddress = 'flush@distributed.net' # Note: Ovine's mail functions will fail gracefully if it cannot connect to the specified # mail server. However, for the mail functions to be useful, you really need a permanent # connection to the mail server. # I plan to add handling of incoming work via POP3 and possibly Outlook in the next version of Ovine. # That's the last of variables you need to set up. import os, sys, string, glob, smtplib, MimeWriter, mimify, StringIO, base64 class Project: def __init__(self): self.name = config[proj][0] # read the configuration into the object attributes self.folder = config[proj][1] self.buffin = config[proj][2] self.buffout = config[proj][3] self.empty = config[proj][4] self.mail = config[proj][5] self.blocks = config[proj][6] self.active = config[proj][7] self.statdict = {} # define statdict as a dictionary self.file = os.path.join(self.folder, self.buffin) # create a file attribute, the full path of the buffer self.outfile = os.path.join(self.folder, self.buffout) self.pathcheck = os.path.exists(self.file) # check that the file attribute is a real file self.flushfail = 0 # set flush success indicator # create current list of possible candidates (plus current buffer) def candidates(self): self.statdict ={} self.filenames = glob.glob1(self.folder, '*'+ self.buffin) # glob.glob1 looks into the folder (arg1) for matches to arg2, returns list of matches self.number = len(self.filenames) # set self.number to the number of candidates (+ 1, the active buffer) for name in self.filenames: # loop through the names of files in the dir path = os.path.join(self.folder, name) modified = os.path.getmtime(path) # this is the last modification date self.statdict[modified] = path # add the "last mod" and "path" to the dictionary as index, value pair def candidate_messages(self): if self.number >= 2: print 'Files waiting in the queue =', self.number-1 elif self.number<=1: print 'There are no candidate files waiting in the queue.' def promote(self): # identify find oldest candidate file in the list of candidates and promote it to be the new buffer if self.number >0: # if there is at least one candidate keylist = self.statdict.keys() # create list of last updated dates smallest = min(keylist) # identify smallest number (=oldest file) oldest = self.statdict[smallest] # get the name of the oldest candidate print 'Making', oldest, 'the new active buffer.' os.rename(oldest, self.file) # rename the file to be new buffer print 'New buffer created successfully.' self.candidate_messages() # print info about queue length else: self.candidate_messages() print 'Please save new work into', self.folder + ', renaming the files appropriately.' def fetch(self): print 'Initiating a fetch.' # send an email to the fetch address asking for more work to be sent print 'Requesting', self.blocks, 'blocks for the', self.name, 'project from', fetchaddress + '.' message = 'Subject: Ovine - Fetch\n\nnumblocks=' + `self.blocks` try: m = smtplib.SMTP(outserver) m.sendmail(mailaddress, fetchaddress, message) print 'When they arrive, please rename and save the fetched files into the appropriate folder.' except: print 'A problem was encountered when trying to use the SMTP server specified.' print 'Fetch abandoned.' def flush(self, address, filepath, filename): print 'Initiating a flush.' # create temp string output = StringIO.StringIO() # create MimeWriter object m = MimeWriter.MimeWriter(output) m.addheader("subject", "Ovine - Flush") m.addheader("MIME-Version", "1.0") m.flushheaders() # create optional plain text segment (set to a space character unless project is ogr). m.startmultipartbody("mixed") text = m.nextpart() t = text.startbody("text/plain") text.flushheaders() if self.name == "ogr" or self.name == "OGR": t.write("contest=OGR") else: t.write(" ") # left blank: current Dnet default fetch is RC5. # create base64 encoded segment and write the buffer file into it as an attachment attach = m.nextpart() attach.addheader("Content-Transfer-Encoding", "base64") attach.addheader("Content-Disposition", 'attachment; filename="%s"' % filename) f = attach.startbody('application/octet-stream; name="%s"' % filename) attach.flushheaders() base64.encode(open(filepath, 'r'), f) m.lastpart() # email the mail message to the recipient try: s = smtplib.SMTP(outserver) s.sendmail(address, flushaddress, output.getvalue()) s.close() except: self.flushfail = 1 print 'A problem was encountered when trying to use the SMTP server specified.' print 'Flush abandoned.' print '(Your', self.buffout,'file has not been affected by this failure.)' def delete_file(self, file): # delete the file at the filepath passed in print 'Removing empty file.' os.remove(file) print 'Empty file removed.' def archive_buffout(self): # did the flush fail? if self.flushfail == 1: pass #do nothing else: # is the archive folder there to hold sent buff-out files? archivefolder = os.path.join(self.folder,"ovinefiles") pathcheck = os.path.exists(archivefolder) if not pathcheck: os.mkdir(archivefolder) # if not already there, create the folder # prepare a new name for the sent file countlist = glob.glob1(archivefolder, '*'+ self.buffout) # count = len(countlist) prefix = `count + 1` + '_' newname = prefix + self.buffout content = self.readfile(self.outfile) # read buff-out into a string object self.writefile(content, newname, archivefolder) # write renamed sent buff-out to archive folder self.delete_file(self.outfile) # delete the file in its old location print 'The buff-out has been flushed and archived into the following location:' print archivefolder print 'You should soon receive an e-mail from', flushaddress, 'confirming a successful flush.' print 'If', flushaddress, 'has been unable to accept the flush for any reason,' print 'you can manually resend the file, renaming it back to', self.buffout print 'Use the last updated date to identify which file you need to resend.' print 'It is a good idea to delete the successfully flushed files in the sentfiles folder regularly.' print 'This makes it easier to find any refused files to resend them, and keeps things neat.' def readfile(self, filepath): sourcefile = open(filepath, 'r') content = sourcefile.read() sourcefile.close() return content def writefile(self, content, filename, folder): # output content to a file called filename in named folder output = open(os.path.join(folder, filename), 'w') output.write(content) output.close() def flushandfetch(self): if self.mail == 1: # if mail options are on if os.path.exists(self.outfile): # if buff-out exists self.flush(mailaddress,self.outfile, self.buffout) self.archive_buffout() self.fetch() def main_function(self): print print 'Project', self.name, 'is active.' #Does the buffer file exist? pathcheck = os.path.exists(self.file) if pathcheck: #Check if file is empty size = os.path.getsize(self.file) # get the size in bytes of the file print 'There are', size, 'characters in', self.buffin # just for info, print number of characters (bytes) if size > self.empty: # if there are more characters than an empty file print 'The file contains work. No action required.' # do nothing self.candidates() self.candidate_messages() else: self.delete_file(self.file) # delete the empty file self.candidates() # run the candidates function self.promote() # run the promote function self.flushandfetch() else: # file never existed, so try to promote print "There is no file called", self.buffin, "in the directory", self.folder + "." self.candidates() # look for candidates self.promote() # run the promote function # self.flushandfetch() # commented out to avoid 24 fetches per day if left long enough # Does the file exist now? pathcheck = os.path.exists(self.file) if pathcheck ==0: # new buffer file still does not exist print print "If you have already downloaded work into the correct folder, you should check the script configuration settings, and the way the files have been renamed." print else: pass proj = 0 # set project counter to 0 print print '*'*30 for item in config: # for each project entry in the config tuple' p = Project() # create instance p of class "Project" if p.active == 1: # if active for this project p.main_function() # run the main-function else: print print 'Project', p.name, 'is not active.' proj = proj + 1