django-syncr / syncr / app /

# -*- coding: utf-8 -*-
# vim: ai ts=4 sw=4 et
- finish the couple of methods that are not working yet
 - favorites
 - sync recent

import calendar
from datetime import datetime, timedelta
from time import strptime
import math
import gdata.geo
from django.core.exceptions import ObjectDoesNotExist
from syncr.picasaweb.models import Photo, Album, FavoriteList

class PicasawebSyncrError(Exception):


class PicasawebSyncr:

    """PicasaSyncr objects sync picasaweb photos, photo sets, and favorites
    lists with the Django backend.

    It does not currently sync user meta-data. Photo, PhotoSet, and
    FavoriteList objects include some meta-data, but are mostly Django
    ManyToManyFields to Photo objects.

    This app requires google gdata library. Available at:

    def __init__(self, email, password, cli_verbose=False):
        """Construct a new PicasawebSyncr object.

        Required arguments
          email: a google email (username)
          password: account password

        self.cli_verbose = cli_verbose = email
        gd_client = = email
        gd_client.password = password
        gd_client.source = 'django-syncr-picasaweb'
        self.gd_client = gd_client

    def getExifKey(self, exif_data, key):
            return exif_data[key]
            return ''

    def getPhotoTagList(self, username, albumname, gphoto_id):
        if self.cli_verbose:
            print '>>> getPhotoTagList', username, albumname, gphoto_id
        feed = \
                                    % (username, albumname, gphoto_id))
        return ' '.join(entry.title.text for entry in feed.entry)

    def _syncPhoto(self, photo_entry, username, albumname=None, refresh=False):
        """Synchronize a picasaweb photo with the Django backend.
        Required Arguments
          photo_entry: A google data api photo entry

        gphoto_id = photo_entry.gphoto_id.text

        # if self.cli_verbose:
        #    print "syncPhoto album %s id %s" % (albumname, gphoto_id)
        # if we're refreshing this data, then delete the Photo first...

        if refresh:
                p = Photo.objects.get(gphoto_id=gphoto_id)
            except ObjectDoesNotExist:

        if albumname is None:
            if self.cli_verbose:
                print 'Albumname unknown getAlbumFeed for', username
            feed = self.getAlbumFeed(username=username)
            album_id = photo_entry.albumid.text
            albums = [album for album in feed.entry
                      if album.gphoto_id.text == album_id]
            if len(albums) != 1:
                raise PicasawebSyncrError('No such album found for: %s'
                         % album_or_id)
            albumname = albums[0].name.text

        updated = datetime(*strptime(photo_entry.updated.text[:-4]
                            + '000Z', '%Y-%m-%dT%H:%M:%S.000Z')[:7])
            geo_latitude = photo_entry.geo.latitude()
            geo_longtitude = photo_entry.geo.longtitude()
            geo_latitude = ''
            geo_longtitude = ''

        default_dict = {  # 'square_url': urls['Square'],
                          # 'license':[0]['license'],
                          # 'exif_orientation': photo_entry.exif.,
                          # 'exif_software': photo_entry.exif.,
                          # 'exif_aperture': photo_entry.exif.,
                          # 'exif_metering_mode': ,
            'updated': updated,
            'gphoto_id': gphoto_id,
            'owner': username,
            'title': photo_entry.title.text,
            'description': photo_entry.summary.text or '',
            'taken_date': datetime(*strptime(photo_entry.timestamp.isoformat()[:-4]
                                    + '000Z', '%Y-%m-%dT%H:%M:%S.000Z'
            'photopage_url': photo_entry.GetAlternateLink().href,
            'geo_latitude': geo_latitude,
            'geo_longitude': geo_longtitude,
            'exif_model': photo_entry.exif.model\
                 and photo_entry.exif.model.text or '',
            'exif_make': photo_entry.exif.make\
                 and photo_entry.exif.make.text or '',
            'exif_exposure': photo_entry.exif.exposure\
                 and photo_entry.exif.exposure.text or '',
            'exif_iso': photo_entry.exif.iso\
                 and photo_entry.exif.iso.text or '',
            'exif_flash': photo_entry.exif.flash\
                 and photo_entry.exif.flash.text or '',
            'exif_focal_length': photo_entry.exif.focallength\
                 and photo_entry.exif.focallength.text or '',

        # 'exif_color_space': self.getExifKey(exif_data, 'Color Space'),

        (obj, created) = \
        if self.cli_verbose:
            status = created and 'created' or 'already exists (same)'
            delta = timedelta(seconds=3)
            #if obj.updated < updated:
            if not created and (updated > (obj.updated + delta)):
                status = 'updated'
            print 'Photo %s: %s' % (obj, status)

        #if not created and obj.updated < updated:
        if not created and (updated > (obj.updated + delta)):
            for (key, value) in default_dict.items():
                setattr(obj, key, value)
        return obj

    # TODO: syncPhoto

    # TODO: syncRecent

    def getAlbumFeed(self, username=None):
        ''' Retrieves the xml feed for a picasaweb user.'''
        #kwargs = {}
        #if username is not None:
        #    kwargs.setdefault('user', username)
        #feed = self.gd_client.GetUserFeed(**kwargs)
        if not username:
            return None
        feed = self.gd_client.GetUserFeed(user=username)
        return feed

    def syncAlbum(self, album, username=None):
        ''' Pull basic album info into local model, from the top-level feed.'''
        if isinstance(album, (int, long, str)):
            feed = self.getAlbumFeed(username=username)
            if isinstance(album, (int, long)):
                album_id = '%s' % album
                albums = [album for album in feed.entry
                          if album.gphoto_id.text == album_id]
                album_name = album
                albums = [album for album in feed.entry
                          if == album_name]
            if len(albums) != 1:
                raise PicasawebSyncrError(
                    'No such album found for: %s (found %s)' % (album, albums))
            album = albums[0]

        updated = datetime(*strptime(album.updated.text[:-4] + '000Z',
        gphoto_id = album.gphoto_id.text
        username = album.user.text
        nickname = album.nickname.text
        numphotos = int(album.numphotos.text)
        albumname =
        location = album.location.text
        if location is None:
            location = ''
        default_dict = {
            'gphoto_id': gphoto_id,
            'albumname': albumname,
            'owner': username,
            'nickname': nickname,
            'title': album.title.text,
            'description': album.summary.text or '',
            'location': location,
            'updated': updated,
            'access': album.access.text,
        (d_album, created) = \
        delta = timedelta(seconds=3)
        if self.cli_verbose:
            status = created and 'created' or 'already exists (same)'
            if not created and updated > (d_album.updated + delta):
                status = 'updated'
            print 'Album', album.title.text, status
        if not created and (updated > (d_album.updated + delta)):
            for (key, value) in default_dict.items():
                setattr(d_album, key, value)

    # get photo list

        feed_url = '/data/feed/api/user/%s/album/%s' % (username, albumname)
        feed_url += '?kind=photo'

    # if the PICASA_THUMBSIZES and PICASA_IMGMAX values are set in,
    # then include those settings in the feed url

        from django.conf import settings

    # PICASA_THUMBSIZES can be set in as a tuple of three
    # sizes. E.g. ( '72c', '160c', '288',)
    # This setting controls the sizes of the thumbnails provided

        if getattr(settings, 'PICASA_THUMBSIZES', False):
            feed_url += '&thumbsize=%s'\
                 % ','.join(settings.PICASA_THUMBSIZES)

    # PICASA_IMGMAX can be set in to allow content_url to be
    # used directly in an <img> tag if this is set to a size up to 800.

        if getattr(settings, 'PICASA_IMGMAX', False):
            feed_url += '&imgmax=%s' % settings.PICASA_IMGMAX

        photo_feed = self.gd_client.GetFeed(feed_url)

        for photo_entry in photo_feed.entry:
            photo = self._syncPhoto(photo_entry, username,
            if photo:
        feed_ids = set(photo_entry.gphoto_id.text for photo_entry in
        local_ids = set(e['gphoto_id'] for e in
        photo_diff = local_ids - feed_ids
        if self.cli_verbose:
            if photo_diff:
                print 'Sync deletes photos:', photo_diff
        for gphoto_id in photo_diff:
            photo =

    def syncAllAlbums(self, username=None):
        """Synchronize all photo albums for the picasaweb user."""

        if self.cli_verbose:
            print 'Sync all albums for', username or
        feed = self.getAlbumFeed(username=username)

        for album in feed.entry: