From 94669a2179ce88dd4f1efde62ac06093e2abda46 Mon Sep 17 00:00:00 2001 From: Jim Miller Date: Wed, 4 May 2011 17:54:36 -0500 Subject: [PATCH] Fix up exception handling, first working appengine(SDK) version. --- app.yaml | 4 +- defaults.ini | 4 +- fanficdownloader/adapters/__init__.py | 7 +- .../adapters/adapter_fanfictionnet.py | 10 +- fanficdownloader/adapters/adapter_test1.py | 14 +- .../adapters/adapter_twilightednet.py | 7 +- .../adapters/adapter_whoficcom.py | 5 +- fanficdownloader/adapters/base_adapter.py | 11 +- fanficdownloader/{adapters => }/exceptions.py | 8 +- index.html | 4 +- main.py | 224 ++++++++---------- newdownload.py | 10 +- simplejson/__init__.pyc | Bin 12071 -> 0 bytes simplejson/decoder.pyc | Bin 11292 -> 0 bytes simplejson/encoder.pyc | Bin 13938 -> 0 bytes simplejson/scanner.pyc | Bin 2340 -> 0 bytes 16 files changed, 159 insertions(+), 149 deletions(-) rename fanficdownloader/{adapters => }/exceptions.py (67%) delete mode 100644 simplejson/__init__.pyc delete mode 100644 simplejson/decoder.pyc delete mode 100644 simplejson/encoder.pyc delete mode 100644 simplejson/scanner.pyc diff --git a/app.yaml b/app.yaml index bee0c4e6..f8c0d6b4 100644 --- a/app.yaml +++ b/app.yaml @@ -1,6 +1,6 @@ # fanfictionloader -application: fanfictionloader -version: 3-0-2 +application: ffd-retief +version: 4-0-0 runtime: python api_version: 1 diff --git a/defaults.ini b/defaults.ini index bc75de44..f0029adf 100644 --- a/defaults.ini +++ b/defaults.ini @@ -55,7 +55,7 @@ safe_filename: true extratags: FanFiction ## number of seconds to sleep between calls to the story site. -slow_down_sleep_time:0.5 +#slow_down_sleep_time:0.5 ## Each output format has a section that overrides [defaults] @@ -87,7 +87,7 @@ wide_titlepage_entries: description, storyUrl, author URL ## Each site has a section that overrides [defaults] *and* the format section [test1.com] -titlepage_entries: title,description,category,genre, status,dateCreated,rating,numChapters,numWords,extratags,description,storyUrl,extratags +#titlepage_entries: title,description,category,genre, status,dateCreated,rating,numChapters,numWords,extratags,description,storyUrl,extratags extratags: FanFiction,Testing ## If necessary, you can define [:] sections to customize diff --git a/fanficdownloader/adapters/__init__.py b/fanficdownloader/adapters/__init__.py index ec8b55fc..63254b4d 100644 --- a/fanficdownloader/adapters/__init__.py +++ b/fanficdownloader/adapters/__init__.py @@ -5,6 +5,8 @@ from os.path import dirname, basename, normpath import logging import urlparse as up +import fanficdownloader.exceptions as exceptions + ## This bit of complexity allows adapters to be added by just adding ## the source file. It eliminates the long if/else clauses we used to ## need to pick out the adapter. @@ -21,11 +23,10 @@ def getAdapter(config,url): adapter = cls(config,url) # raises InvalidStoryURL return adapter # No adapter found. - raise UnknownSite( url, (cls.getSiteDomain() for cls in __class_list) ) + raise exceptions.UnknownSite( url, [cls.getSiteDomain() for cls in __class_list] ) ## Automatically import each adapter_*.py file. -## Each must call _register_handler() with their class to be -## registered. +## Each implement getClass() to their class filelist = glob.glob(dirname(__file__)+'/adapter_*.py') sys.path.insert(0,normpath(dirname(__file__))) diff --git a/fanficdownloader/adapters/adapter_fanfictionnet.py b/fanficdownloader/adapters/adapter_fanfictionnet.py index 47b341e1..0ef69765 100644 --- a/fanficdownloader/adapters/adapter_fanfictionnet.py +++ b/fanficdownloader/adapters/adapter_fanfictionnet.py @@ -5,8 +5,10 @@ import datetime import logging import re import urllib2 +import time import fanficdownloader.BeautifulSoup as bs +import fanficdownloader.exceptions as exceptions from base_adapter import BaseSiteAdapter, utf8FromSoup @@ -50,7 +52,7 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter): soup = bs.BeautifulSoup(self._fetchUrl(url)) except urllib2.HTTPError, e: if e.code == 404: - raise adapters.StoryDoesNotExist(self.url) + raise exceptions.StoryDoesNotExist(self.url) else: raise e @@ -166,14 +168,16 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter): def getChapterText(self, url): logging.debug('Getting chapter text from: %s' % url) - + time.sleep(0.5) ## ffnet tends to fail more if hit too fast. + ## This is in additional to what ever the + ## slow_down_sleep_time setting is. soup = bs.BeautifulStoneSoup(self._fetchUrl(url), selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags. span = soup.find('div', {'id' : 'storytext'}) if None == span: - raise adapters.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url) + raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url) return utf8FromSoup(span) diff --git a/fanficdownloader/adapters/adapter_test1.py b/fanficdownloader/adapters/adapter_test1.py index a4b1ac0e..fdf9cb87 100644 --- a/fanficdownloader/adapters/adapter_test1.py +++ b/fanficdownloader/adapters/adapter_test1.py @@ -3,6 +3,7 @@ import datetime import fanficdownloader.BeautifulSoup as bs +import fanficdownloader.exceptions as exceptions from base_adapter import BaseSiteAdapter, utf8FromSoup @@ -12,6 +13,8 @@ class TestSiteAdapter(BaseSiteAdapter): BaseSiteAdapter.__init__(self, config, url) self.story.setMetadata('siteabbrev','tst1') self.crazystring = u" crazy tests:[bare amp(&) quote(') amp(&) gt(>) lt(<) ATnT(AT&T) pound(£)]" + # get storyId from url--url validation guarantees query is only sid=1234 + self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1]) @staticmethod def getSiteDomain(): @@ -24,8 +27,14 @@ class TestSiteAdapter(BaseSiteAdapter): return BaseSiteAdapter.getSiteURLPattern(self)+'\?sid=\d+$' def extractChapterUrlsAndMetadata(self): + + if self.story.getMetadata('storyId') == '666': + raise exceptions.StoryDoesNotExist(self.url) + + if self.story.getMetadata('storyId') == '668': + raise exceptions.FailedToLogin(self.url,"FakeUser") + self.story.setMetadata(u'title',"Test Story Title "+self.crazystring) - self.story.setMetadata('storyId','12345') self.story.setMetadata('storyUrl',self.url) self.story.setMetadata('description',u'Description '+self.crazystring+u''' Done @@ -67,6 +76,9 @@ Some more longer description. "I suck at summaries!" "Better than it sounds!" #

