# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.
#
# Author: Benjamin Kampmann <benjamin@fluendo.com>

from elisa.plugins.database.database_parser import DatabaseParser
from elisa.plugins.database.models import File, MusicTrack, MusicAlbum, \
        Artist, Tag, Image, PhotoAlbum, Video

from elisa.core.media_uri import MediaUri

from elisa.core import common

from elisa.extern.storm_wrapper import store
from storm.locals import create_database

from elisa.plugins.database.database_updater import SCHEMA

from twisted.trial.unittest import TestCase
from twisted.internet import defer, task
from twisted.python.failure import Failure

import datetime, os, tempfile


class ConfigMock(dict):
    def get_section(self, section_name, default=None):
        return {}


class ApplicationMock(object):
    config = ConfigMock()


class ResourceManagerMock(object):
    def register_component(data):
        pass

    def get(self, uri):
        return None, defer.succeed(None)


class DBMixin(object):

    def setUp(self):
        self.patch_application()
        self.db = create_database('sqlite:')
        self.store = store.DeferredStore(self.db)
        dfr = self.store.start()
        dfr.addCallback(lambda x: self._create_tables())
        dfr.addCallback(self._set_store_to_application)
        return dfr

    def _set_store_to_application(self, result=None):
        common.application.store = self.store
    
    def tearDown(self):
        self.unpatch_application()
        return self.store.stop()

    def patch_application(self):
        self.application = common.application
        common.application = ApplicationMock()
        common.application.resource_manager = ResourceManagerMock()

    def unpatch_application(self):
        common.application = self.application

    def _execute(self, string):
        return self.store.execute(string)

    def _execute_many(self, queries):
        def go_through(queries):
            for query in queries:
                dfr = self._execute(query)
                yield dfr
        dfr = task.coiterate(go_through(queries))
        return dfr

    def _create_tables(self):
        # create the tables
        return self._execute_many(SCHEMA).addCallback(lambda x:
                self.store.commit())



class MetadataDummy(object):
    def __init__(self):
        self.added = []
        self.paused = False
    def initialize(self):
        return defer.succeed(self)
    def get_metadata(self, file):
        if self.paused:
            dfr = defer.Deferred()
        else:
            dfr = defer.succeed({})
        self.added.append( (file, dfr) )
        return dfr
    def clean(self):
        return defer.succeed(True)

class Dummy(object):pass

class ParserSetupMixin(DBMixin):
    def setUp(self):
        def parser_done(parser):
            self.parser = parser
            self.parser.store = self.store

            self.metadata = MetadataDummy()

            # overwrite some parts
            metadata = self.parser.metadata
            self.parser.metadata = self.metadata

            return metadata.clean()

        self.files = []

        dfr = super(ParserSetupMixin, self).setUp()
        dfr.addCallback(lambda x: DatabaseParser.create({}))
        dfr.addCallback(parser_done)
        return dfr

    def tearDown(self):
        
        dirs = []
        for dir, file in self.files:
            os.unlink(file)
            if dir:
                dirs.append(dir)

        self.files = []

        for dir in dirs:
            os.removedirs(dir)

        def parser_cleaned(result):
            return super(ParserSetupMixin, self).tearDown()
        
        if self.parser is None:
            return parser_cleaned(True)

        dfr = self.parser.clean()
        dfr.addCallback(parser_cleaned)
        return dfr

    def ensure_file(self, file, dirs=[]):
        directories = None
        if dirs:
            directories = os.path.join(*dirs)
            file = os.path.join(directories, file)

            try:
                os.makedirs(directories)
            except Error:
                pass
        
        self.files.append( (directories, file) )
        writer = open(file, 'w')
        return os.path.abspath(file)
 

class TestGeneralFileParsing(ParserSetupMixin, TestCase):

    def test_modification_time_the_same(self):
        fd, tmp_path = tempfile.mkstemp()

        def check(res):
            self.failUnless(self.metadata.added)
            os.close(fd)
            os.unlink(tmp_path)

        def test(res):
            dummy = Dummy()
            dummy.mtime = 13
            dummy.uri = MediaUri({'scheme':'file', 'path': tmp_path})
            root = Dummy()
            root.section = "foo"
            root.root_uri = dummy.uri
            return self.parser.query_model(dummy, root)

        file = File()
        file.path = u"/test41"
        file.modification_time = 13
        dfr = self.store.add(file)
        return dfr.addCallback(test).addCallback(check)

    def test_newer_modification_time(self):

        def check(res):
            self.assertEquals(len(self.metadata.added), 1)

        def test(res):
            dummy = Dummy()
            dummy.mtime = 19
            dummy.uri = MediaUri('file:///test42')
            source = Dummy()
            source.files_failed = []
            source.root_uri = dummy.uri
            source.section = "somewhere"
            return self.parser.query_model(dummy, source)

        file = File()
        file.path = u"/test42"
        file.modification_time = 13
        file.source = None
        dfr = self.store.add(file)
        return dfr.addCallback(test).addCallback(check)

    def test_file_not_yet_there(self):
        def check(res):
            self.assertEquals(len(self.metadata.added), 1)

        dummy = Dummy()
        dummy.mtime = 19
        dummy.uri = MediaUri('file:///test4') 

        stat = Dummy()
        stat.files_failed = []
        stat.root_uri = MediaUri('file:///test')
        stat.section = "something"
        return self.parser.query_model(dummy, stat).addCallback(check)

    def test_update_modification_time(self):
        file = Dummy()
        file.path = 'test'
        file.modification_time = -1
        self.parser.update_modification_time(Failure(NotImplementedError()), file)
        self.failIf(file.modification_time == -1, "Modifcation time not changed")

    def test_set_mime_type(self):

        def check(res, file):
            self.assertEquals(file.mime_type, u'test')

        def parse_it(track, file):
            tags = {'mime_type':u'test'}

            return self.parser.parse_tags(tags, file).addCallback(check, file)

        file = File()
        file.path = u"testcase"
        return self.store.add(file).addCallback(parse_it, file)

    def test_deleted(self):

        class DummySource(object):pass

        def mark_as_deleted(result, source):
            return self.parser.mark_deleted(source)

        def check_deleted(result, file):
            self.assertTrue(file.deleted, "Was not deleted")

        file = File()
        file.path = u"testcase"
        file.source = u'/tmp'

        return self.store.add(file).addCallback(mark_as_deleted, u'/tmp'
                ).addCallback(check_deleted, file)

