2012-11-12 16:12:43 -08:00
|
|
|
"""
|
|
|
|
A python class to manage fetching and caching of images by URL
|
|
|
|
"""
|
|
|
|
|
|
|
|
"""
|
|
|
|
Copyright 2012 Anthony Beville
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import sqlite3 as lite
|
|
|
|
import os
|
|
|
|
import datetime
|
|
|
|
import shutil
|
|
|
|
import tempfile
|
|
|
|
import urllib2, urllib
|
|
|
|
|
2012-11-27 10:01:40 -08:00
|
|
|
try:
|
|
|
|
from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest
|
|
|
|
from PyQt4.QtCore import QUrl, pyqtSignal, QObject, QByteArray
|
|
|
|
from PyQt4 import QtGui
|
|
|
|
except ImportError:
|
2012-11-27 10:14:53 -08:00
|
|
|
# No Qt, so define a few dummy QObjects to help us compile
|
|
|
|
class QObject():
|
2012-11-27 15:18:54 -08:00
|
|
|
def __init__(self,*args):
|
|
|
|
pass
|
2012-11-27 10:14:53 -08:00
|
|
|
class QByteArray():
|
|
|
|
pass
|
|
|
|
class pyqtSignal():
|
|
|
|
def __init__(self,*args):
|
|
|
|
pass
|
|
|
|
def emit(a,b,c):
|
|
|
|
pass
|
2012-11-12 16:12:43 -08:00
|
|
|
|
|
|
|
from settings import ComicTaggerSettings
|
|
|
|
|
|
|
|
class ImageFetcher(QObject):
|
|
|
|
|
|
|
|
fetchComplete = pyqtSignal( QByteArray , int)
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self ):
|
|
|
|
QObject.__init__(self)
|
|
|
|
|
|
|
|
self.settings_folder = ComicTaggerSettings.getSettingsFolder()
|
|
|
|
self.db_file = os.path.join( self.settings_folder, "image_url_cache.db" )
|
|
|
|
self.cache_folder = os.path.join( self.settings_folder, "image_cache" )
|
|
|
|
|
|
|
|
if not os.path.exists( self.db_file ):
|
|
|
|
self.create_image_db()
|
|
|
|
|
2012-11-20 00:57:12 -08:00
|
|
|
def clearCache( self ):
|
|
|
|
os.unlink( self.db_file )
|
|
|
|
if os.path.isdir( self.cache_folder ):
|
|
|
|
shutil.rmtree( self.cache_folder )
|
|
|
|
|
|
|
|
|
2012-11-12 16:12:43 -08:00
|
|
|
def fetch( self, url, user_data=None, blocking=False ):
|
|
|
|
"""
|
|
|
|
If called with blocking=True, this will block until the image is
|
|
|
|
fetched.
|
|
|
|
|
|
|
|
If called with blocking=False, this will run the fetch in the
|
|
|
|
background, and emit a signal when done
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.user_data = user_data
|
|
|
|
self.fetched_url = url
|
|
|
|
|
|
|
|
# first look in the DB
|
|
|
|
image_data = self.get_image_from_cache( url )
|
|
|
|
|
|
|
|
if blocking:
|
|
|
|
if image_data is None:
|
|
|
|
image_data = urllib.urlopen(url).read()
|
|
|
|
# save the image to the cache
|
|
|
|
self.add_image_to_cache( self.fetched_url, image_data )
|
|
|
|
return image_data
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
# if we found it, just emit the signal asap
|
|
|
|
if image_data is not None:
|
|
|
|
self.fetchComplete.emit( QByteArray(image_data), self.user_data )
|
|
|
|
return
|
|
|
|
|
|
|
|
# didn't find it. look online
|
|
|
|
self.nam = QNetworkAccessManager()
|
|
|
|
self.nam.finished.connect(self.finishRequest)
|
|
|
|
self.nam.get(QNetworkRequest(QUrl(url)))
|
|
|
|
|
|
|
|
#we'll get called back when done...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def finishRequest(self, reply):
|
|
|
|
|
|
|
|
# read in the image data
|
|
|
|
image_data = reply.readAll()
|
|
|
|
|
|
|
|
# save the image to the cache
|
|
|
|
self.add_image_to_cache( self.fetched_url, image_data )
|
|
|
|
|
|
|
|
self.fetchComplete.emit( QByteArray(image_data), self.user_data )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_image_db( self ):
|
|
|
|
|
|
|
|
# this will wipe out any existing version
|
|
|
|
open( self.db_file, 'w').close()
|
|
|
|
|
|
|
|
# wipe any existing image cache folder too
|
|
|
|
if os.path.isdir( self.cache_folder ):
|
2012-11-17 16:32:01 -08:00
|
|
|
shutil.rmtree( self.cache_folder )
|
2012-11-12 16:12:43 -08:00
|
|
|
os.makedirs( self.cache_folder )
|
|
|
|
|
|
|
|
con = lite.connect( self.db_file )
|
|
|
|
|
|
|
|
# create tables
|
|
|
|
with con:
|
|
|
|
|
|
|
|
cur = con.cursor()
|
|
|
|
|
|
|
|
cur.execute("CREATE TABLE Images(" +
|
|
|
|
"url TEXT," +
|
|
|
|
"filename TEXT," +
|
|
|
|
"timestamp TEXT," +
|
|
|
|
"PRIMARY KEY (url) )"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def add_image_to_cache( self, url, image_data ):
|
|
|
|
|
|
|
|
con = lite.connect( self.db_file )
|
|
|
|
|
|
|
|
with con:
|
|
|
|
|
|
|
|
cur = con.cursor()
|
|
|
|
|
|
|
|
timestamp = datetime.datetime.now()
|
|
|
|
|
|
|
|
tmp_fd, filename = tempfile.mkstemp(dir=self.cache_folder, prefix="img")
|
|
|
|
f = os.fdopen(tmp_fd, 'w+b')
|
|
|
|
f.write( image_data )
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
cur.execute("INSERT or REPLACE INTO Images VALUES( ?, ?, ? )" ,
|
|
|
|
(url,
|
|
|
|
filename,
|
|
|
|
timestamp )
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_image_from_cache( self, url ):
|
|
|
|
|
|
|
|
con = lite.connect( self.db_file )
|
|
|
|
with con:
|
|
|
|
cur = con.cursor()
|
|
|
|
|
|
|
|
cur.execute("SELECT filename FROM Images WHERE url=?", [ url ])
|
|
|
|
row = cur.fetchone()
|
|
|
|
|
|
|
|
if row is None :
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
filename = row[0]
|
|
|
|
image_data = None
|
|
|
|
|
|
|
|
try:
|
|
|
|
with open( filename, 'rb' ) as f:
|
|
|
|
image_data = f.read()
|
|
|
|
f.close()
|
|
|
|
except IOError as e:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return image_data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|