“It might be all it takes.” He held out his hand and shook Wilfred’s, he glanced at the Vinvocci woman as she knelt there cradling the body of her partner, and he said not a word.

Disclaimer: I don't own Harry Potter or the craziness of Romilda Vane.

*EDIT* Romilda is in her 4th year, like she always has.

Thanks xxSkitten for Beta reading this! :D

Full Summary: Harry and Ginny are together. Romilda Vane is not happy. She can't stand seeing the guy she wants to be with the person she deserves to be with, with another girl - especially a girl younger that is far less pretty than her. She orders 100 Love potions from Weasley's Wizard Wheezes, Wonder Witch line. Several get to undesired targets, such as Ron Weasley. What happens when Ginny takes matters into her own hands?


Romilda Vane (3rd Person)

"Th-Tha-That little skank!" snarled Romilda Vane as she watched Harry Potter and Ginny Weasley from the balcony overlooking the common room.

"Romilda," said Abigail Stones, one of her friends, "Lets go, you don't need to watch this."

Abigail stones had long, sleek black hair that was always in a high ponytail. She had pale skin that very few blemishes. She had a long, blocky nose and a small mouth. Her hazel eyes were behind think horned rimmed glasses, and her uniform was in order without a crease or wrinkle in sight.

"What does he see in her?" Romilda snarled in a whisper, her eyes upon the red-headed fifth year. "I mean, she's all freckle-y and gingery, she's a filthy fifth year-"

