def __init__(self, logger, thumbnailSize, storeLocally, gui):
        super(RemotePicturesHandler, self).__init__()

        self.logger = logger
        self._thumbnailSize = thumbnailSize
        self._storeLocally = storeLocally 
        self._gui = gui
        self._storage = RemotePicturesStorage(self, self.logger)
        self._currentlyDownloading = set() # set of currently downloading pictures
        self._lock = loggingMutex(u"Remote Pictures Handler", qMutex=True, logging=get_settings().get_verbose())
        
        self._createThumbnailAndAddCategory = AsyncCall(self,
                                                        self.logger,
                                                        self._createThumbnail,
                                                        self._addCategoryAndCloseFile)
        
        self._createAndChangeThumbnail = AsyncCall(self,
                                                   self.logger,
                                                   self._createThumbnail,
                                                   self._changeThumbnail)
        
        self._loadPictures.connect(self._loadPicturesSlot)
        self._processRemotePicture.connect(self._processRemotePictureSlot)
        self._checkCategory.connect(self._checkCategorySlot)
        self._thumbnailSizeChanged.connect(self._thumbnailSizeChangedSlot)
        self._storeLocallyChanged.connect(self._storeLocallyChangedSlot)
class RemotePicturesHandler(QObject):
    addCategory = pyqtSignal(object, object, int) # category, thumbnail path, thumbnail size
    categoryThumbnailChanged = pyqtSignal(object, object, int) # category, thumbnail path, thumbnail size
    categoriesChanged = pyqtSignal()
    # cat, picID, picRow, hasPrev, hasNext
    displayImageInGui = pyqtSignal(object, int, list, bool, bool)

    _loadPictures = pyqtSignal()
    _processRemotePicture = pyqtSignal(str, object) # data, ip
    _checkCategory = pyqtSignal(object)
    _thumbnailSizeChanged = pyqtSignal(int)
    _storeLocallyChanged = pyqtSignal(bool)
    
    def __init__(self, logger, thumbnailSize, storeLocally, gui):
        super(RemotePicturesHandler, self).__init__()

        self.logger = logger
        self._thumbnailSize = thumbnailSize
        self._storeLocally = storeLocally 
        self._gui = gui
        self._storage = RemotePicturesStorage(self, self.logger)
        self._currentlyDownloading = set() # set of currently downloading pictures
        self._lock = loggingMutex(u"Remote Pictures Handler", qMutex=True, logging=get_settings().get_verbose())
        
        self._createThumbnailAndAddCategory = AsyncCall(self,
                                                        self.logger,
                                                        self._createThumbnail,
                                                        self._addCategoryAndCloseFile)
        
        self._createAndChangeThumbnail = AsyncCall(self,
                                                   self.logger,
                                                   self._createThumbnail,
                                                   self._changeThumbnail)
        
        self._loadPictures.connect(self._loadPicturesSlot)
        self._processRemotePicture.connect(self._processRemotePictureSlot)
        self._checkCategory.connect(self._checkCategorySlot)
        self._thumbnailSizeChanged.connect(self._thumbnailSizeChangedSlot)
        self._storeLocallyChanged.connect(self._storeLocallyChangedSlot)
    
    def loadPictures(self):
        self._loadPictures.emit()
    @loggingSlot()
    def _loadPicturesSlot(self):
        categories = self._storage.getCategories(alsoEmpty=False)
        for row in categories:
            category = row[RemotePicturesStorage.CAT_TITLE_COL]
            thumbnailPath = row[RemotePicturesStorage.CAT_THUMBNAIL_COL]
            
            picFile = None
            picURL = None
            if not thumbnailPath or not os.path.exists(thumbnailPath):
                thumbnailPath = None
                
                # create thumbnail from first image
                _picID, picRow = self._storage.getLatestPicture(category)
                picFile = picRow[RemotePicturesStorage.PIC_FILE_COL]
                if picFile and os.path.exists(picFile):
                    picFile = picRow[RemotePicturesStorage.PIC_FILE_COL]
                else:
                    picURL = picRow[RemotePicturesStorage.PIC_URL_COL]
                
            self._addCategory(category,
                              thumbnailPath=thumbnailPath,
                              imageFile=picFile,
                              imageURL=picURL)
    
    def finish(self):
        pass
    
    def _getPicturesDirectory(self):
        picDir = get_settings().get_config("remote_pictures")
        if not os.path.exists(picDir):
            os.makedirs(picDir)
        return picDir
    
    def _createPictureFile(self, category, ext='.jpg'):
        prefix = sanitizeForFilename(category)
        return tempfile.NamedTemporaryFile(suffix=ext, prefix=prefix, dir=self._getPicturesDirectory(), delete=False)
    
    def _createThumbnail(self, inFile, inPath, inURL, inImage, category):
        """Called asynchronously"""
        try:
            fileName = None
            
            # if category already has a thumbnail, overwrite
            curPath = self._storage.getCategoryThumbnail(category)
            if curPath is not None:
                try:
                    # check if we can write the file
                    open(curPath, 'wb').close()
                    fileName = curPath
                except:
                    pass
            
            if fileName is None:
                outFile = self._createPictureFile(category)
                fileName = outFile.name
                outFile.close()
            
            oldImage = None
            imageData = None
            if inImage:
                oldImage = inImage
            elif inFile:
                imageData = inFile.read()
            elif inPath and os.path.exists(inPath):
                with contextlib.closing(open(inPath, 'rb')) as inFile:
                    imageData = inFile.read()
            else:
                imageData = urllib2.urlopen(inURL.encode('utf-8')).read()
            
            if oldImage is None:
                oldImage = QImage.fromData(imageData)
            if oldImage.width() > CategoriesModel.MAX_THUMBNAIL_SIZE or oldImage.height() > CategoriesModel.MAX_THUMBNAIL_SIZE:
                newImage = oldImage.scaled(CategoriesModel.MAX_THUMBNAIL_SIZE,
                                           CategoriesModel.MAX_THUMBNAIL_SIZE,
                                           Qt.KeepAspectRatio,
                                           Qt.SmoothTransformation)
            else:
                # no up-scaling here
                newImage = oldImage
            newImage.save(fileName, 'jpeg')
            return fileName, inFile, category
        except:
            self.logger.exception("Error trying to create thumbnail for category")
            return None, inFile, category
            
    def __setCategoryThumbnail(self, thumbnailPath, imageFile, category):
        if thumbnailPath is not None:
            # store thumbnail path in database
            self._storage.setCategoryThumbnail(category, thumbnailPath)
        if imageFile is not None:
            imageFile.close()
    
    @loggingSlot(object)
    def _addCategoryAndCloseFile(self, aTuple):
        """Called synchronously, with result of _createThumbnail"""
        thumbnailPath, imageFile, category = aTuple
        self.__setCategoryThumbnail(thumbnailPath, imageFile, category)
        self.addCategory.emit(category, thumbnailPath, self._thumbnailSize)
            
    @loggingSlot(object)
    def _changeThumbnail(self, aTuple):
        """Called synchronously, with result of _createThumbnail"""
        thumbnailPath, imageFile, category = aTuple
        self.__setCategoryThumbnail(thumbnailPath, imageFile, category)
        self.categoryThumbnailChanged.emit(category, thumbnailPath, self._thumbnailSize)
            
    def _addCategory(self, category, thumbnailPath=None, imageFile=None, imageURL=None):
        # cache category image
        closeImmediately = True
        try:
            if thumbnailPath != None:
                self.addCategory.emit(category, thumbnailPath, self._thumbnailSize)
            elif (imageFile and os.path.exists(imageFile)) or imageURL is not None:
                # create thumbnail asynchronously, then close imageFile
                self._createThumbnailAndAddCategory(None, imageFile, imageURL, None, category)
                closeImmediately = False
            else:
                raise Exception("No image path specified.")
        finally:
            if closeImmediately and imageFile != None:
                imageFile.close()

    def thumbnailSizeChanged(self, newValue):
        self._thumbnailSizeChanged.emit(newValue)
    @loggingSlot(int)
    def _thumbnailSizeChangedSlot(self, newValue):
        self._thumbnailSize = newValue
        
    def storeLocallyChanged(self, newValue):
        self._storeLocallyChanged.emit(newValue)
    @loggingSlot(bool)
    def _storeLocallyChangedSlot(self, newValue):
        self._storeLocally = newValue

    def checkCategory(self, cat):
        self._checkCategory.emit(cat)
    @loggingSlot(object)
    def _checkCategorySlot(self, cat):
        cat = convert_string(cat)
        if not self._storage.hasCategory(cat):
            self._storage.addEmptyCategory(cat)
            self.categoriesChanged.emit()

    def _addPicture(self, imageFile, imagePath, url, category, description, sender):
        if category == None:
            category = PrivacySettings.NO_CATEGORY

        catAdded = self._storage.addPicture(category,
                                            url if url else None,
                                            description if description else None,
                                            imagePath,
                                            time(),
                                            None,
                                            sender if sender else None)
        
        if catAdded:
            if imageFile is not None:
                imageFile.seek(0)
            # image file will be closed after thumbnail was created
            self._createThumbnailAndAddCategory(imageFile, imagePath, url, None, category)
        elif imageFile is not None:
            imageFile.close()
            
        if self._gui.isShowingCategory(category):
            # if category is open, display image immediately
            self._displayImage(category, self._storage.getLatestPicture(category))
    
    def _hasPicture(self, cat, url):
        return self._storage.hasPicture(cat, url)
            
    def _displayImage(self, category, result):
        picID, picRow = result
        if picID is None:
            self.logger.error("Cannot display picture %d (picture not found).", picID)
            
        self.displayImageInGui.emit(category,
                                    picID,
                                    list(picRow),
                                    self._storage.hasPrevious(category, picID),
                                    self._storage.hasNext(category, picID))
        self._storage.seenPicture(picID)
        
    @loggingSlot(QThread, object)
    def _errorDownloadingPicture(self, thread, err):
        self.logger.error("Error downloading picture from url %s: %s", convert_string(thread.url), err)
        thread.deleteLater()
        
    @loggingFunc
    def _downloadedPicture(self, category, description, sender, thread, url):
        if self._hasPicture(category, url):
            self.logger.warning("Picture already in storage: %s", url)
            return
        
        name = "New Remote Picture"
        if category != None:
            name = name + " in category %s" % category
            
        # create temporary image file to display in notification
        url = convert_string(url)
        thread.target.flush()
        
        displayNotification(name, description, self.logger, thread.target.name)
        
        self._addPicture(thread.target,
                         thread.target.name if self._storeLocally else None,
                         url,
                         category,
                         description,
                         sender)
          
    def _extractPicture(self, url, category, description, sender):
        if not self._hasPicture(category, url):
            ext = os.path.splitext(urlparse(url.encode('utf-8')).path)[1]
            if self._storeLocally:
                target = self._createPictureFile(category, ext=ext)
            else:
                target = tempfile.NamedTemporaryFile(suffix=ext)
            
            downloadThread = DownloadThread(self, self.logger, url, target)
            downloadThread.success.connect(partial(self._downloadedPicture, category, description, sender))
            downloadThread.error.connect(self._errorDownloadingPicture)
            downloadThread.finished.connect(downloadThread.deleteLater)
            downloadThread.start()
        else:
            self.logger.debug("Remote Pics: Downloaded this url before, won't do it again: %s", url)
        
    def processRemotePicture(self, value, ip):
        self._processRemotePicture.emit(value, ip)
    @loggingSlot(str, object)
    def _processRemotePictureSlot(self, value, ip):
        value = convert_raw(value)
        ip = convert_string(ip)
        with contextlib.closing(StringIO(value)) as strIn:
            reader = csv.reader(strIn, delimiter = ' ', quotechar = '"')
            valueList = [aValue.decode('utf-8') for aValue in reader.next()]
            url = valueList[0]
            desc = None
            cat = PrivacySettings.NO_CATEGORY
            if len(valueList) > 1:
                desc = valueList[1]
            if len(valueList) > 2:
                cat = valueList[2]
        
        self._lock.lock()
        try:
            if url in self._currentlyDownloading:
                self.logger.debug("Already downloading picture %s", url)
                return
            self._currentlyDownloading.add(url)
        finally:
            self._lock.unlock()
        
        self._extractPicture(url, cat, desc, get_peers().getPeerID(pIP=ip))    
    
    def getCategories(self, alsoEmpty):
        return self._storage.getCategories(alsoEmpty)
    
    def getCategoryNames(self, alsoEmpty):
        return self._storage.getCategoryNames(alsoEmpty)
    
    def willIgnorePeerAction(self, category, url):
        self._lock.lock()
        try:
            if url in self._currentlyDownloading:
                return True
        finally:
            self._lock.unlock()
        return self._hasPicture(category, url)

    ################# PUBLIC SLOTS ##################
        
    @loggingSlot(object)   
    def openCategory(self, category):
        category = convert_string(category)
        if not self._storage.hasCategory(category):
            self.logger.error("Cannot open category %s (category not found).", category)
            return
        
        self._displayImage(category, self._storage.getLatestPicture(category))
    
    @loggingSlot(object, int)
    def displayPrev(self, cat, curID):
        cat = convert_string(cat)
        self._displayImage(cat, self._storage.getPreviousPicture(cat, curID))
    
    @loggingSlot(object, int)
    def displayNext(self, cat, curID):
        cat = convert_string(cat)
        self._displayImage(cat, self._storage.getNextPicture(cat, curID))    

    @loggingSlot(object, object, object)
    def pictureDownloaded(self, category, url, picData):
        if self._storeLocally:
            ext = os.path.splitext(urlparse(url.encode('utf-8')).path)[1]
            with contextlib.closing(self._createPictureFile(category, ext)) as picFile:
                picFile.write(picData)
                self._storage.setPictureFile(category, url, picFile.name)
    
    @loggingSlot(object, QImage)
    def setCategoryThumbnail(self, category, image):
        self._createAndChangeThumbnail(None, None, None, image, category)