class TestRemoving(ParserSetupMixin, TestCase):
    """
    Test whether the removal of files works properly.
    """

    def add_items(self, items):
        def iterator(items):
            for item in items:
                yield self.store.add(item)
        dfr = task.coiterate(iterator(items))
        return dfr

    def test_simple_removing_per_source(self):

        def remover(result):
            return self.parser.delete_files(u"/tmp")

        def got_files(files):
            self.assertEquals(len(files), 0)

        def check(result):
            dfr = self.store.find(File)
            dfr.addCallback( lambda x: x.all())
            dfr.addCallback(got_files)
            return dfr

        file = File()
        file.path = u"test"
        file.source = u"/tmp"
        file.deleted = 1

        return self.store.add(file).addCallback(remover).addCallback(check)

    def test_remove_only_of_source(self):

        def remover(result):
            return self.parser.delete_files(u"/tmp")

        def got_files(files):
            self.assertEquals(files, [u't4'])

        def check(result):
            dfr = self.store.find(File)
            dfr.addCallback( lambda x: x.values(File.path))
            dfr.addCallback(got_files)
            return dfr
         
        files = []

        file1 = File()
        file1.path = u"test"
        file1.source = u"/tmp"
        file1.deleted = 1

        files.append(file1)

        file2 = File()
        file2.path = u"t2"
        file2.source = u"/tmp"
        file2.deleted = 1

        files.append(file2)

        file3 = File()
        file3.path = u"t3"
        file3.source = u"/tmp"
        file3.deleted = 1
        
        files.append(file3)

        other_file = File()
        other_file.path = u"t4"
        other_file.source = u"yeah"
        other_file.deleted = 1

        files.append(other_file)

        return self.add_items(files).addCallback(remover).addCallback(check)

    def test_dont_remove_non_marked(self):

        def remover(result):
            return self.parser.delete_files(u"/tmp")

        def got_files(files):
            self.assertEquals(files, [u't3'])

        def check(result):
            dfr = self.store.find(File)
            dfr.addCallback( lambda x: x.values(File.path))
            dfr.addCallback(got_files)
            return dfr
         
        files = []

        file1 = File()
        file1.path = u"test"
        file1.source = u"/tmp"
        file1.deleted = 1

        files.append(file1)

        file2 = File()
        file2.path = u"t2"
        file2.source = u"/tmp"
        file2.deleted = 1

        files.append(file2)

        file3 = File()
        file3.path = u"t3"
        file3.source = u"/tmp"
        # NOT MARKED !!!
        file3.deleted = 0
        
        files.append(file3)

        return self.add_items(files).addCallback(remover).addCallback(check)

    def test_remove_related(self):
 
        def remover(result):
            return self.parser.delete_files(u"/tmp")

        items = []

        for i, cls in enumerate((MusicTrack, Image, Video)):
            file_obj = File()
            file_obj.path = u'test%d' % i
            file_obj.source = u'/tmp'
            file_obj.deleted = 1
            items.append(file_obj)
            obj = cls()
            obj.file_path = file_obj.path
            items.append(obj)

        expected =  ((File, 0), (MusicTrack, 0), (Image, 0), (Video, 0))

        dfr = self.add_items(items)
        dfr.addCallback(remover)
        dfr.addCallback(self.check_count_for_classes, expected)
        return dfr

    def check_count_for_classes(self, result, klasses):
        def check_count(count, klass, reference):
            msg = "%s has an unexpected number of entries" % klass
            self.assertEquals(count, reference, msg)

        def iter_count(klasses):
            for klass, count in klasses:
                dfr = self.store.find(klass)
                dfr.addCallback(lambda x: x.count())
                dfr.addCallback(check_count, klass, count)
                yield dfr

        dfr = task.coiterate(iter_count(klasses))
        return dfr

    def test_remove_related_album_and_artist(self):
 
        def remover(result):
            return self.parser.delete_files(u"/tmp")

        def set_up():

            file = File()
            file.path = u"test"
            file.source = u"/tmp"
            file.deleted = 1
            yield self.store.add(file)

            album = MusicAlbum()
            album.name = u"Super troopers"
            yield self.store.add(album)

            artist = Artist()
            artist.name = u"Yuppie"
            yield self.store.add(artist)

            track = MusicTrack()
            track.file_path = u"test"
            track.album_name = u"Super troopers"
            yield self.store.add(track)

            yield track.artists.add(artist)

            yield self.store.commit()

        dfr = task.coiterate(set_up())

        dfr.addCallback(remover)

        klasses = ((File, 0), (MusicTrack, 0), (MusicAlbum, 0), (Artist, 0))
        dfr.addCallback(self.check_count_for_classes, klasses)
        return dfr

    def test_remove_related_album_and_artist_still_tracks(self):
 
        def remover(result):
            return self.parser.delete_files(u"/tmp")

        def set_up():

            file = File()
            file.path = u"test"
            file.source = u"/tmp"
            file.deleted = 1
            yield self.store.add(file)

            album = MusicAlbum()
            album.name = u"Super troopers"
            yield self.store.add(album)

            artist = Artist()
            artist.name = u"Yuppie"
            yield self.store.add(artist)

            track = MusicTrack()
            track.file_path = u"test"
            track.album_name = u"Super troopers"
            yield self.store.add(track)

            yield track.artists.add(artist)

            # this track is not going to be removed, so there is still a track
            # existing in the album for that artist
            track2 = MusicTrack()
            track2.file_path = u"tested2"
            track2.album_name = u"Super troopers"
            yield self.store.add(track2)

            yield track2.artists.add(artist)

            yield self.store.commit()

        dfr = task.coiterate(set_up())

        dfr.addCallback(remover)

        klasses = ((MusicTrack, 1),
                    (File, 0),
                    (MusicAlbum, 1),
                    (Artist, 1))
        dfr.addCallback(self.check_count_for_classes, klasses)
        return dfr

    def test_remove_related_photo_album(self):
 
        def remover(result):
            return self.parser.delete_files(u"/tmp")

        def set_up():

            file = File()
            file.path = u"test"
            file.source = u"/tmp"
            file.deleted = 1
            yield self.store.add(file)

            album = PhotoAlbum()
            album.name = u"Super troopers"
            yield self.store.add(album)
        
            image = Image()
            image.file_path = u"test"
            image.album_name = u"Super troopers"
            yield self.store.add(image)

            yield self.store.commit()

        dfr = task.coiterate(set_up())

        dfr.addCallback(remover)

        klasses = ((File, 0), (Image, 0), (PhotoAlbum, 0))
        dfr.addCallback(self.check_count_for_classes, klasses)
        return dfr

    def test_remove_related_album_still_photos(self):
 
        def remover(result):
            return self.parser.delete_files(u"/tmp")

        def set_up():
            file = File()
            file.path = u"test"
            file.source = u"/tmp"
            file.deleted = 1
            yield self.store.add(file)

            album = PhotoAlbum()
            album.name = u"Super troopers"
            yield self.store.add(album)
        
            image = Image()
            image.file_path = u"test"
            image.album_name = u"Super troopers"
            yield self.store.add(image)

            image2 = Image()
            image2.file_path = u"else"
            image2.album_name = u"Super troopers"
            yield self.store.add(image2)

            yield self.store.commit()

        dfr = task.coiterate(set_up())

        dfr.addCallback(remover)

        klasses = ((File, 0), (Image, 1), (PhotoAlbum, 1))
        dfr.addCallback(self.check_count_for_classes, klasses)
        return dfr

