diff --git a/adapter.py b/adapter.py index 1360824..f4dd116 100644 --- a/adapter.py +++ b/adapter.py @@ -2,6 +2,7 @@ import logging import datetime +from output import makeAcceptableFilename try: from google.appengine.api.urlfetch import fetch as googlefetch @@ -15,6 +16,18 @@ class LoginRequiredException(Exception): def __str__(self): return repr(self.url + ' requires user to be logged in') + +class StoryArchivedAlready(Exception): + pass + +class StoryDoesNotExist(Exception): + pass + +class FailedToDownload(Exception): + pass + +class InvalidStoryURL(Exception): + pass class FanfictionSiteAdapter: appEngine = appEngineGlob @@ -93,12 +106,13 @@ class FanfictionSiteAdapter: return self.uuid def getOutputName(self): - self.outputName = self.storyName.replace(" ", "_") + self.outputStorySep + self.storyId + self.outputName = makeAcceptableFilename(self.storyName.replace(" ", "_") + self.outputStorySep + self.storyId) logging.debug('self.outputName=%s' % self.outputName) return self.outputName - def getOutputFileName(self, booksDirectory, format): - self.outputFileName = booksDirectory + "/" + self.getOutputName() + "." + format + def getOutputFileName(self, booksDirectory, bookExt): + self.getOutputName() # make sure self.outputName is populated + self.outputFileName = booksDirectory + "/" + self.outputName + bookExt logging.debug('self.outputFileName=%s' % self.outputFileName) return self.outputFileName diff --git a/downloader.py b/downloader.py index ee0120f..3dfd7fc 100644 --- a/downloader.py +++ b/downloader.py @@ -14,12 +14,18 @@ import urlparse as up import BeautifulSoup as bs import htmlentitydefs as hdefs +import zipdir +import output +import adapter +from adapter import StoryArchivedAlready +from adapter import StoryDoesNotExist +from adapter import FailedToDownload +from adapter import InvalidStoryURL +from adapter import LoginRequiredException import ffnet import fpcom import ficwad -import output -import adapter import fictionalley import hpfiction import twilighted @@ -31,15 +37,30 @@ import time class FanficLoader: '''A controller class which handles the interaction between various specific downloaders and writers''' booksDirectory = "books" + standAlone = False - def __init__(self, adapter, writerClass, quiet = False, inmemory = False, compress=True): + def __init__(self, adapter, writerClass, quiet = False, inmemory = False, compress=True, overwrite=False): self.adapter = adapter self.writerClass = writerClass self.quiet = quiet self.inmemory = inmemory self.compress = compress self.badLogin = False - self.overWrite = True + self.overWrite = overwrite + + def getBooksDirectory(self): + return self.booksDirectory + + def setBooksDirectory(self, bd): + self.booksDirectory = bd + return self.booksDirectory + + def getStandAlone(self): + return self.standAlone + + def setStandAlone(self, sa): + self.standAlone = sa + return self.standAlone def getAdapter(): return self.adapter @@ -55,13 +76,16 @@ class FanficLoader: urls = self.adapter.extractIndividualUrls() - if (self.adapter.hasAppEngine): - self.overWrite = True + logging.debug("self.writerClass=%s" % self.writerClass) + if self.standAlone and not self.inmemory: + s = self.adapter.getOutputFileName(self.booksDirectory, self.writerClass.getFormatExt()) + logging.debug("Always overwrite? %s" % self.overWrite) + if not self.overWrite: + logging.debug("Checking if current archive of the story exists. Filename=%s" % s) + if not zipdir.checkNewer ( s, self.adapter.getStoryUpdated() ): + raise StoryArchivedAlready("A Current archive file \"" + s + "\" already exists! Skipping!") else: - s = self.adapter.getOutputFileName(self.booksDirectory, format) - if not self.overWrite and os.path.isfile(s): - print >> sys.stderr, "File " + s + " already exists! Skipping!" - exit(10) + logging.debug("Do not check for existance of archive file.") self.writer = self.writerClass(self.booksDirectory, self.adapter, inmemory=self.inmemory, compress=self.compress) @@ -83,10 +107,17 @@ class FanficLoader: if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) - (url, format) = sys.argv[1:] - # (url) = sys.argv[1] - # format = 'epub' + argvlen = len(sys.argv) + url = None + if argvlen > 1: + url = sys.argv[1] + if argvlen > 2: + bookFormat = sys.argv[2] + if url is None: + print >> sys.stderr, "Usage: downloader.py URL Type" + sys.exit(-1) + if type(url) is unicode: print('URL is unicode') url = url.encode('latin1') @@ -117,9 +148,9 @@ if __name__ == '__main__': print >> sys.stderr, "Oi! I can haz not appropriate adapter for URL %s!" % url sys.exit(1) - if format == 'epub': + if bookFormat == 'epub': writerClass = output.EPubFanficWriter - elif format == 'html': + elif bookFormat == 'html': writerClass = output.HTMLWriter if adapter.requiresLogin(url): @@ -134,5 +165,28 @@ if __name__ == '__main__': loader = FanficLoader(adapter, writerClass) - loader.download() + loader.setStandAlone(True) + + try: + loader.download() + except FailedToDownload, ftd: + print >> sys.stderr, str(ftd) + sys.exit(2) # Error Downloading + except InvalidStoryURL, isu: + print >> sys.stderr, str(isu) + sys.exit(3) # Unknown Error + except StoryArchivedAlready, se: + print >> sys.stderr, str(se) + sys.exit(10) # Skipped + except StoryDoesNotExist, sdne: + print >> sys.stderr, str(sdne) + sys.exit(20) # Missing + except LoginRequiredException, lre: + print >> sys.stderr, str(lre) + sys.exit(30) # Missing + except Exception, e: + print >> sys.stderr, str(e) + sys.exit(99) # Unknown Error + + sys.exit(0) diff --git a/ffnet.py b/ffnet.py index 7a201e2..a6fee2d 100644 --- a/ffnet.py +++ b/ffnet.py @@ -70,8 +70,7 @@ class FFNet(FanfictionSiteAdapter): logging.debug('spl=%s' % spl) if spl is not None: if len(spl) > 0 and spl[0] != 's': - logging.error("Error URL \"%s\" is not a story." % self.url) - exit (20) + raise InvalidStoryURL("Error URL \"%s\" is not a story." % self.url) if len(spl) > 1: self.storyId = spl[1] if len(spl) > 2: @@ -150,9 +149,23 @@ class FFNet(FanfictionSiteAdapter): return True def extractIndividualUrls(self): - data = self.fetchUrl(self.url) + data = '' + try: + data = self.fetchUrl(self.url) + except Exception, e: + data = '' + logging.error("Caught an exception reading URL " + self.url + ". Exception " + str(e) + ".") + if data is None: + raise StoryDoesNotExist("Problem reading story URL " + self.url + "!") + d2 = re.sub('&\#[0-9]+;', ' ', data) - soup = bs.BeautifulStoneSoup(d2) + soup = None + try: + soup = bs.BeautifulStoneSoup(d2) + except: + logging.error("Failed to decode: <%s>" % d2) + raise FailedToDownload("Error downloading Story: %s! Problem decoding page!" % self.url) + allA = soup.findAll('a') for a in allA: if 'href' in a._getAttrMap() and a['href'].find('/u/') != -1: @@ -264,7 +277,15 @@ class FFNet(FanfictionSiteAdapter): def getText(self, url): time.sleep( 2.0 ) - data = self.fetchUrl(url) + data = '' + try: + data = self.fetchUrl(url) + except Exception, e: + data = '' + logging.error("Caught an exception reading URL " + url + ". Exception " + str(e) + ".") + if data is None: + raise FailedToDownload("Error downloading Chapter: %s! Problem getting page!" % url) + lines = data.split('\n') textbuf = '' @@ -276,16 +297,15 @@ class FFNet(FanfictionSiteAdapter): except: data = olddata + soup = None try: soup = bs.BeautifulStoneSoup(data) except: - logging.info("Failed to decode: <%s>" % data) - soup = None + raise FailedToDownload("Error downloading Chapter: %s! Problem decoding page!" % url) + div = soup.find('div', {'id' : 'storytext'}) if None == div: - logging.error("Error downloading Chapter: %s" % url) - exit (20) - return '
' + raise FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url) return div.__str__('utf8') diff --git a/fictionalley.py b/fictionalley.py index fbbe95e..31f43b5 100644 --- a/fictionalley.py +++ b/fictionalley.py @@ -124,25 +124,36 @@ class FictionAlley(FanfictionSiteAdapter): def extractIndividualUrls(self): - data = self.opener.open(self.url).read() + data = '' + try: + data = self.opener.open(self.url).read() + except Exception, e: + data = '' + logging.error("Caught an exception reading URL " + self.url + ". Exception " + str(e) + ".") + if data is None: + raise StoryDoesNotExist("Problem reading story URL " + self.url + "!") # There is some usefull information in the headers of the first chapter page.. data = data.replace('','