"And you're a fourth year!" Abigail interjected, but Romilda just kept on ranting.

"…and I heard they live in a dump!" Her nostrils flared.

"Well what are you going to do about it, just sit and watch them all the time?" Piped up Charlotte Henderson, the second of Romilda's present friends. She had curly shoulder length blonde hair and wore a thick layer of make up to cover up her various large red pimples. Her eyes were dark blue and were surrounded with large clumpy eyelashes. She had an eager expression, like she was witnessing two people on a Muggle drama who were about to kiss.

"Of course not!" She said, looking away as Ginny kissed Harry. "I've ordered one-hundred love potions from that Wonder Witch line from Weasley's Wizard Wheezes, so once I get him in my grasp I'll have him for the rest of the year!"

"You realize," Abigail said, rolling her eyes slightly. "That with your luck, you'll get every guy in the school but him."

"It will only be for around an hour, and I could always just make him jealous by making every guy close to him fall in love with me."

Abigail sighed, "One, he has a girlfriend. Two, you already got his best friend and he wasn't jealous, he was pissed, and three, you'll get expelled before you can get to him."

"Sometimes I wonder how we're friends!" Romilda snapped at Abigail.

"We're friends because you need a good influence around you, or you would be as crazy as Peeves." Abigail stated.

Romilda spun around to glare at her friend, knowing Abigail was right but did not daring to admit it.

The silence was broken by Charlotte. "So how are you going to slip him the potion?" She asked, honestly interested.

"Just wait 'till morning, and you'll see." Romilda said, looking back down at Harry, then suddenly realizing Ginny wasn't there.

Then, Ginny appeared next to them. She stalked through their group, not looking at any of them. She stopped at the girl's dorm door and turned her head slightly to see them from the corner of her eye.

"One-hundred? You're that desperate?" Ginny said with a mix of humor and anger. Then, the red-head turned to the door and left them all in a surprised state.

"You're screwed." Abigail said matter-of-factly. She went into the dorm without another word.

"She can be so insensitive." Charlotte said, looking where Abigail had left while shaking her head.

"You can say that again," mumbled Romilda, downcast.

"She can be-" Charlotte began again, but Romilda held her hand up.

"That was a figure of speech, pea-brain." She snapped. "Sometimes you can be as dumb as that Loony Lovegood." She then stalked up to her room with one last pleading look at Harry, whispering fiercely under her breath.

"You will be mine…"


Isn't Romilda Pleasant? ;] xD Oh she's crazy, insane, envious, has stalkerish and man stealing tendencies. and that's why she's everyone's FAVORITE character.

Also Romilda's in her fourth year. yeah. oh an NO FEMSLASH geez.

Also, Abigail Stones and Charlotte Henderson are to OC's that i made up on the spot because even crazies need friends. Ones the ignored good influence and ones a stereotypical dumb 'blonde' (NO OFFENSE TO BLONDES! I'm blonde and I don't take those things that personally unless their clearly mean that way. Also Charlotte's Muggle-Born so she watches all those Muggle TV's shows were all addicted too. ;] .. )

The rest of the story will be in Ginny's point of view whether its 1st or 3rd Person IDK yet but probably 1st person. The pairing in this are - Harry x Ginny / Romilda x Harry / Ron x Hermione (hints of) / Charolette x OC (Undetermined).

