## $Id: test_imagestorage.py 7193 2011-11-25 07:21:29Z henrik $
##
## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
import os
import tempfile
import shutil
import unittest
from StringIO import StringIO
from hurry.file import HurryFile
from hurry.file.interfaces import IFileRetrieval
from zope.component import (
    getUtility, provideUtility, queryUtility, provideAdapter)
from zope.component.hooks import setSite
from zope.interface.verify import verifyClass, verifyObject
from waeup.sirp.app import University
from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
from waeup.sirp.imagestorage import (
    FileStoreNameChooser, ExtFileStore, DefaultFileStoreHandler,
    DefaultStorage)
from waeup.sirp.interfaces import (
    IFileStoreNameChooser, IExtFileStore, IFileStoreHandler,)

class HelperFuncsTests(unittest.TestCase):

    def setUp(self):
        self.workdir = tempfile.mkdtemp()

    def tearDown(self):
        shutil.rmtree(self.workdir)

class FileStoreNameChooserTests(FunctionalTestCase):

    layer = FunctionalLayer

    def test_iface(self):
        # we provide the interfaces we promise to do
        obj = FileStoreNameChooser(None)
        verifyClass(IFileStoreNameChooser, FileStoreNameChooser)
        verifyObject(IFileStoreNameChooser, obj)
        return

    def test_accessible_as_adapter(self):
        # we can get a file name chooser via adapter
        chooser = IFileStoreNameChooser(object())
        self.assertTrue(
            isinstance(chooser, FileStoreNameChooser))
        return

    def test_check_name(self):
        # default file name choosers accept any string
        chooser = FileStoreNameChooser(object())
        self.assertEqual(chooser.checkName('Hi there!'), True)
        self.assertEqual(chooser.checkName(None), False)
        return

    def test_choose_name(self):
        # we get a simple string if we do not pass in a valid string
        chooser = FileStoreNameChooser(object())
        self.assertEqual(chooser.chooseName('myname'), 'myname')
        self.assertEqual(chooser.chooseName(None), u'unknown_file')
        return

class ExtFileStoreTests(unittest.TestCase):
    # Test external file store (non-functional mode)

    def setUp(self):
        self.workdir = tempfile.mkdtemp()
        self.root = None
        return

    def tearDown(self):
        shutil.rmtree(self.workdir)
        if self.root is not None:
            shutil.rmtree(self.root)
        return

    def test_iface(self):
        obj = ExtFileStore(None)
        verifyClass(IExtFileStore, ExtFileStore)
        verifyObject(IExtFileStore, obj)
        return

    def test_root_setup_wo_site(self):
        # if no site is available we can use a temporary root
        fs = ExtFileStore()
        self.root = fs.root
        self.assertTrue(isinstance(self.root, basestring))
        self.assertTrue(os.path.exists(self.root))
        return

    def test_create_instance(self):
        storage1 = ExtFileStore()
        storage2 = ExtFileStore(root=self.workdir)
        self.root = storage1.root
        self.assertTrue(storage1.root is not None)
        self.assertTrue(storage1.root != storage2.root)
        self.assertEqual(storage2.root, self.workdir)
        return

    def test_create_file(self):
        # We can store files
        storage = ExtFileStore(root=self.workdir)
        dummy_file = StringIO('sample file')
        image_file = storage.createFile('mysample', dummy_file)
        self.assertTrue('mysample' in os.listdir(storage.root))
        self.assertEqual('mysample', image_file.data)
        return

    def test_create_file_w_ext(self):
        # We can store files with filename extension
        storage = ExtFileStore(root=self.workdir)
        dummy_file = StringIO('sample file')
        image_file = storage.createFile('mysample.txt', dummy_file)
        self.assertTrue('mysample.txt' in os.listdir(storage.root))
        self.assertEqual('mysample.txt', image_file.data)
        return

    def test_get_file(self):
        # We can get files after having them stored
        storage = ExtFileStore(root=self.workdir)
        dummy_file = StringIO('sample file')
        image_file = storage.createFile('mysample', dummy_file)
        result = storage.getFile(image_file.data)
        self.assertEqual(result.read(), 'sample file')
        return

    def test_get_file_w_ext(self):
        # We can get files with filename extension after having them
        # stored
        storage = ExtFileStore(root=self.workdir)
        dummy_file = StringIO('sample file')
        image_file = storage.createFile('mysample.txt', dummy_file)
        result = storage.getFile('mysample')
        self.assertEqual(result.read(), 'sample file')
        return

    def test_replace_file_w_new_ext(self):
        # when we store a file with the same file_id but different
        # filename extension, the old file will be deleted
        storage = ExtFileStore(root=self.workdir)
        dummy_file = StringIO('sample_file')
        image_file = storage.createFile('mysample.jpg', dummy_file)
        file1_path = storage.getFile('mysample').name
        new_file = storage.createFile(
            'mysample.png', StringIO('new file'))
        file2 = storage.getFile('mysample')
        self.assertEqual(file2.name[-12:], 'mysample.png')
        self.assertEqual(file2.read(), 'new file')
        # the old file was deleted
        self.assertFalse(os.path.exists(file1_path))
        return

    def test_extract_marker(self):
        # file stores support extracting markers from filenames
        storage = ExtFileStore(root=self.workdir)
        result1 = storage.extractMarker(None)
        result2 = storage.extractMarker('')
        result3 = storage.extractMarker('no-marker')
        result4 = storage.extractMarker('no-marker.txt')
        result5 = storage.extractMarker('__MARKER__foo.jpg')
        result6 = storage.extractMarker('__MaRkEr__foo.jpg')
        result7 = storage.extractMarker('__THE_MARKER__foo.jpg')
        result8 = storage.extractMarker('__A_MARK__my__foo.jpg')

        self.assertEqual(result1, ('', '', '', ''))
        self.assertEqual(result2, ('', '', '', ''))
        self.assertEqual(result3, ('', 'no-marker', 'no-marker', ''))
        self.assertEqual(result4, ('', 'no-marker.txt', 'no-marker', '.txt'))
        self.assertEqual(result5, ('marker', 'foo.jpg', 'foo', '.jpg'))
        self.assertEqual(result6, ('marker', 'foo.jpg', 'foo', '.jpg'))
        self.assertEqual(result7, ('the_marker', 'foo.jpg', 'foo', '.jpg'))
        self.assertEqual(result8, ('a_mark', 'my__foo.jpg', 'my__foo', '.jpg'))
        return

