Commits

b7w committed 5e49e38

Allow multiply storage pathes

  • Participants
  • Parent commits 7fa75f2

Comments (0)

Files changed (17)

bviewer/api/tests/test_edit.py

         gallery_id = self.data.gallery_b7w.id
         album_id = self.data.album_b7w.id
 
-        self.new_gallery = dict(title='New gallery', user=user_id, url='test')
+        self.new_gallery = dict(description='New gallery', user=user_id, url='test')
         self.new_album = dict(title='New album', gallery=gallery_id)
         self.new_image = dict(path='image1.jpg', album=album_id)
         self.data.generate_image(self.data.gallery_b7w.home, 'image1.jpg')

bviewer/archive/controllers.py

 # -*- coding: utf-8 -*-
 import logging
 
-from bviewer.core.files.storage import ImageStorage
+from bviewer.core.files.proxy import ProxyImageStorage
 from bviewer.core.utils import ImageOptions, cache_method, as_job, get_redis_connection
 
 
         :type name: str
         """
         self.gallery = gallery
-        self.storage = ImageStorage(gallery, archive_cache=True)
+        self.storage = ProxyImageStorage(gallery, archive_cache=True)
         self.image_paths = [self.storage.get_path(i) for i in image_paths]
         self.name = name or self.uid
 

bviewer/core/controllers.py

 # -*- coding: utf-8 -*-
 import logging
 import re
+
 from django.conf import settings
 from django.core.cache import cache
 from django.db.models import Q
 from django.utils.encoding import smart_text
 
 from bviewer.core.exceptions import FileError
+from bviewer.core.files.proxy import ProxyImageStorage
 from bviewer.core.files.response import download_response
 from bviewer.core.files.storage import ImageStorage
 from bviewer.core.images import CacheImage
         """
         :type obj: bviewer.core.models.Album
         """
-        return AlbumController(obj.gallery, obj.gallery.user, obj=obj)
+        return AlbumController(obj, obj.user, obj=obj.top_album)
 
     @cache_method
     def get_object(self):
         sizes = [i for i in settings.VIEWER_IMAGE_SIZE.keys() if i != 'full']
         for size in sizes:
             for image in images:
-                storage = ImageStorage(self.gallery)
+                storage = ProxyImageStorage(self.gallery)
                 self._pre_cache_image(storage, image, size)
 
     def _pre_cache_image(self, storage, image, size):
     MODEL = Image
 
     def get_response(self, size):
-        #: :type: bviewer.core.models.Image
+        # : :type: bviewer.core.models.Image
         image = self.get_object()
-        storage = ImageStorage(self.gallery)
+        storage = ProxyImageStorage(self.gallery)
         options = ImageOptions.from_settings(size)
         image_path = storage.get_path(image.path, options)
         if not image_path.exists:
     MODEL = Video
 
     def get_response(self, size):
-        #: :type: bviewer.core.models.Video
+        # : :type: bviewer.core.models.Video
         video = self.get_object()
-        storage = ImageStorage(self.gallery)
+        storage = ProxyImageStorage(self.gallery)
         options = ImageOptions.from_settings(size, name=str(video.id))
         image_url = storage.get_url(video.thumbnail_url, options)
 

bviewer/core/files/base.py

+# -*- coding: utf-8 -*-
+import hashlib
+import uuid
+
+from django.utils.encoding import smart_bytes
+
+
+class BaseImageStorage(object):
+    def hash_for(self, content):
+        return hashlib.sha1(smart_bytes(content)).hexdigest()
+
+    def gen_temp_name(self):
+        return uuid.uuid1().hex

bviewer/core/files/path.py

         self.storage = storage
         self.id = None
         self.path = path
+        self.full_path = storage.gallery.home + '/' + path
         self.options = options
         self.name = os.path.basename(path)
         self.saved = False
         raise NotImplementedError()
 
 
+class MountPath(object):
+    def __init__(self, name):
+        self.name = name
+        self.full_path = name
+        self.is_image = False
+        self.is_dir = True
+
+
 class ImagePath(BasePath, ImagePathCacheMixin):
     """
     Provide basic operation on files, and cache.

bviewer/core/files/proxy.py