Reviews = Something... GOOD!

~ Sincerely MNM

#
''' + if self.story.getMetadata('storyId') == '667': + raise exceptions.FailedToDownload("Error downloading Chapter: %s!" % url) + soup = bs.BeautifulStoneSoup(u'''

Chapter

diff --git a/fanficdownloader/adapters/adapter_twilightednet.py b/fanficdownloader/adapters/adapter_twilightednet.py index dded7719..f3d64064 100644 --- a/fanficdownloader/adapters/adapter_twilightednet.py +++ b/fanficdownloader/adapters/adapter_twilightednet.py @@ -9,6 +9,7 @@ import urllib2 import fanficdownloader.BeautifulSoup as bs from fanficdownloader.htmlcleanup import stripHTML +import fanficdownloader.exceptions as exceptions from base_adapter import BaseSiteAdapter, utf8FromSoup @@ -74,7 +75,7 @@ class TwilightedNetSiteAdapter(BaseSiteAdapter): if self.needToLoginCheck(d) : logging.info("Failed to login to URL %s as %s" % (loginUrl, data['penname'])) - raise adapters.FailedToLogin(url,data['penname']) + raise exceptions.FailedToLogin(url,data['penname']) return False else: return True @@ -88,7 +89,7 @@ class TwilightedNetSiteAdapter(BaseSiteAdapter): data = self._fetchUrl(url) except urllib2.HTTPError, e: if e.code == 404: - raise adapters.StoryDoesNotExist(self.url) + raise exceptions.StoryDoesNotExist(self.url) else: raise e @@ -190,7 +191,7 @@ class TwilightedNetSiteAdapter(BaseSiteAdapter): span = soup.find('div', {'id' : 'story'}) if None == span: - raise adapters.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url) + raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url) return utf8FromSoup(span) diff --git a/fanficdownloader/adapters/adapter_whoficcom.py b/fanficdownloader/adapters/adapter_whoficcom.py index c848a443..b25a9785 100644 --- a/fanficdownloader/adapters/adapter_whoficcom.py +++ b/fanficdownloader/adapters/adapter_whoficcom.py @@ -7,6 +7,7 @@ import re import urllib2 import fanficdownloader.BeautifulSoup as bs +import fanficdownloader.exceptions as exceptions from base_adapter import BaseSiteAdapter, utf8FromSoup @@ -44,7 +45,7 @@ class WhoficComSiteAdapter(BaseSiteAdapter): soup = bs.BeautifulSoup(self._fetchUrl(url)) except urllib2.HTTPError, e: if e.code == 404: - raise adapters.StoryDoesNotExist(self.url) + raise exceptions.StoryDoesNotExist(self.url) else: raise e @@ -173,7 +174,7 @@ class WhoficComSiteAdapter(BaseSiteAdapter): span = soup.find('span', {'style' : 'font-size: 100%;'}) if None == span: - raise adapters.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url) + raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url) return utf8FromSoup(span) diff --git a/fanficdownloader/adapters/base_adapter.py b/fanficdownloader/adapters/base_adapter.py index 85dd8d35..d4233d08 100644 --- a/fanficdownloader/adapters/base_adapter.py +++ b/fanficdownloader/adapters/base_adapter.py @@ -9,7 +9,7 @@ import urlparse as up from fanficdownloader.story import Story from fanficdownloader.configurable import Configurable from fanficdownloader.htmlcleanup import removeEntities, removeAllEntities, stripHTML -from fanficdownloader.adapters.exceptions import InvalidStoryURL +from fanficdownloader.exceptions import InvalidStoryURL class BaseSiteAdapter(Configurable): @@ -29,6 +29,7 @@ class BaseSiteAdapter(Configurable): self.addConfigSection(self.getSiteDomain()) self.opener = u2.build_opener(u2.HTTPCookieProcessor()) self.storyDone = False + self.metadataDone = False self.story = Story() self.story.setMetadata('site',self.getSiteDomain()) self.story.setMetadata('dateCreated',datetime.datetime.now()) @@ -58,13 +59,19 @@ class BaseSiteAdapter(Configurable): # Does the download the first time it's called. def getStory(self): if not self.storyDone: - self.extractChapterUrlsAndMetadata() + self.getStoryMetadataOnly() for (title,url) in self.chapterUrls: self.story.addChapter(removeEntities(title), removeEntities(self.getChapterText(url))) self.storyDone = True return self.story + def getStoryMetadataOnly(self): + if not self.metadataDone: + self.extractChapterUrlsAndMetadata() + self.metadataDone = True + return self.story + ############################### @staticmethod diff --git a/fanficdownloader/adapters/exceptions.py b/fanficdownloader/exceptions.py similarity index 67% rename from fanficdownloader/adapters/exceptions.py rename to fanficdownloader/exceptions.py index 3f554442..44cae238 100644 --- a/fanficdownloader/adapters/exceptions.py +++ b/fanficdownloader/exceptions.py @@ -14,7 +14,7 @@ class InvalidStoryURL(Exception): self.example=example def __str__(self): - return "Bad Story URL: %s\nFor site: %s\nExample: %s" % (self.url, self.domain, self.example) + return "Bad Story URL: (%s) for site: (%s) Example: (%s)" % (self.url, self.domain, self.example) class FailedToLogin(Exception): def __init__(self,url,username): @@ -22,14 +22,14 @@ class FailedToLogin(Exception): self.username=username def __str__(self): - return "Failed to Login for URL: %s with username: %s" % (self.url, self.username) + return "Failed to Login for URL: (%s) with username: (%s)" % (self.url, self.username) class StoryDoesNotExist(Exception): def __init__(self,url): self.url=url def __str__(self): - return "Story Does Not Exit: " + self.url + return "Story does not exist: (%s)" % self.url class UnknownSite(Exception): def __init__(self,url,supported_sites_list): @@ -37,5 +37,5 @@ class UnknownSite(Exception): self.supported_sites_list=supported_sites_list def __str__(self): - return "Unknown Site("+self.url+"). Supported sites: "+", ".join(self.supported_sites_list) + return "Unknown Site(%s). Supported sites: (%s)" % (self.url, ", ".join(self.supported_sites_list)) diff --git a/index.html b/index.html index 499d3be8..51d6cd57 100644 --- a/index.html +++ b/index.html @@ -65,8 +65,8 @@ src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
EPub HTML - Plain Text - Mobi (Kindle) + Plain Text +