class DefaultFileStoreHandlerTests(unittest.TestCase):

    def test_iface(self):
        obj = DefaultFileStoreHandler()
        verifyClass(IFileStoreHandler, DefaultFileStoreHandler)
        verifyObject(IFileStoreHandler, obj)
        return

class CustomizedFileHandler(object):
    def pathFromFileID(self, store, root, file_id):
        return os.path.join(root, file_id[12:])

    def createFile(self, store, root, filename, file_id, f):
        ext = os.path.splitext(filename)[1]
        path = self.pathFromFileID(store, root, file_id) + ext
        return f, path, HurryFile(filename, file_id + ext)

class CustomContext(object):
    pass

class CustomContextFileChooser(object):
    def __init__(self, context):
        self.context = context

    def chooseName(self, name=None, attr=None):
        # this name chooser returns different file ids depending on
        # the `attr` parameter, a simple string.
        if attr=='img':
            return '__mymarker__mysample_img.jpg'
        elif attr=='doc':
            return '__mymarker__mysample_doc.doc'
        return '__mymarker__mysample.txt'

class FunctionalExtFileStoreTests(FunctionalTestCase):

    layer = FunctionalLayer

    def setUp(self):
        super(FunctionalExtFileStoreTests, self).setUp()
        self.workdir = tempfile.mkdtemp()
        self.samplefile = os.path.join(self.workdir, 'sample')
        self.otherfile = os.path.join(self.workdir, 'other')
        open(self.samplefile, 'wb').write('Hi there!')
        open(self.otherfile, 'wb').write('Hi from other!')
        self.fd = open(self.samplefile, 'r')
        self.fd2 = open(self.otherfile, 'r')
        self.getRootFolder()['app'] = University()
        self.app = self.getRootFolder()['app']
        self.app['datacenter'].setStoragePath(self.workdir)
        # register a custom filename mangler
        provideUtility(
            CustomizedFileHandler(), IFileStoreHandler, name=u'mymarker')
        # register a file chooser adapter for CustomContext
        provideAdapter(
            CustomContextFileChooser,
            (CustomContext,), IFileStoreNameChooser)
        return


    def tearDown(self):
        super(FunctionalExtFileStoreTests, self).tearDown()
        self.fd.close()
        self.fd2.close()
        shutil.rmtree(self.workdir)
        return

    def test_root_setup_w_site(self):
        # if a site is available we use it to determine the root dir
        fs = ExtFileStore()
        setSite(self.app)
        self.root = fs.root
        expected_root = os.path.join(
            self.app['datacenter'].storage, 'media')
        self.assertTrue(isinstance(self.root, basestring))
        self.assertEqual(self.root, expected_root)
        return

    def test_get_utility(self):
        # we can get an ExtFileStore by global utility lookup
        fs1 = getUtility(IExtFileStore)
        fs2 = getUtility(IExtFileStore)
        self.assertTrue(isinstance(fs1, ExtFileStore))
        self.assertTrue(fs1 is fs2)
        return

    def test_default_handler_create_file(self):
        # we can use the default handler to store files
        fs = ExtFileStore()
        result = fs.createFile('sample.txt', StringIO('sample text'))
        self.assertEqual(result.data, 'sample.txt')
        self.assertTrue('sample.txt' in os.listdir(fs.root))
        return

    def test_default_handler_get_file(self):
        # we can get files stored by the default handler
        fs = ExtFileStore()
        fs.createFile('sample.txt', StringIO('sample text'))
        result1 = fs.getFile('sample.txt')
        result2 = fs.getFile('not-existent')
        self.assertEqual(result1.read(), 'sample text')
        self.assertTrue(result2 is None)
        return

    def test_customized_handler_create_file(self):
        # we can use registered filename handlers
        fs = ExtFileStore()
        result = fs.createFile(
            '__MYMARKER__sample.txt', StringIO('sample text'))
        self.assertEqual(result.data, '__MYMARKER__sample.txt')
        self.assertTrue('sample.txt' in os.listdir(fs.root))
        return

    def test_customized_handler_create_file_w_ext(self):
        # when we create a file of img type, the filename ext is taken
        # from input file.
        fs = ExtFileStore()
        result = fs.createFile(
            '__MYMARKER__sample_img.png', StringIO('sample text'))
        self.assertEqual(result.data, '__MYMARKER__sample_img.png')
        self.assertTrue('sample_img.png' in os.listdir(fs.root))
        return

    def test_customized_handler_get_file(self):
        # we consider registered filename handlers when asking for
        # stored files.
        fs = ExtFileStore()
        fs.createFile('__MYMARKER__sample.txt', StringIO('sample text'))
        result1 = fs.getFile('__MYMARKER__sample.txt')
        result2 = fs.getFile('__MYMARKER__not-existent')
        result3 = fs.getFile('not-existent')
        self.assertEqual(result1.read(), 'sample text')
        self.assertTrue(result2 is None)
        self.assertTrue(result3 is None)
        return

    def test_get_file_by_context_w_attr(self):
        # if we register a file name chooser, we can also get a file
        # by context and attribute
        fs = ExtFileStore()
        context = CustomContext()
        file_id1 = IFileStoreNameChooser(context).chooseName()
        file_id2 = IFileStoreNameChooser(context).chooseName(attr='img')
        file_id3 = IFileStoreNameChooser(context).chooseName(attr='doc')
        fs = ExtFileStore()
        # create three files for a single context, each which
        # different content
        fs.createFile(file_id1, StringIO('my sample 1'))
        fs.createFile(file_id2, StringIO('my sample 2'))
        fs.createFile(file_id3, StringIO('my sample 3'))
        # now get back all files indicated by different `attr` markers
        result1 = fs.getFileByContext(context)
        result2 = fs.getFileByContext(context, attr='img')
        result3 = fs.getFileByContext(context, attr='doc')
        # each file has a different file id
        self.assertEqual(file_id1, '__mymarker__mysample.txt')
        self.assertEqual(file_id2, '__mymarker__mysample_img.jpg')
        self.assertEqual(file_id3, '__mymarker__mysample_doc.doc')
        # each file has different content
        self.assertEqual(result1.read(), 'my sample 1')
        self.assertEqual(result2.read(), 'my sample 2')
        self.assertEqual(result3.read(), 'my sample 3')
        return

    def test_get_default_handler(self):
        # we can get a default handler
        result = queryUtility(IFileStoreHandler)
        self.assertTrue(
            isinstance(result, DefaultFileStoreHandler))
        return

    def test_get_default_file_retrieval(self):
        # we get a file store when requesting a file retrieval
        result = queryUtility(IFileRetrieval)
        self.assertTrue(
            isinstance(result, DefaultStorage))

    def test_delete_file(self):
        # we can remove stored files from storage
        fs = ExtFileStore()
        # First, we store a file in file store
        fs.createFile('sample.txt', StringIO('sample text'))
        # Then we delete it
        fs.deleteFile('sample.txt')
        # 'Deletion' means, next call to getFile should get None
        result = fs.getFile('sample.txt')
        self.assertTrue(result is None)
        # Hm, okay we can also check, whether it was really deleted
        self.assertTrue('sample.txt' not in os.listdir(fs.root))
        return

    def test_delete_file_by_context_w_attr(self):
        # if we register a file name chooser, we can also delete a file
        # by context and attribute
        fs = ExtFileStore()
        context = CustomContext()
        file_id1 = IFileStoreNameChooser(context).chooseName()
        file_id2 = IFileStoreNameChooser(context).chooseName(attr='img')
        file_id3 = IFileStoreNameChooser(context).chooseName(attr='doc')
        fs = ExtFileStore()
        # create three files for a single context, each which
        # different content
        fs.createFile(file_id1, StringIO('my sample 1'))
        fs.createFile(file_id2, StringIO('my sample 2'))
        fs.createFile(file_id3, StringIO('my sample 3'))
        # now delete first two of these files
        fs.deleteFileByContext(context)
        fs.deleteFileByContext(context, attr='img')
        # Following getFile calls should give None for two of the
        # files.
        result1 = fs.getFileByContext(context)
        result2 = fs.getFileByContext(context, attr='img')
        result3 = fs.getFileByContext(context, attr='doc')
        self.assertEqual(result1, None)
        self.assertEqual(result2, None)
        self.assertEqual(result3.read(), 'my sample 3')
        return