+# -*- coding: utf-8 -*-
+from bviewer.core.exceptions import FileError
+from bviewer.core.files.base import BaseImageStorage
+from bviewer.core.files.path import MountPath
+from bviewer.core.files.storage import ImageStorage
+from bviewer.core.files.utils import GalleryConfig
+
+
+class ProxyImageStorage(BaseImageStorage):
+    def __init__(self, gallery, root_path=None, cache_path=None, archive_cache=False):
+        """
+        :param root_path: place where image files stored, default settings.VIEWER_STORAGE_PATH
+        :param cache_path: place where cache stored, default settings.VIEWER_CACHE_PATH
+        :param archive_cache: set 'archives' sub cache folder instead of 'images'
+        :type gallery: bviewer.core.models.Gallery
+        """
+        self.gallery = gallery
+        self.mounts = gallery.home.split(';')
+        self.configs = [GalleryConfig(gallery, i) for i in self.mounts]
+        self.storages = {}
+        for conf in self.configs:
+            storage = ImageStorage(conf, root_path=root_path, cache_path=cache_path, archive_cache=archive_cache)
+            self.storages[conf.home] = storage
+
+    def _get_storage(self, path):
+        for mount in self.mounts:
+            if path.startswith(mount):
+                if path == mount:
+                    return self.storages[mount], ''
+                return self.storages[mount], path.replace(mount + '/', '')
+        raise FileError('No storage mount found')
+
+    def list(self, path=None, saved_images=None):
+        if not path:
+            return sorted(MountPath(i) for i in self.mounts)
+        storage, path = self._get_storage(path)
+        return storage.list(path=path, saved_images=saved_images)
+
+    def get_path(self, path, options=None):
+        storage, path = self._get_storage(path)
+        return storage.get_path(path, options=options)
+
+    def get_url(self, url, options=None):
+        storage = list(self.storages.values())[0]
+        return storage.get_path(url, options=options)
+
+    def get_archive(self, options=None):
+        storage = list(self.storages.values())[0]
+        return storage.get_archive(options=options)
+
+    def clear_cache(self, full=False):
+        for storage in self.storages:
+            storage.clear_cache(full=full)
+
+    def cache_size(self):
+        return sum(i.cache_size() for i in self.storages.values())
+
+    def __repr__(self):
+        return 'ProxyImageStorage({0})'.format(self.gallery)

bviewer/core/files/storage.py

 # -*- coding: utf-8 -*-
-from functools import wraps
 import hashlib
 import logging
 import os
 from django.conf import settings
 from django.utils.encoding import smart_bytes, smart_text
 
+from bviewer.core.files.base import BaseImageStorage
 from bviewer.core.files.path import ImagePath, ImageUrl, ImageArchivePath
 from bviewer.core.exceptions import FileError
 from bviewer.core.images import Exif
-from bviewer.core.utils import method_call_str
+from bviewer.core.utils import io_call
+
 
 logger = logging.getLogger(__name__)
 
 