For Mobi (Kindle) select EPub and Convert it.

diff --git a/main.py b/main.py index eaa41d9f..e2625b31 100644 --- a/main.py +++ b/main.py @@ -15,10 +15,15 @@ # limitations under the License. # +import logging +logging.getLogger().setLevel(logging.DEBUG) + import os +from os.path import dirname, basename, normpath import sys import zlib -import logging +import urllib + import traceback import StringIO @@ -42,6 +47,9 @@ from fanficdownloader.zipdir import * from ffstorage import * +from fanficdownloader import adapters, writers +import ConfigParser + class LoginRequired(webapp.RequestHandler): def get(self): user = users.get_current_user() @@ -104,29 +112,29 @@ class FileServer(webapp.RequestHandler): name = fanfic.name.encode('utf-8') - name = makeAcceptableFilename(name) + #name = urllib.quote(name) logging.info("Serving file: %s" % name) - if fanfic.format == 'epub': + if name.endswith('.epub'): self.response.headers['Content-Type'] = 'application/epub+zip' - self.response.headers['Content-disposition'] = 'attachment; filename=' + name + '.epub' - elif fanfic.format == 'html': + elif name.endswith('.html'): self.response.headers['Content-Type'] = 'text/html' - self.response.headers['Content-disposition'] = 'attachment; filename=' + name + '.html.zip' - elif fanfic.format == 'text': + elif name.endswith('.txt'): self.response.headers['Content-Type'] = 'text/plain' - self.response.headers['Content-disposition'] = 'attachment; filename=' +name + '.txt.zip' - elif fanfic.format == 'mobi': - self.response.headers['Content-Type'] = 'application/x-mobipocket-ebook' - self.response.headers['Content-disposition'] = 'attachment; filename=' + name + '.mobi' + elif name.endswith('.zip'): + self.response.headers['Content-Type'] = 'application/zip' + else: + self.response.headers['Content-Type'] = 'application/octet-stream' + + self.response.headers['Content-disposition'] = 'attachment; filename="%s"' % name data = DownloadData.all().filter("download =", fanfic).order("index") - # epub, txt and html are all already compressed. + # epubs are all already compressed. # Each chunk is compress individually to avoid having # to hold the whole in memory just for the # compress/uncompress - if fanfic.format == 'mobi': + if fanfic.format != 'epub': def dc(data): try: return zlib.decompress(data) @@ -230,18 +238,47 @@ class FanfictionDownloader(webapp.RequestHandler): download.user = user download.url = url download.format = format - download.put() + adapter = None - taskqueue.add(url='/fdowntask', - queue_name="download", - params={'format':format, - 'url':url, - 'login':login, - 'password':password, - 'user':user.email()}) + try: + config = ConfigParser.ConfigParser() + logging.debug('reading defaults.ini config file, if present') + config.read('defaults.ini') + logging.debug('reading appengine.ini config file, if present') + config.read('appengine.ini') + adapter = adapters.getAdapter(config,url) + logging.info('Created an adaper: %s' % adapter) + + if len(login) > 1: + adapter.username=login + adapter.password=password + ## This scrapes the metadata, which will be + ## duplicated in the queue task, but it + ## detects bad URLs, bad login, bad story, etc + ## without waiting for the queue. So I think + ## it's worth the double up. Could maybe save + ## it all in the download object someday. + story = adapter.getStoryMetadataOnly() + download.title = story.getMetadata('title') + download.author = story.getMetadata('author') + download.put() + + taskqueue.add(url='/fdowntask', + queue_name="download", + params={'format':format, + 'url':url, + 'login':login, + 'password':password, + 'user':user.email()}) + + logging.info("enqueued download key: " + str(download.key())) + + except Exception, e: + logging.exception(e) + download.failure = str(e) + download.put() - logging.info("enqueued download key: " + str(download.key())) self.redirect('/status?id='+str(download.key())) return @@ -289,120 +326,67 @@ class FanfictionDownloaderTask(webapp.RequestHandler): logging.info('Creating adapter...') try: - if url.find('fictionalley') != -1: - adapter = fictionalley.FictionAlley(url) - elif url.find('ficwad') != -1: - adapter = ficwad.FicWad(url) - elif url.find('fanfiction.net') != -1: - adapter = ffnet.FFNet(url) - elif url.find('fictionpress.com') != -1: - adapter = fpcom.FPCom(url) - elif url.find('harrypotterfanfiction.com') != -1: - adapter = hpfiction.HPFiction(url) - elif url.find('twilighted.net') != -1: - adapter = twilighted.Twilighted(url) - elif url.find('twiwrite.net') != -1: - adapter = twiwrite.Twiwrite(url) - elif url.find('adastrafanfic.com') != -1: - adapter = adastrafanfic.Adastrafanfic(url) - elif url.find('whofic.com') != -1: - adapter = whofic.Whofic(url) - elif url.find('potionsandsnitches.net') != -1: - adapter = potionsNsnitches.PotionsNSnitches(url) - elif url.find('mediaminer.org') != -1: - adapter = mediaminer.MediaMiner(url) - else: - logging.debug("Bad URL detected") - download.failure = url +" is not a valid story URL." - download.put() - return + config = ConfigParser.ConfigParser() + logging.debug('reading defaults.ini config file, if present') + config.read('defaults.ini') + logging.debug('reading appengine.ini config file, if present') + config.read('appengine.ini') + adapter = adapters.getAdapter(config,url) except Exception, e: logging.exception(e) - download.failure = "Adapter was not created: " + str(e) + download.failure = str(e) download.put() return logging.info('Created an adaper: %s' % adapter) if len(login) > 1: - adapter.setLogin(login) - adapter.setPassword(password) + adapter.username=login + adapter.password=password - if format == 'epub': - writerClass = output.EPubFanficWriter - elif format == 'html': - writerClass = output.HTMLWriter - elif format == 'mobi': - writerClass = output.MobiWriter - else: - writerClass = output.TextWriter - - loader = FanficLoader(adapter, - writerClass, - quiet = True, - inmemory=True, - compress=False) try: - data = loader.download() - - if format == 'html' or format == 'text': - # data is uncompressed hence huge - ext = '.html' - if format == 'text': - ext = '.txt' - logging.debug(data) - files = {makeAcceptableFilename(str(adapter.getOutputName())) + ext : StringIO.StringIO(data.decode('utf-8')) } - d = inMemoryZip(files) - data = d.getvalue() - - - except LoginRequiredException, e: - logging.exception(e) - download.failure = 'Login problem detected' - download.put() - return + # adapter.getStory() is what does all the heavy lifting. + writer = writers.getWriter(format,config,adapter.getStory()) except Exception, e: logging.exception(e) - download.failure = 'Some exception happened in downloader: ' + str(e) + download.failure = str(e) download.put() return - - if data == None: - if loader.badLogin: - logging.debug("Bad login detected") - download.failure = 'Login failed' - download.put() - return - download.failure = 'No data returned by adaptor' - download.put() - else: - download.name = self._printableVersion(adapter.getOutputName()) - download.title = self._printableVersion(adapter.getStoryName()) - download.author = self._printableVersion(adapter.getAuthorName()) - download.put() - index=0 + + download.name = writer.getOutputFileName() + download.title = adapter.getStory().getMetadata('title') + download.author = adapter.getStory().getMetadata('author') + download.put() + index=0 - # epub, txt and html are all already compressed. - # Each chunk is compressed individually to avoid having - # to hold the whole in memory just for the - # compress/uncompress. - if format == 'mobi': - def c(data): - return zlib.compress(data) - else: - def c(data): - return data - - while( len(data) > 0 ): - DownloadData(download=download, - index=index, - blob=c(data[:1000000])).put() - index += 1 - data = data[1000000:] - download.completed=True - download.put() + outbuffer = StringIO.StringIO() + writer.writeStory(outbuffer) + data = outbuffer.getvalue() + outbuffer.close() + del writer + del adapter + + # epubs are all already compressed. + # Each chunk is compressed individually to avoid having + # to hold the whole in memory just for the + # compress/uncompress. + if format != 'epub': + def c(data): + return zlib.compress(data) + else: + def c(data): + return data - logging.info("Download finished OK") + while( len(data) > 0 ): + DownloadData(download=download, + index=index, + blob=c(data[:1000000])).put() + index += 1 + data = data[1000000:] + download.completed=True + download.put() + + logging.info("Download finished OK") return def toPercentDecimal(match): diff --git a/newdownload.py b/newdownload.py index 287c665e..49c12697 100644 --- a/newdownload.py +++ b/newdownload.py @@ -6,7 +6,7 @@ logging.basicConfig(level=logging.DEBUG,format="%(levelname)s:%(filename)s(%(lin import sys, os import getpass -from fanficdownloader import adapters,writers +from fanficdownloader import adapters,writers,exceptions import ConfigParser @@ -27,7 +27,7 @@ try: try: print adapter.getStory() - except adapters.FailedToLogin, ftl: + except exceptions.FailedToLogin, ftl: print "Login Failed, Need Username/Password." sys.stdout.write("Username: ") adapter.username = sys.stdin.readline().strip() @@ -40,9 +40,9 @@ try: writeStory(adapter,"txt") del adapter -except adapters.InvalidStoryURL, isu: +except exceptions.InvalidStoryURL, isu: print isu -except adapters.StoryDoesNotExist, dne: +except exceptions.StoryDoesNotExist, dne: print dne -except adapters.UnknownSite, us: +except exceptions.UnknownSite, us: print us diff --git a/simplejson/__init__.pyc b/simplejson/__init__.pyc deleted file mode 100644 index f01003d4f81d37513d0f8a2a5fb857b8448ae2bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12071 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 E0OpUxq5uE@ diff --git a/simplejson/decoder.pyc b/simplejson/decoder.pyc deleted file mode 100644 index 2ae9b3591ee9c6400d5cd09eb0a05999ef680bdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11292 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 diff --git a/simplejson/scanner.pyc b/simplejson/scanner.pyc deleted file mode 100644 index 30d94445f0a0c941ee46b6c4fa3bd255e662f6ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2340 zcmb_dUvC>l5T8B&cj6S1v}wr$X-SKd%5~uhP(>h$k&IGTvRgn&D(iB$PA@s%o$fXz z5y=AtVOzZO)Mvf`?|cM43}2x9X3kDsMZDG4&dtuw@3*rvb9;aNTPd%;dewAj{43%4 z6-F|IaEW#x6}c82DcVtVx+v2O9a-dOXeUeR``{L3b&d|p6jnX8C{7O5ZHEFAz? zAg#zNlA9ByB(hAKY@MOa3jk)x&C{>gutGXZ5r}n#b~3zmr&{2M79hUuJZY_%@JI(M ziDL(Wj?3O_{909oRWl3Gw~uspyx7K^k~N5GZKJyJ#ly4RPimh(-*ea3)~b6C_T2kx z8`WLic)nY^|9nHH4ioX1!N_1FzeAi6c|@aBQ8X%x#iCJ>OoqHHjk03N(I_WMo<=26 z3N*4rDbh%ZQle2_lroJnG|16F(ZHfXi3VjFWN83no(4pN0u74Po8grIhTRJ^EFc*c z;%PZ7ix_-l&P?(LET?l!e5UBuxkXYKLsNw@ihfaPVa_aOJ+vrXCN-4f0ET2Qq42{D zU1X^fye7qd8Sz_%UvW&&em#p)*I|i5OL5o+BD{!IPQGzefF z6A7CA^0ai@Er@-d6dG`B1h^A~DXjQEu+jvEl1#%sOJU`!uo>QM_7a9@5dw^|o5F#m zm@p#hbC9APku$HIr9^j}vIAKFOyA@i3eWG3{ zIxkRwH)e>o$Wl5#EAS>B(n%yi{GG-=Cow+6Js|1g*nk3Pu^1&KZ#*i zWPmpauP=+>*!RU|WNPALEz=&74Hp(Y+fOta8&f7~dHkB9=1}c)nG}o)>uL zR9tHw-){+v+GIg47gH8jSD!TEIE+mN(~b$FrqQu&yfBbpT4A6?dCF07DBnmZd1wc5 zcpN1Xg$~@R?9cYZ#9nY9cF#SLkOF;ToELU1A@vPkZeC#YfsTc|7u!zCa}voj)=8Cb zBLVBc30-F7Lqv9*=q|v9*V9?g4{c*6TRYQBb{yNM<4Y0|bc5smJ~m}+xPPb}(|r+! zM`rGl%L#+T*r4ZICZ$guC0}yOcEiBYQ|sw@tMI2}1ET&c(Q#5g@?y{T!9(P#W zX0gKJ9*esW>9>Z5Q%*a@zrvmZWcT(e3tKW%O|TMg;kttU^v-rjDngP~o6NYe`? n)!s$2k|Nk1^+WgA*I)9HlN%6uHH!vYlm;tVdFUTlrBD9@kb}!4