class TestFindingRightTypeParsing(ParserSetupMixin, TestCase):
    """
    check if the parser at least starts with the right entries in the database
    for the given file_type (the least gstreamer can give us)
    """

    def test_image(self):
        """
        check whether file_type:image leads to having an image entry
        """
        path = self.ensure_file(u'test')

        def check(res, path):

            def got_image(image):
                self.failUnless(image, "Image not in DB")
            
            return self.store.get(Image, path).addCallback(got_image)

        def parse_it(res, file):
            tags = {'file_type' : 'image'}
            return self.parser.parse_tags(tags, file, section='pictures')

        file = File()
        file.path = path
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check, path)

    def test_video(self):
        """
        check whether file_type:video leads to having an video entry
        """

        path = self.ensure_file(u'test')

        def check(res):

            def got_video(video):
                self.failUnless(video, "Video not in DB")
            
            return self.store.get(Video, u'test').addCallback(got_video)

        def parse_it(res, file):
            tags = {'file_type' : 'video'}
            return self.parser.parse_tags(tags, file, section='video')

        file = File()
        file.path = u"test"
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check)

    def test_audio(self):
        """
        test whether file_type:audio leads to having the musictrack entry
        """

        def check(res):

            def got_track(track):
                self.failUnless(track, "MusicTrack not in DB")
            
            return self.store.get(MusicTrack, u'test').addCallback(got_track)

        def parse_it(res, file):
            tags = {'file_type' : 'audio'}
            return self.parser.parse_tags(tags, file, section='music')

        file = File()
        file.path = u"test"
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check)

class TestMusicParsing(ParserSetupMixin, TestCase):

    def test_with_new_album_and_new_artist(self):

        def check(res):

            def got_album(album):
                self.assertEquals(album.name, 'music')
                release_date = datetime.datetime(2004, 11, 19)
                self.assertEquals(album.release_date, release_date)

            def got_artists(artists):
                self.assertEquals(artists, ['madonna'])

            def got_track(track):
                self.assertEquals(track.title, u'testie')
                self.assertEquals(track.file_path, u'test')
                self.assertEquals(track.genre, u'cheese')
                self.assertEquals(track.duration, 1000)
                self.assertEquals(track.track_number, 10)
                return track.album.addCallback(got_album).addCallback(
                        lambda x: track.artists.values(Artist.name)).addCallback(got_artists)
            
            return self.store.get(MusicTrack, u'test').addCallback(got_track)

        def parse_it(res, file):
            tags = {'artist' : 'madonna',
                    'file_type' : 'audio',
                    'album' : 'music',
                    'song' : 'testie',
                    'duration' : 1000,
                    "genre" : "cheese",
                    "date" : 1100818800.0,
                    'track' : 10}
            return self.parser.parse_tags(tags, file, section='music')

        file = File()
        file.path = u"test"
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check)


    def test_with_new_album_and_known_artist(self):

        def add_album(res):
            album = MusicAlbum()
            album.name = u'music'
            return self.store.add(album)

        def check(res, reference):
           
            def got_artists(artists):
                self.assertEquals(artists, ['madonna'])

            def got_album(album, reference):
                self.failUnless(album is reference)

            def got_track(track, reference):
                self.assertEquals(track.title, u'testie')
                return track.artists.values(Artist.name).addCallback(
                        got_artists).addCallback(lambda x:
                        track.album).addCallback(got_album, reference)
            
            return self.store.find(MusicTrack, MusicTrack.file_path == \
                        u'testcase').addCallback(lambda x:
                        x.one()).addCallback(got_track, reference)

        def parse_it(album, file):
            tags = {'artist' : 'madonna',
                    'file_type' : 'audio',
                    'album' : 'music',
                    'song' : 'testie',
                    'track' : 10}

            dfr = self.parser.parse_tags(tags, file, section='music')
            dfr.addCallback(check, album)
            return dfr

        file = File()
        file.path = u"testcase"
        return self.store.add(file).addCallback(add_album).addCallback(
                parse_it, file)


    def test_reuse_album_and_artist(self):

        def add_artist(album):
            artist = Artist()
            artist.name = u"madonna"
            return self.store.add(artist).addCallback(lambda x: (x,
            album))

        def add_album(res):
            album = MusicAlbum()
            album.name = u'music'
            return self.store.add(album).addCallback(add_artist)

        def check(result, ref_artist, ref_album):

            def got_album(album, ref):
                self.failUnless(album is ref, "Album not reused")
            
            def got_track(track, ref_art, ref_alb):
                self.assertEquals(track.title, u'testie')
                return track.album.addCallback(got_album,
                        ref_album).addCallback(lambda x:
                        track.artists.one())

            return self.store.find(MusicTrack, MusicTrack.file_path == \
                    u'testcase').addCallback(lambda x:
                    x.one()).addCallback(got_track, ref_artist, ref_album)

        def parse_it(refs, file):
            tags = {'artist' : 'madonna',
                    'file_type' : 'audio',
                    'album' : 'music',
                    'song' : 'testie',
                    'track' : 10}
            dfr = self.parser.parse_tags(tags, file, section='music')
            dfr.addCallback(check, *refs)
            return dfr

        file = File()
        file.path = u"testcase"
        return self.store.add(file).addCallback(add_album).addCallback(
                parse_it, file)

    def test_reuse_fuzzy_album_and_artist(self):

        def add_artist(album):
            artist = Artist()
            artist.name = u"Madonna"
            return self.store.add(artist).addCallback(lambda x: (x,
            album))

        def add_album(res):
            album = MusicAlbum()
            album.name = u'Music'
            return self.store.add(album).addCallback(add_artist)

        def check(result, ref_artist, ref_album):

            def got_album(album, ref):
                self.failUnless(album is ref, "Fuzzy Album not reused")
            
            def got_track(track, ref_art, ref_alb):
                self.assertEquals(track.title, u'testie')
                return track.album.addCallback(got_album,
                        ref_album).addCallback(lambda x:
                        track.artists.one())

            return self.store.find(MusicTrack, MusicTrack.file_path == \
                    u'testcase').addCallback(lambda x:
                    x.one()).addCallback(got_track, ref_artist, ref_album)

        def parse_it(refs, file):
            tags = {'artist' : 'madonna',
                    'file_type' : 'audio',
                    'album' : 'music',
                    'song' : 'testie',
                    'track' : 10}
            dfr = self.parser.parse_tags(tags, file, section='music')
            dfr.addCallback(check, *refs)
            return dfr

        file = File()
        file.path = u"testcase"
        return self.store.add(file).addCallback(add_album).addCallback(
                parse_it, file)

    test_reuse_fuzzy_album_and_artist.todo = 'Not implemented yet'

    def test_reuse_track(self):

        def add_track(file):
            track = MusicTrack()
            track.file_path = file.path
            return self.store.add(track)

        def check(res, reference):
            
            def got_track(track, reference):
                self.failUnless(track is reference, "Track was not reused")
                self.assertEquals(track.title, u'testie')

            return self.store.find(MusicTrack, MusicTrack.file_path == \
                        u'testcase').addCallback(lambda x:
                        x.one()).addCallback(got_track, reference)

        def parse_it(track, file):
            tags = {'artist' : 'madonna',
                    'file_type' : 'audio',
                    'album' : 'music',
                    'song' : 'testie',
                    'track' : 10}

            dfr = self.parser.parse_tags(tags, file, section='music')
            dfr.addCallback(check, track)
            return dfr

        file = File()
        file.path = u"testcase"
        return self.store.add(file).addCallback(add_track).addCallback(
                parse_it, file)

    def test_empty(self):

        def check(res):

            def got_album(album):
                self.failIf(album)

            def got_artists(artists):
                self.assertEquals(artists, [])


            def got_track(track):
                self.assertEquals(track.title, None)
                self.assertEquals(track.genre, None)
                self.assertEquals(track.duration, None)
                return track.album.addCallback(got_album).addCallback(
                        lambda x: track.artists.values(Artist.name)
                        ).addCallback(got_artists)
            
            return self.store.get(MusicTrack, u'test').addCallback(got_track)

        def parse_it(res, file):
            tags = {'file_type' : 'audio'}
            return self.parser.parse_tags(tags, file, section='music')

        file = File()
        file.path = u"test"
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check)

    def test_none_values(self):

        def check(res):

            def got_album(album):
                self.failIf(album)

            def got_artists(artists):
                self.assertEquals(artists, [])


            def got_track(track):
                self.assertEquals(track.title, None)
                self.assertEquals(track.genre, None)
                self.assertEquals(track.duration, None)
                return track.album.addCallback(got_album).addCallback(
                        lambda x: track.artists.values(Artist.name)
                        ).addCallback(got_artists)
            
            return self.store.get(MusicTrack, u'test').addCallback(got_track)

        def parse_it(res, file):
            tags = {'file_type' : 'audio', 'artist' : None,
                    'date' : None, 'album' : None, 'duration' : None,
                    'genre' : None, 'song' : None}
            return self.parser.parse_tags(tags, file, section='music')

        file = File()
        file.path = u"test"
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check)

    def test_wma_tags(self):

        def check(res):

            def got_album(album):
                self.assertEquals(album.name, 'music')
                release_date = datetime.datetime(1982, 1, 1)
                self.assertEquals(album.release_date, release_date)

            def got_artists(artists):
                self.assertEquals(artists, ['madonna'])

            def got_track(track):
                self.assertEquals(track.title, u'testie')
                self.assertEquals(track.file_path, u'test')
                self.assertEquals(track.genre, u'cheese')
                self.assertEquals(track.duration, 1000)
                self.assertEquals(track.track_number, 10)
                return track.album.addCallback(got_album).addCallback(
                        lambda x: track.artists.values(Artist.name)).addCallback(got_artists)
            
            return self.store.get(MusicTrack, u'test').addCallback(got_track)

        def parse_it(res, file):
            tags = {'artist' : None,
                    'WM/AlbumArtist' : 'madonna',
                    'file_type' : 'audio',
                    'album' : None,
                    'WM/AlbumTitle' : 'music',
                    'song' : None,
                    'title' : 'testie',
                    'duration' : 1000,
                    "genre" : "cheese",
                    "date" : None,
                    'WM/Year' : '1982',
                    'track' : None,
                    'WM/TrackNumber' : 10}
            return self.parser.parse_tags(tags, file, section='music')

        file = File()
        file.path = u"test"
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check)