-def io_call(func):
-    """
-    Wrap method where can be raised `IOError` and re raise `FileError`.
-    Save method calls with all args to debug log.
-    """
-
-    @wraps(func)
-    def wrapper(self, *args, **kwargs):
-        try:
-            logger.debug(method_call_str(func.__name__, self, *args, **kwargs))
-            return func(self, *args, **kwargs)
-        except IOError as e:
-            print('1')
-            logger.exception(e)
-            raise FileError(e)
-
-    return wrapper
-
-
-class ImageStorage(object):
+class ImageStorage(BaseImageStorage):
     """
     Wrapper on file system.
     Provide methods to get pre config ImagePath, ImageUrl, ImageArchivePath.
             image_path = ImagePath(self, relative_path)
             if image_path.is_image or image_path.is_dir:
                 for obj in saved_images:
-                    if obj.path == image_path.path:
+                    if obj.path == image_path.full_path:
                         image_path.saved = True
                         image_path.id = obj.id
                         break
                 while sum(map(os.path.getsize, cache_paths)) > self._max_cache_size:
                     os.remove(cache_paths.pop())
 
-
     @io_call
     def cache_size(self):
         abs_cache = self._abs_cache_path

bviewer/core/files/utils.py

                 return data
             return data
 
-        return _split(self.path, [])
+        return _split(self.path, [])
+
+
+class GalleryConfig(object):
+    def __init__(self, gallery, mount):
+        self.gallery = gallery
+        self.home = mount
+        self.url = gallery.url
+        self.cache_size = gallery.cache_size
+        self.cache_archive_size = gallery.cache_archive_size
+
+    def __str__(self):
+        return 'GalleryConfig({0}, {1})'.format(self.gallery, self.home)

bviewer/core/management/commands/clearcache.py

 
 from django.core.management.base import BaseCommand
 
-from bviewer.core.files.storage import ImageStorage
+from bviewer.core.files.proxy import ProxyImageStorage
 from bviewer.core.models import Gallery
 
 
     def handle(self, *args, **options):
         full = 'full' in args
         for user in Gallery.objects.all():
-            ImageStorage(user).clear_cache(full=full)
-            ImageStorage(user, archive_cache=True).clear_cache(full=full)
+            ProxyImageStorage(user).clear_cache(full=full)
+            ProxyImageStorage(user, archive_cache=True).clear_cache(full=full)
         self.stdout.write('Clear cache for {c} users'.format(c=Gallery.objects.count()))

bviewer/core/models.py

 import json
 import logging
 import uuid
+from bviewer.core.files.proxy import ProxyImageStorage
 
 try:
     from urllib2 import urlopen, URLError
         """
         Check path exists
         """
-        storage = ImageStorage(self.album.gallery)
-        if not storage.exists(self.path):
+        storage = ProxyImageStorage(self.album.gallery)
+        if not storage.get_path(self.path).exists:
             raise ValidationError(smart_text('No {0} path exists').format(self.path))
 
     def __str__(self):
     :type instance: Image
     """
     if created:
-        storage = ImageStorage(instance.album.gallery)
+        storage = ProxyImageStorage(instance.album.gallery)
         set_time_from_exif(storage, instance, save=True)
 
 

bviewer/core/tests/test_controllers.py

         gallery = data.gallery_b7w
         top_album = gallery.top_album
 
-        controller = AlbumController(gallery, gallery, uid=top_album.id)
+        controller = AlbumController(gallery, gallery.user, uid=top_album.id)
         self.assertEqual(controller.uid, top_album.id)
         self.assertIsNone(controller.obj)
         self.assertEqual(controller.get_object(), top_album)
 
-        controller = AlbumController(gallery, gallery, obj=top_album)
+        controller = AlbumController(gallery, gallery.user, obj=top_album)
         self.assertEqual(controller.uid, top_album.id)
         self.assertEqual(controller.obj, top_album)
         self.assertEqual(controller.get_object(), top_album)

bviewer/core/tests/test_files.py

 # -*- coding: utf-8 -*-
 import os
+
 from mock import Mock, patch
-
 from django.test import TestCase
 from django.utils import six
 
 
 class ImagePathTestCase(TestCase):
     def setUp(self):
-        self.storage = Mock(hash_for=str)
+        self.storage = Mock(gallery=Mock(home='home'), hash_for=str)
         self.f1 = ImagePath(self.storage, 'path/1.jpg')
         self.f2 = ImagePath(self.storage, 'path/2.jpg')
 
     def test_url(self):
         storage = Mock(
             hash_for=Mock(return_value='cache_name'),
-            gallery=Mock(url='gallery.url'),
+            gallery=Mock(home='home', url='gallery.url'),
             type='type'
         )
         self.assertEqual(storage.hash_for(), 'cache_name')

bviewer/core/utils.py

 from django.utils.encoding import smart_text, smart_bytes
 from django.utils.functional import wraps
 
-from bviewer.core.exceptions import ResizeOptionsError
+from bviewer.core.exceptions import ResizeOptionsError, FileError
 
 
 logger = logging.getLogger(__name__)
     if image_path.is_image and image_path.exif.ctime:
         image.time = image_path.exif.ctime
         if save:
-            image.save()
+            image.save()
+
+
+def io_call(func):
+    """
+    Wrap method where can be raised `IOError` and re raise `FileError`.
+    Save method calls with all args to debug log.
+    """
+
+    @wraps(func)
+    def wrapper(self, *args, **kwargs):
+        try:
+            logger.debug(method_call_str(func.__name__, self, *args, **kwargs))
+            return func(self, *args, **kwargs)
+        except IOError as e:
+            logger.exception(e)
+            raise FileError(e)
+
+    return wrapper