class TestImageParsing(ParserSetupMixin, TestCase):

    def test_with_new_album(self):
        path = self.ensure_file(u'test', dirs=['something', 'winter 2006'])

        def check(res):

            def got_album(album):
                self.assertEquals(album.name, 'winter 2006')

            def got_image(image):
                date = datetime.datetime(2008, 6, 11, 12, 4, 59)
                self.assertEquals(image.shot_time, date)
                # not stored in the db
                #self.assertEquals(image.device_make, "Fluendo")
                #self.assertEquals(image.device_model, "Elisa Capture Cam")
                self.assertEquals(image.with_flash, True)
                self.assertEquals(image.orientation, 1)
                self.assertEquals(image.gps_altitude, 12)
                self.assertEquals(image.gps_latitude, '41,24,0N')
                self.assertEquals(image.gps_longitude, '2,10,0E')
                return image.album.addCallback(got_album)
            
            return self.store.get(Image, path).addCallback(got_image)

        def parse_it(res, file):
            tags = {'date-time-original' : '2008:06:11 12:04:59',
                    'device-make' : 'Fluendo',
                    'device-model' : 'Elisa Capture Cam',
                    'capture-flash' : 1,
                    'capture-orientation' : 1,
                    'gps-altitude' : 12,
                    'gps-latitude' : '41,24,0N',
                    'gps-longitude' :  '2,10,0E',
                    'file_type' : 'image',
                    }
            return self.parser.parse_tags(tags, file, 'pictures')

        file = File()
        file.path = path
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check)

    def test_dates(self):

        def test_one(key, value, expected):
            def got_image(image, expected):
                self.assertEquals(image.shot_time, expected)
            
            def check(res, key, expected):
                return self.store.get(Image, u'test%s' % key).addCallback(got_image,
                        expected)

            def parse_it(res, file):
                tags = {'file_type': 'image', key : value}
                return self.parser.parse_tags(tags, file, section='pictures')

            file = File()
            file.path = u"test%s" % key
            dfr = self.store.add(file)
            return dfr.addCallback(parse_it, file).addCallback(check, key, expected)

        listing = [ ('date-time-modified', '2008-06-11 12:04:59',
                            datetime.datetime(2008, 6, 11, 12, 4, 59)),
                    ('date-time-original', '2007-05-17T18:09:12',
                            datetime.datetime(2007, 5, 17, 18, 9, 12)),
                    ('date-time-digitized', '2006-04-19T19:25:59',
                            datetime.datetime(2006, 4, 19, 19, 25, 59)),
                    ]

        def iterate(to_test):
            for key, value, result in to_test:
                yield test_one(key, value, result)

        dfr = task.coiterate(iterate(listing))
        return dfr


    def test_uses_the_best_date(self):
        # if it can choose original is the best one
        tags = {'file_type' : 'image',
                'date-time-modified':'2008:06:11 12:04:59',
                'date-time-original': '2007:05:17 18:09:12',
                'date-time-digitized': '2006:04:19 19:25:59',}

        def got_image(image):
            self.assertEquals(image.shot_time,
                    datetime.datetime(2007, 5, 17, 18, 9, 12))
        
        def check(res):
            return self.store.get(Image, u'test').addCallback(got_image)

        def parse_it(res, file, tags):
            return self.parser.parse_tags(tags, file, section='pictures')

        file = File()
        file.path = u"test"
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file, tags).addCallback(check)

    def test_use_fallback_date(self):
        tags = {'file_type' : 'image' }

        # create the file we tell the parser have been parsed
        path = self.ensure_file('test_photo.jpg')

        # the first picture of little unix was taken one minute after he was
        # born but was accessed later very often
        os.utime(path, (102345600, 60))

        # check if the right date was set
        def got_image(image):
            self.assertEquals(image.shot_time,
                    # one minute after baby unix was born
                    datetime.datetime(1970, 1, 1, 1, 1, 0))
        
        def check(res, path):
            return self.store.get(Image, unicode(path)).addCallback(got_image)

        def parse_it(res, file, tags):
            return self.parser.parse_tags(tags, file, section='pictures')

        file = File()
        file.path = unicode(path)
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file, tags).addCallback(check, path)



    def test_reuse_album(self):

        path = self.ensure_file(u'testcase', ['test_album'])

        def add_album(res):
            album = PhotoAlbum()
            album.name = u'test_album'
            return self.store.add(album)

        def check(result, ref_album, path):

            def got_album(album, ref):
                self.failUnless(album is ref, "Album not reused")
            
            def got_image(image, ref_album):
                return image.album.addCallback(got_album,
                        ref_album)

            return self.store.get(Image, path
                    ).addCallback(got_image, ref_album)

        def parse_it(ref, file):
            tags = {'file_type' : 'image'}
            return self.parser.parse_tags(tags, file, 'pictures'
                    ).addCallback(check, ref, path)

        file = File()
        file.path = path
        return self.store.add(file).addCallback(add_album).addCallback(
                parse_it, file)

    def test_reuse_image(self):

        path = self.ensure_file(u'testcase')

        def add_image(file):
            image = Image()
            image.file_path = file.path
            return self.store.add(image)

        def check(res, reference, path):
            
            def got_image(image, reference):
                self.failUnless(image is reference, "Track was not reused")

            return self.store.get(Image, path
                        ).addCallback(got_image, reference)

        def parse_it(image, file, path):
            tags = { 'file_type' : 'image'}

            return self.parser.parse_tags(tags, file
                    ).addCallback(check, image, path)

        file = File()
        file.path = path
        return self.store.add(file).addCallback(add_image).addCallback(
                parse_it, file, path)

    def test_empty(self):

        path = self.ensure_file(u'test', ['alabum'])

        def check(res, path):

            def got_album(album):
                self.assertEquals(album.name, u'alabum')

            def got_image(image):
                # here now the fallback date happens, so that we actually have a
                # shot time, even if the tags don't contain it
                # self.assertEquals(image.shot_time, None)

                # not stored in the database
                #self.assertEquals(image.device_make, None)
                #self.assertEquals(image.device_model, None)
                self.assertEquals(image.with_flash, None)
                self.assertEquals(image.orientation, None)
                self.assertEquals(image.gps_altitude, None)
                self.assertEquals(image.gps_latitude, None)
                self.assertEquals(image.gps_longitude, None)
                return image.album.addCallback(got_album)
            
            return self.store.get(Image, path).addCallback(got_image)

        def parse_it(res, file):
            tags = {'file_type' : 'image'}
            return self.parser.parse_tags(tags, file, 'pictures')

        file = File()
        file.path = path
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check, path)

    def test_album_parsing(self):

        def test_one(path, albumname): 

            def check(res):

                def got_album(album):
                    self.assertEquals(album.name, albumname)

                def got_image(image):
                   return image.album.addCallback(got_album)
                
                return self.store.get(Image, path).addCallback(got_image)

            def parse_it(res, file):
                tags = {'file_type' : 'image',
                        'date-time-original' : None,
                        'device-make' : None,
                        'device-model' : None,
                        'capture-flash' : None,
                        'capture-orientation' : None,
                        'gps-altitude' : None,
                        'gps-latitude' : None,
                        'gps-longitude' : None,
                        }
                return self.parser.parse_tags(tags, file, 'pictures')

            file = File()
            file.path = path
            dfr = self.store.add(file)
            return dfr.addCallback(parse_it, file).addCallback(check)

        listing = [ ((u'home','ben','Bilder'), 'test.jpg', u'Bilder'),
                    ((u'Test',), 'nice.jpg', u'Test')]

        def iterate(paths):
            for dirs, file, albumname in paths:
                path = self.ensure_file(file, dirs)
                yield test_one(path, albumname)

        dfr = task.coiterate(iterate(listing))
        return dfr

    def test_no_album(self):
    
        def check(res):
            def got_album(album):
                self.failIf(album, "There should not be any album...")

            def got_image(image):
               return image.album.addCallback(got_album)
            
            return self.store.get(Image, u"test").addCallback(got_image)

        def parse_it(res, file):
            tags = {'file_type' : 'image'}
            return self.parser.parse_tags(tags, file)

        file = File()
        file.path = u'test'
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check)

    test_no_album.skip = "When does it happen, that we have file that is in no"\
                         " folder?."


    def test_none_values(self):
        
        path = self.ensure_file(u'test', ['path', 'to','albumli'])

        def check(res, path):

            def got_album(album):
                self.assertEquals(album.name, u'albumli')

            def got_image(image):
                self.assertEquals(image.shot_time, None)
                #self.assertEquals(image.device_make, None) # not stored
                #self.assertEquals(image.device_model, None)
                self.assertEquals(image.with_flash, None)
                self.assertEquals(image.orientation, None)
                self.assertEquals(image.gps_altitude, None)
                self.assertEquals(image.gps_latitude, None)
                self.assertEquals(image.gps_longitude, None)
                return image.album.addCallback(got_album)
            
            return self.store.get(Image, path).addCallback(got_image)

        def parse_it(res, file):
            tags = {'file_type' : 'image',
                    'date-time-original' : None,
                    'device-make' : None,
                    'device-model' : None,
                    'capture-flash' : None,
                    'capture-orientation' : None,
                    'gps-altitude' : None,
                    'gps-latitude' : None,
                    'gps-longitude' : None,
                    }
            return self.parser.parse_tags(tags, file, section='pictures')

        file = File()
        file.path = path
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check, path)

    test_none_values.todo = "The date time fallback hack makes this test fail"



class TestTagging(ParserSetupMixin, TestCase):

    todo = "The tagging system is deactivated"

    def test_audio_tagged(self):

        def check(res, file):
            def got_tags(tags):
                self.assertEquals(tags, ['audio'])

            return file.tags.values(Tag.name).addCallback(got_tags)

        def parse_it(res, file):
            tags = {'file_type' : 'audio'}
            return self.parser.parse_tags(tags, file, section='music')

        file = File()
        file.path = u"test"
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check, file)

    def test_video_tagged(self):

        def check(res, file):
            def got_tags(tags):
                self.assertEquals(tags, [u'video'])

            return file.tags.values(Tag.name).addCallback(got_tags)

        def parse_it(res, file):
            tags = {'file_type' : 'video'}
            return self.parser.parse_tags(tags, file, section='video')

        file = File()
        file.path = u"test"
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check, file)

    def test_image_tagged(self):

        def check(res, file):
            def got_tags(tags):
                self.assertEquals(tags, ['image'])

            return file.tags.values(Tag.name).addCallback(got_tags)

        def parse_it(res, file):
            tags = {'file_type' : 'image'}
            return self.parser.parse_tags(tags, file, section='pictures')

        file = File()
        file.path = u"test"
        dfr = self.store.add(file)
        return dfr.addCallback(parse_it, file).addCallback(check, file)

    def test_retag(self):

        def check(res, file):
            def got_tags(tags):
                self.assertEquals(tags, ['audio'])

            return file.tags.values(Tag.name).addCallback(got_tags)

        def parse_it(res, file):
            tags = {'file_type' : 'audio'}
            return self.parser.parse_tags(tags, file)

        def add_tag(res, file):
            tag = Tag()
            tag.name = u'audio'
            return self.store.add(tag).addCallback(lambda x:
                    file.tags.add(tag)).addCallback(lambda x:
                    self.store.commit())

        file = File()
        file.path = u"test"
        return self.store.add(file).addCallback(add_tag, file
                ).addCallback(parse_it, file).addCallback(check, file)