bviewer/profile/actions.py

 # -*- coding: utf-8 -*-
 import logging
+
 from django.contrib import admin
 from django.db.models import F
 from django.shortcuts import redirect, render
 
+from bviewer.core.files.proxy import ProxyImageStorage
 from bviewer.core.utils import set_time_from_exif
-from bviewer.core.files.storage import ImageStorage
 from bviewer.profile.forms import BulkTimeUpdateForm
 
 
 
 def update_time_from_exif(model_admin, request, queryset):
     for image in queryset:
-        storage = ImageStorage(image.album.user)
+        storage = ProxyImageStorage(image.album.user)
         set_time_from_exif(storage, image, save=True)

bviewer/profile/admin.py

 from django.utils.encoding import smart_text
 
 from bviewer.core.controllers import AlbumController
-from bviewer.core.files.storage import ImageStorage
+from bviewer.core.files.proxy import ProxyImageStorage
 from bviewer.core.models import Album, Image, Gallery, Video
 from bviewer.profile.actions import bulk_time_update, update_time_from_exif
 from bviewer.profile.forms import AdminUserChangeForm, AdminGalleryForm, AdminAlbumForm
     form = AdminGalleryForm
 
     list_display = ('url', 'top_album', 'description', )
+
+    readonly_fields = ('home', 'cache_info', )
     exclude = ('user',)
 
-    readonly_fields = ('cache_info', )
-
     ordering = ('url',)
 
     def cache_info(self, user):
-        storage = ImageStorage(user)
+        storage = ProxyImageStorage(user)
         images_size = storage.cache_size() / 2 ** 20
-        storage = ImageStorage(user, archive_cache=True)
+        storage = ProxyImageStorage(user, archive_cache=True)
         archive_size = storage.cache_size() / 2 ** 20
         return 'Images size: {0} MB, archives size: {1} MB'.format(images_size, archive_size)
 

bviewer/profile/templates/profile/images.html

                     </li>
                     {% for dir in folder.dirs %}
                         <li>
-                            <a href="{% url 'profile.album' album.id %}?p={{ dir.path }}" class="thumbnail">
+                            <a href="{% url 'profile.album' album.id %}?p={{ dir.full_path }}" class="thumbnail">
                                 <img src="{% static 'core/img/album.png' %}">
 
                                 <p class="caption">{{ dir.name }}</p>
                             <a class="thumbnail image">
                                 <input type='checkbox' class="caption-checkbox"
                                        data-id="{{ file.id }}"
-                                       data-value="{{ file.path }}"
+                                       data-value="{{ file.full_path }}"
                                        {% if file.saved %}checked{% endif %}>
-                                <img src="{% url 'profile.download' %}?p={{ file.path }}">
+                                <img src="{% url 'profile.download' %}?p={{ file.full_path }}">
 
                                 <p class="caption">{{ file.name }}</p>
                             </a>

bviewer/profile/views.py

 # -*- coding: utf-8 -*-
 import logging
+
 from django.contrib.auth.decorators import login_required, permission_required
 from django.http import Http404, HttpResponseRedirect
 from django.shortcuts import render
 
 from bviewer.core.controllers import get_gallery, AlbumController
 from bviewer.core.exceptions import FileError
+from bviewer.core.files.proxy import ProxyImageStorage
 from bviewer.core.files.response import download_response
-from bviewer.core.files.storage import ImageStorage
 from bviewer.core.files.utils import ImageFolder
 from bviewer.core.images import CacheImage
 from bviewer.core.utils import ImageOptions, as_job
         return message_view(request, message='No such album')
 
     images = controller.get_images()
-    storage = ImageStorage(gallery)
+    storage = ProxyImageStorage(gallery)
     path = request.GET.get('p', '')
     try:
         image_paths = storage.list(path, saved_images=images)
 def download_image(request):
     if request.GET.get('p', None):
         path = request.GET['p']
-        user = get_gallery(request)
-        storage = ImageStorage(user)
+        gallery = get_gallery(request)
+        storage = ProxyImageStorage(gallery)
         options = ImageOptions.from_settings('tiny')
         image_path = storage.get_path(path, options)
         try: