mirror of
https://github.com/ajurna/cbwebreader.git
synced 2025-12-06 06:17:17 +00:00
massive change on how files are accessed.
moved from prviding a path to using unique id's if there might have been any directory traversal issues they should be gone now.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from comic.models import Setting, ComicBook, ComicPage, ComicStatus
|
from comic.models import Setting, ComicBook, ComicPage, ComicStatus, Directory
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Setting)
|
@admin.register(Setting)
|
||||||
@@ -17,6 +17,12 @@ class ComicPageAdmin(admin.ModelAdmin):
|
|||||||
list_display = ('Comic', 'index', 'page_file_name', 'content_type')
|
list_display = ('Comic', 'index', 'page_file_name', 'content_type')
|
||||||
list_filter = ['Comic']
|
list_filter = ['Comic']
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ComicStatus)
|
@admin.register(ComicStatus)
|
||||||
class ComicStatusAdmin(admin.ModelAdmin):
|
class ComicStatusAdmin(admin.ModelAdmin):
|
||||||
list_display = ['user', 'comic', 'last_read_page', 'unread']
|
list_display = ['user', 'comic', 'last_read_page', 'unread']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Directory)
|
||||||
|
class DirectoryAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|||||||
0
comic/management/__init__.py
Normal file
0
comic/management/__init__.py
Normal file
0
comic/management/commands/__init__.py
Normal file
0
comic/management/commands/__init__.py
Normal file
52
comic/management/commands/scan_comics.py
Normal file
52
comic/management/commands/scan_comics.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from comic.models import Setting, Directory, ComicBook
|
||||||
|
|
||||||
|
import os
|
||||||
|
from os.path import isdir
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Scan directories to Update Comic DB'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.base_dir = Setting.objects.get(name='BASE_DIR').value
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
self.scan_directory()
|
||||||
|
|
||||||
|
def scan_directory(self, directory=False):
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
:type directory: Directory
|
||||||
|
"""
|
||||||
|
if not directory:
|
||||||
|
comic_dir = self.base_dir
|
||||||
|
else:
|
||||||
|
comic_dir = os.path.join(self.base_dir, directory.get_path())
|
||||||
|
for file in os.listdir(comic_dir):
|
||||||
|
if isdir(os.path.join(comic_dir, file)):
|
||||||
|
if directory:
|
||||||
|
next_directory, created = Directory.objects.get_or_create(name=file,
|
||||||
|
parent=directory)
|
||||||
|
else:
|
||||||
|
next_directory, created = Directory.objects.get_or_create(name=file,
|
||||||
|
parent__isnull=True)
|
||||||
|
if created:
|
||||||
|
next_directory.save()
|
||||||
|
self.scan_directory(next_directory)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if directory:
|
||||||
|
ComicBook.objects.get(file_name=file,
|
||||||
|
directory=directory)
|
||||||
|
else:
|
||||||
|
ComicBook.objects.get(file_name=file,
|
||||||
|
directory__isnull=True)
|
||||||
|
except ComicBook.DoesNotExist:
|
||||||
|
ComicBook.process_comic_book(file, directory)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
49
comic/migrations/0008_auto_20160331_1140.py
Normal file
49
comic/migrations/0008_auto_20160331_1140.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-03-31 10:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.utils.timezone import utc
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comic', '0007_auto_20150626_1820'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Directory',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100)),
|
||||||
|
('selector', models.UUIDField(default=uuid.uuid4, null=True)),
|
||||||
|
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comic.Directory')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='comicbook',
|
||||||
|
name='date_added',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 3, 31, 10, 40, 30, 62170, tzinfo=utc)),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='comicbook',
|
||||||
|
name='selector',
|
||||||
|
field=models.UUIDField(default=uuid.uuid4, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='comicbook',
|
||||||
|
name='version',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='comicbook',
|
||||||
|
name='directory',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comic.Directory'),
|
||||||
|
),
|
||||||
|
]
|
||||||
29
comic/migrations/0009_auto_20160331_1140.py
Normal file
29
comic/migrations/0009_auto_20160331_1140.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-03-31 10:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
def gen_uuid(apps, schema_editor):
|
||||||
|
comicbook = apps.get_model('comic', 'comicbook')
|
||||||
|
for row in comicbook.objects.all():
|
||||||
|
row.selector = uuid.uuid4()
|
||||||
|
row.save()
|
||||||
|
directory = apps.get_model('comic', 'directory')
|
||||||
|
for row in directory.objects.all():
|
||||||
|
row.selector = uuid.uuid4()
|
||||||
|
row.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comic', '0008_auto_20160331_1140'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
|
||||||
|
]
|
||||||
27
comic/migrations/0010_auto_20160331_1140.py
Normal file
27
comic/migrations/0010_auto_20160331_1140.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-03-31 10:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comic', '0009_auto_20160331_1140'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='comicbook',
|
||||||
|
name='selector',
|
||||||
|
field=models.UUIDField(default=uuid.uuid4, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='directory',
|
||||||
|
name='selector',
|
||||||
|
field=models.UUIDField(default=uuid.uuid4, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
20
comic/migrations/0011_auto_20160331_1141.py
Normal file
20
comic/migrations/0011_auto_20160331_1141.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-03-31 10:41
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comic', '0010_auto_20160331_1140'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='comicbook',
|
||||||
|
name='version',
|
||||||
|
field=models.IntegerField(default=1),
|
||||||
|
),
|
||||||
|
]
|
||||||
26
comic/migrations/0012_auto_20160401_0949.py
Normal file
26
comic/migrations/0012_auto_20160401_0949.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-04-01 08:49
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comic', '0011_auto_20160331_1141'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='comicbook',
|
||||||
|
name='selector',
|
||||||
|
field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='directory',
|
||||||
|
name='selector',
|
||||||
|
field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
281
comic/models.py
281
comic/models.py
@@ -1,13 +1,14 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.transaction import atomic
|
|
||||||
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.transaction import atomic
|
||||||
|
|
||||||
from comic import rarfile
|
from comic import rarfile
|
||||||
from comic.util import get_ordered_dir_list
|
|
||||||
|
import uuid
|
||||||
import zipfile
|
import zipfile
|
||||||
from os import path
|
from os import path, listdir
|
||||||
|
|
||||||
if settings.UNRAR_TOOL:
|
if settings.UNRAR_TOOL:
|
||||||
rarfile.UNRAR_TOOL = settings.UNRAR_TOOL
|
rarfile.UNRAR_TOOL = settings.UNRAR_TOOL
|
||||||
@@ -24,13 +25,67 @@ class Setting(models.Model):
|
|||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
|
|
||||||
|
class Directory(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
parent = models.ForeignKey('Directory', null=True, blank=True)
|
||||||
|
selector = models.UUIDField(unique=True, default=uuid.uuid4, db_index=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'Directory: {0}; {1}'.format(self.name, self.parent)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
l = self.get_path_items()
|
||||||
|
l.reverse()
|
||||||
|
return path.sep.join(l)
|
||||||
|
|
||||||
|
def get_path(self):
|
||||||
|
l = self.get_path_items()
|
||||||
|
l.reverse()
|
||||||
|
return path.sep.join(l)
|
||||||
|
|
||||||
|
def get_path_items(self, p=False):
|
||||||
|
if not p:
|
||||||
|
p = []
|
||||||
|
p.append(self.name)
|
||||||
|
if self.parent:
|
||||||
|
self.parent.get_path_items(p)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def get_path_objects(self, p=False):
|
||||||
|
if not p:
|
||||||
|
p = []
|
||||||
|
p.append(self)
|
||||||
|
if self.parent:
|
||||||
|
self.parent.get_path_objects(p)
|
||||||
|
return p
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_dir_from_path(file_path):
|
||||||
|
file_path = file_path.split(path.sep)
|
||||||
|
print(file_path)
|
||||||
|
for d in Directory.objects.filter(name=file_path[-1]):
|
||||||
|
print(d)
|
||||||
|
if d.get_path_items() == file_path:
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
class ComicBook(models.Model):
|
class ComicBook(models.Model):
|
||||||
file_name = models.CharField(max_length=100, unique=True)
|
file_name = models.CharField(max_length=100, unique=True)
|
||||||
|
date_added = models.DateTimeField(auto_now_add=True)
|
||||||
|
directory = models.ForeignKey(Directory, blank=True, null=True)
|
||||||
|
selector = models.UUIDField(unique=True, default=uuid.uuid4, db_index=True)
|
||||||
|
version = models.IntegerField(default=1)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.file_name
|
return self.file_name
|
||||||
|
|
||||||
def get_image(self, archive_path, page):
|
def get_image(self, page):
|
||||||
|
base_dir = Setting.objects.get(name='BASE_DIR').value
|
||||||
|
if self.directory:
|
||||||
|
archive_path = path.join(base_dir, self.directory.path, self.file_name)
|
||||||
|
else:
|
||||||
|
archive_path = path.join(base_dir, self.file_name)
|
||||||
try:
|
try:
|
||||||
archive = rarfile.RarFile(archive_path)
|
archive = rarfile.RarFile(archive_path)
|
||||||
except rarfile.NotRarFile:
|
except rarfile.NotRarFile:
|
||||||
@@ -62,120 +117,103 @@ class ComicBook(models.Model):
|
|||||||
self.q_prev_to_directory = False
|
self.q_prev_to_directory = False
|
||||||
self.q_next_to_directory = False
|
self.q_next_to_directory = False
|
||||||
|
|
||||||
def nav(self, comic_path, page):
|
def nav(self, page, user):
|
||||||
out = self.Navigation()
|
out = self.Navigation()
|
||||||
out.cur_index = page
|
out.cur_index = page
|
||||||
out.cur_path = comic_path
|
out.cur_path = urlsafe_base64_encode(self.selector.bytes)
|
||||||
|
|
||||||
if page == 0:
|
if page == 0:
|
||||||
out.prev_path, out.prev_index = self.nav_get_prev_comic(comic_path)
|
out.prev_path, out.prev_index = self.nav_get_prev_comic(user)
|
||||||
if out.prev_index == -1:
|
if out.prev_index == -1:
|
||||||
out.q_prev_to_directory = True
|
out.q_prev_to_directory = True
|
||||||
else:
|
else:
|
||||||
out.prev_index = page - 1
|
out.prev_index = page - 1
|
||||||
out.prev_path = comic_path
|
out.prev_path = out.cur_path
|
||||||
|
|
||||||
if self.is_last_page(page):
|
if self.is_last_page(page):
|
||||||
out.next_path, out.next_index = self.nav_get_next_comic(comic_path)
|
out.next_path, out.next_index = self.nav_get_next_comic(user)
|
||||||
if out.next_index == -1:
|
if out.next_index == -1:
|
||||||
out.q_next_to_directory = True
|
out.q_next_to_directory = True
|
||||||
else:
|
else:
|
||||||
out.next_index = page + 1
|
out.next_index = page + 1
|
||||||
out.next_path = comic_path
|
out.next_path = out.cur_path
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@staticmethod
|
def nav_get_prev_comic(self, user):
|
||||||
def nav_get_prev_comic(comic_path):
|
|
||||||
base_dir = Setting.objects.get(name='BASE_DIR').value
|
base_dir = Setting.objects.get(name='BASE_DIR').value
|
||||||
comic_path = urlsafe_base64_decode(comic_path).decode()
|
if self.directory:
|
||||||
directory, comic = path.split(comic_path)
|
folder = path.join(base_dir, self.directory.path)
|
||||||
dir_list = get_ordered_dir_list(path.join(base_dir, directory))
|
else:
|
||||||
comic_index = dir_list.index(comic)
|
folder = base_dir
|
||||||
|
dir_list = ComicBook.get_ordered_dir_list(folder)
|
||||||
|
comic_index = dir_list.index(self.file_name)
|
||||||
if comic_index == 0:
|
if comic_index == 0:
|
||||||
comic_path = urlsafe_base64_encode(directory.encode())
|
if self.directory:
|
||||||
|
comic_path = urlsafe_base64_encode(self.directory.selector.bytes)
|
||||||
|
else:
|
||||||
|
comic_path = ''
|
||||||
index = -1
|
index = -1
|
||||||
else:
|
else:
|
||||||
prev_comic = dir_list[comic_index - 1]
|
prev_comic = dir_list[comic_index - 1]
|
||||||
comic_path = path.join(directory, prev_comic)
|
|
||||||
if not path.isdir(path.join(base_dir, directory, prev_comic)):
|
if not path.isdir(path.join(folder, prev_comic)):
|
||||||
try:
|
try:
|
||||||
book = ComicBook.objects.get(file_name=prev_comic)
|
if self.directory:
|
||||||
except ComicBook.DoesNotExist:
|
book = ComicBook.objects.get(file_name=prev_comic,
|
||||||
book = ComicBook.process_comic_book(base_dir, comic_path, prev_comic)
|
directory=self.directory)
|
||||||
index = ComicPage.objects.filter(Comic=book).count() - 1
|
|
||||||
comic_path = urlsafe_base64_encode(comic_path.encode())
|
|
||||||
else:
|
else:
|
||||||
comic_path = urlsafe_base64_encode(directory.encode())
|
book = ComicBook.objects.get(file_name=prev_comic,
|
||||||
|
directory__isnull=True)
|
||||||
|
except ComicBook.DoesNotExist:
|
||||||
|
if self.directory:
|
||||||
|
book = ComicBook.process_comic_book(prev_comic, self.directory)
|
||||||
|
else:
|
||||||
|
book = ComicBook.process_comic_book(prev_comic)
|
||||||
|
cs, _ = ComicStatus.objects.get_or_create(comic=book, user=user)
|
||||||
|
index = cs.last_read_page
|
||||||
|
comic_path = urlsafe_base64_encode(book.selector.bytes)
|
||||||
|
else:
|
||||||
|
if self.directory:
|
||||||
|
comic_path = urlsafe_base64_encode(self.directory.selector.bytes)
|
||||||
|
else:
|
||||||
|
comic_path = ''
|
||||||
index = -1
|
index = -1
|
||||||
return comic_path, index
|
return comic_path, index
|
||||||
|
|
||||||
@staticmethod
|
def nav_get_next_comic(self, user):
|
||||||
def nav_get_next_comic(comic_path):
|
base_dir = Setting.objects.get(name='BASE_DIR').value
|
||||||
base_dir = Setting.objects.get(name='BASE_DIR')
|
if self.directory:
|
||||||
comic_path = urlsafe_base64_decode(comic_path).decode()
|
folder = path.join(base_dir, self.directory.path)
|
||||||
directory, comic = path.split(comic_path)
|
else:
|
||||||
dir_list = get_ordered_dir_list(path.join(base_dir.value, directory))
|
folder = base_dir
|
||||||
comic_index = dir_list.index(comic)
|
dir_list = ComicBook.get_ordered_dir_list(folder)
|
||||||
|
comic_index = dir_list.index(self.file_name)
|
||||||
try:
|
try:
|
||||||
next_comic = dir_list[comic_index + 1]
|
next_comic = dir_list[comic_index + 1]
|
||||||
comic_path = path.join(directory, next_comic)
|
try:
|
||||||
comic_path = urlsafe_base64_encode(comic_path)
|
if self.directory:
|
||||||
index = 0
|
book = ComicBook.objects.get(file_name=next_comic,
|
||||||
|
directory=self.directory)
|
||||||
|
else:
|
||||||
|
book = ComicBook.objects.get(file_name=next_comic,
|
||||||
|
directory__isnull=True)
|
||||||
|
except ComicBook.DoesNotExist:
|
||||||
|
if self.directory:
|
||||||
|
book = ComicBook.process_comic_book(next_comic, self.directory)
|
||||||
|
else:
|
||||||
|
book = ComicBook.process_comic_book(next_comic)
|
||||||
|
comic_path = urlsafe_base64_encode(book.selector.bytes)
|
||||||
|
cs, _ = ComicStatus.objects.get_or_create(comic=book, user=user)
|
||||||
|
index = cs.last_read_page
|
||||||
except IndexError:
|
except IndexError:
|
||||||
comic_path = urlsafe_base64_encode(directory)
|
if self.directory:
|
||||||
|
comic_path = urlsafe_base64_encode(self.directory.selector.bytes)
|
||||||
|
else:
|
||||||
|
comic_path = ''
|
||||||
index = -1
|
index = -1
|
||||||
return comic_path, index
|
return comic_path, index
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def process_comic_book(base_dir, comic_path, comic_file_name):
|
|
||||||
try:
|
|
||||||
cbx = rarfile.RarFile(path.join(base_dir, comic_path))
|
|
||||||
except rarfile.NotRarFile:
|
|
||||||
cbx = zipfile.ZipFile(path.join(base_dir, comic_path))
|
|
||||||
except zipfile.BadZipfile:
|
|
||||||
return False
|
|
||||||
with atomic():
|
|
||||||
book = ComicBook(file_name=comic_file_name)
|
|
||||||
book.save()
|
|
||||||
i = 0
|
|
||||||
for f in sorted([str(x) for x in cbx.namelist()], key=str.lower):
|
|
||||||
try:
|
|
||||||
dot_index = f.rindex('.') + 1
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
ext = f.lower()[dot_index:]
|
|
||||||
if ext in ['jpg', 'jpeg']:
|
|
||||||
page = ComicPage(Comic=book,
|
|
||||||
index=i,
|
|
||||||
page_file_name=f,
|
|
||||||
content_type='image/jpeg')
|
|
||||||
page.save()
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
elif ext == 'png':
|
|
||||||
page = ComicPage(Comic=book,
|
|
||||||
index=i,
|
|
||||||
page_file_name=f,
|
|
||||||
content_type='image/png')
|
|
||||||
page.save()
|
|
||||||
i += 1
|
|
||||||
elif ext == 'bmp':
|
|
||||||
page = ComicPage(Comic=book,
|
|
||||||
index=i,
|
|
||||||
page_file_name=f,
|
|
||||||
content_type='image/bmp')
|
|
||||||
page.save()
|
|
||||||
i += 1
|
|
||||||
elif ext == 'gif':
|
|
||||||
page = ComicPage(Comic=book,
|
|
||||||
index=i,
|
|
||||||
page_file_name=f,
|
|
||||||
content_type='image/gif')
|
|
||||||
page.save()
|
|
||||||
i += 1
|
|
||||||
return book
|
|
||||||
|
|
||||||
class DirFile:
|
class DirFile:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.name = ''
|
self.name = ''
|
||||||
@@ -192,7 +230,7 @@ class ComicBook(models.Model):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_directory(user, base_dir, comic_path):
|
def generate_directory(user, base_dir, comic_path):
|
||||||
files = []
|
files = []
|
||||||
for fn in get_ordered_dir_list(path.join(base_dir, comic_path)):
|
for fn in ComicBook.get_ordered_dir_list(path.join(base_dir, comic_path)):
|
||||||
df = ComicBook.DirFile()
|
df = ComicBook.DirFile()
|
||||||
df.name = fn
|
df.name = fn
|
||||||
if path.isdir(path.join(base_dir, comic_path, fn)):
|
if path.isdir(path.join(base_dir, comic_path, fn)):
|
||||||
@@ -232,6 +270,76 @@ class ComicBook(models.Model):
|
|||||||
def page_name(self, index):
|
def page_name(self, index):
|
||||||
return ComicPage.objects.get(Comic=self, index=index).page_file_name
|
return ComicPage.objects.get(Comic=self, index=index).page_file_name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def process_comic_book(comic_file_name, directory=False):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:type comic_file_name: str
|
||||||
|
:type directory: Directory
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
book = ComicBook.objects.get(file_name=comic_file_name,
|
||||||
|
version=0)
|
||||||
|
book.directory = directory
|
||||||
|
book.version = 1
|
||||||
|
book.save()
|
||||||
|
return book
|
||||||
|
except ComicBook.DoesNotExist:
|
||||||
|
pass
|
||||||
|
base_dir = Setting.objects.get(name='BASE_DIR').value
|
||||||
|
if directory:
|
||||||
|
comic_full_path = path.join(base_dir, directory.get_path(), comic_file_name)
|
||||||
|
else:
|
||||||
|
comic_full_path = path.join(base_dir, comic_file_name)
|
||||||
|
try:
|
||||||
|
cbx = rarfile.RarFile(comic_full_path)
|
||||||
|
except rarfile.NotRarFile:
|
||||||
|
cbx = zipfile.ZipFile(comic_full_path)
|
||||||
|
except zipfile.BadZipfile:
|
||||||
|
return False
|
||||||
|
with atomic():
|
||||||
|
if directory:
|
||||||
|
book = ComicBook(file_name=comic_file_name,
|
||||||
|
directory=directory)
|
||||||
|
else:
|
||||||
|
book = ComicBook(file_name=comic_file_name)
|
||||||
|
book.save()
|
||||||
|
page_index = 0
|
||||||
|
for page_file_name in sorted([str(x) for x in cbx.namelist()], key=str.lower):
|
||||||
|
try:
|
||||||
|
dot_index = page_file_name.rindex('.') + 1
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
ext = page_file_name.lower()[dot_index:]
|
||||||
|
if ext in ['jpg', 'jpeg']:
|
||||||
|
content_type = 'image/jpeg'
|
||||||
|
elif ext == 'png':
|
||||||
|
content_type = 'image/png'
|
||||||
|
elif ext == 'bmp':
|
||||||
|
content_type = 'image/bmp'
|
||||||
|
elif ext == 'gif':
|
||||||
|
content_type = 'image/gif'
|
||||||
|
else:
|
||||||
|
content_type = 'text/plain'
|
||||||
|
page = ComicPage(Comic=book,
|
||||||
|
index=page_index,
|
||||||
|
page_file_name=page_file_name,
|
||||||
|
content_type=content_type)
|
||||||
|
page.save()
|
||||||
|
page_index += 1
|
||||||
|
return book
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_ordered_dir_list(folder):
|
||||||
|
directories = []
|
||||||
|
files = []
|
||||||
|
for item in listdir(folder):
|
||||||
|
if path.isdir(path.join(folder, item)):
|
||||||
|
directories.append(item)
|
||||||
|
else:
|
||||||
|
files.append(item)
|
||||||
|
return sorted(directories) + sorted(files)
|
||||||
|
|
||||||
|
|
||||||
class ComicPage(models.Model):
|
class ComicPage(models.Model):
|
||||||
Comic = models.ForeignKey(ComicBook)
|
Comic = models.ForeignKey(ComicBook)
|
||||||
@@ -246,5 +354,4 @@ class ComicStatus(models.Model):
|
|||||||
last_read_page = models.IntegerField(default=0)
|
last_read_page = models.IntegerField(default=0)
|
||||||
unread = models.BooleanField(default=True)
|
unread = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
|
||||||
# TODO: add support to reference items last being read
|
# TODO: add support to reference items last being read
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ urlpatterns = [
|
|||||||
url(r'^settings/users/(?P<user_id>[0-9]+)/$', views.user_config_page, name='users'),
|
url(r'^settings/users/(?P<user_id>[0-9]+)/$', views.user_config_page, name='users'),
|
||||||
url(r'^settings/users/add/$', views.user_add_page, name='users'),
|
url(r'^settings/users/add/$', views.user_add_page, name='users'),
|
||||||
url(r'^account/$', views.account_page, name='account'),
|
url(r'^account/$', views.account_page, name='account'),
|
||||||
url(r'^(?P<comic_path>[\w]+)/$', views.comic_list, name='comic_list'),
|
url(r'^read/(?P<comic_selector>[\w-]+)/(?P<page>[0-9]+)/$', views.read_comic, name='read_comic'),
|
||||||
url(r'^read/(?P<comic_path>[\w]+)/(?P<page>[0-9]+)/$', views.read_comic, name='read_comic'),
|
url(r'^read/(?P<comic_selector>[\w-]+)/(?P<page>[0-9]+)/img$', views.get_image, name='get_image'),
|
||||||
url(r'^read/(?P<comic_path>[\w]+)/(?P<page>[0-9]+)/img$', views.get_image, name='get_image'),
|
url(r'^(?P<directory_selector>[\w-]+)/$', views.comic_list, name='comic_list'),
|
||||||
]
|
]
|
||||||
|
|||||||
119
comic/util.py
119
comic/util.py
@@ -1,14 +1,15 @@
|
|||||||
|
from collections import OrderedDict
|
||||||
|
from os import path, listdir
|
||||||
|
|
||||||
from django.utils.http import urlsafe_base64_encode
|
from django.utils.http import urlsafe_base64_encode
|
||||||
|
|
||||||
from os import path
|
from .models import ComicBook, Setting, ComicStatus, Directory
|
||||||
from collections import OrderedDict
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def generate_title_from_path(path):
|
def generate_title_from_path(file_path):
|
||||||
if path == '':
|
if file_path == '':
|
||||||
return 'CBWebReader'
|
return 'CBWebReader'
|
||||||
return 'CBWebReader - ' + ' - '.join(path.split(os.sep))
|
return 'CBWebReader - ' + ' - '.join(file_path.split(path.sep))
|
||||||
|
|
||||||
|
|
||||||
class Menu:
|
class Menu:
|
||||||
@@ -39,19 +40,22 @@ class Breadcrumb:
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
def generate_breadcrumbs_from_path(comic_path):
|
def generate_breadcrumbs_from_path(directory=False):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:type directory: Directory
|
||||||
|
"""
|
||||||
output = [Breadcrumb()]
|
output = [Breadcrumb()]
|
||||||
prefix = '/comic/'
|
prefix = b'/comic/'
|
||||||
last = ''
|
last = ''
|
||||||
comic_path = path.normpath(comic_path)
|
if directory:
|
||||||
folders = comic_path.split(os.sep)
|
folders = directory.get_path_objects()
|
||||||
for item in folders:
|
else:
|
||||||
if item == '.':
|
folders = []
|
||||||
continue
|
for item in folders[::-1]:
|
||||||
bc = Breadcrumb()
|
bc = Breadcrumb()
|
||||||
bc.name = item
|
bc.name = item.name
|
||||||
last = path.join(last, item)
|
bc.url = prefix + urlsafe_base64_encode(item.selector.bytes)
|
||||||
bc.url = prefix + urlsafe_base64_encode(last.encode()).decode()
|
|
||||||
output.append(bc)
|
output.append(bc)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@@ -66,12 +70,93 @@ def generate_breadcrumbs_from_menu(paths):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def generate_directory(user, directory=False):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:type user: User
|
||||||
|
:type directory: Directory
|
||||||
|
"""
|
||||||
|
base_dir = Setting.objects.get(name='BASE_DIR').value
|
||||||
|
files = []
|
||||||
|
if directory:
|
||||||
|
dir_path = directory.path
|
||||||
|
ordered_dir_list = get_ordered_dir_list(path.join(base_dir, directory.path))
|
||||||
|
else:
|
||||||
|
dir_path = ''
|
||||||
|
ordered_dir_list = get_ordered_dir_list(base_dir)
|
||||||
|
for file_name in ordered_dir_list:
|
||||||
|
df = ComicBook.DirFile()
|
||||||
|
df.name = file_name
|
||||||
|
if path.isdir(path.join(base_dir, dir_path, file_name)):
|
||||||
|
if directory:
|
||||||
|
d = Directory.objects.get(name=file_name,
|
||||||
|
parent=directory)
|
||||||
|
else:
|
||||||
|
d = Directory.objects.get(name=file_name,
|
||||||
|
parent__isnull=True)
|
||||||
|
df.isdir = True
|
||||||
|
df.icon = 'glyphicon-folder-open'
|
||||||
|
df.location = urlsafe_base64_encode(d.selector.bytes)
|
||||||
|
elif file_name.lower()[-4:] in ['.rar', '.zip', '.cbr', '.cbz']:
|
||||||
|
df.iscb = True
|
||||||
|
df.icon = 'glyphicon-book'
|
||||||
|
try:
|
||||||
|
if directory:
|
||||||
|
book = ComicBook.objects.get(file_name=file_name,
|
||||||
|
directory=directory)
|
||||||
|
else:
|
||||||
|
book = ComicBook.objects.get(file_name=file_name,
|
||||||
|
directory__isnull=True)
|
||||||
|
except ComicBook.DoesNotExist:
|
||||||
|
book = ComicBook.process_comic_book(file_name, directory)
|
||||||
|
df.location = urlsafe_base64_encode(book.selector.bytes)
|
||||||
|
status, _ = ComicStatus.objects.get_or_create(comic=book, user=user)
|
||||||
|
last_page = status.last_read_page
|
||||||
|
if status.unread:
|
||||||
|
df.label = '<span class="label label-default pull-right">Unread</span>'
|
||||||
|
elif (last_page + 1) == book.page_count:
|
||||||
|
df.label = '<span class="label label-success pull-right">Read</span>'
|
||||||
|
df.cur_page = last_page
|
||||||
|
else:
|
||||||
|
label_text = '<span class="label label-primary pull-right">%s/%s</span>' % \
|
||||||
|
(last_page + 1, book.page_count)
|
||||||
|
df.label = label_text
|
||||||
|
df.cur_page = last_page
|
||||||
|
|
||||||
|
# df.label = '<span class="label label-danger pull-right">Unprocessed</span>'
|
||||||
|
files.append(df)
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
def get_ordered_dir_list(folder):
|
def get_ordered_dir_list(folder):
|
||||||
directories = []
|
directories = []
|
||||||
files = []
|
files = []
|
||||||
for item in os.listdir(folder):
|
for item in listdir(folder):
|
||||||
if path.isdir(path.join(folder, item)):
|
if path.isdir(path.join(folder, item)):
|
||||||
directories.append(item)
|
directories.append(item)
|
||||||
else:
|
else:
|
||||||
files.append(item)
|
files.append(item)
|
||||||
return sorted(directories) + sorted(files)
|
return sorted(directories) + sorted(files)
|
||||||
|
|
||||||
|
|
||||||
|
def scan_directory(directory=False):
|
||||||
|
"""
|
||||||
|
TODO: Increase efficiency of this. reduce amount of queries.
|
||||||
|
:type directory: Directory
|
||||||
|
"""
|
||||||
|
base_dir = Setting.objects.get(name='BASE_DIR').value
|
||||||
|
# filter(os.path.isdir, os.listdir(os.getcwd()))
|
||||||
|
if directory:
|
||||||
|
full_path = path.join(base_dir, directory.path)
|
||||||
|
else:
|
||||||
|
full_path = base_dir
|
||||||
|
dir_list = listdir(full_path)
|
||||||
|
directorys = [d for d in dir_list if path.isdir(path.join(full_path, d))]
|
||||||
|
for direct in directorys:
|
||||||
|
if directory:
|
||||||
|
d, created = Directory.objects.get_or_create(name=direct,
|
||||||
|
parent=directory)
|
||||||
|
else:
|
||||||
|
d, created = Directory.objects.get_or_create(name=direct)
|
||||||
|
if created:
|
||||||
|
d.save()
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
from django.http import HttpResponse
|
import uuid
|
||||||
from django.template import RequestContext
|
from os import path
|
||||||
from django.utils.http import urlsafe_base64_decode
|
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.contrib.auth import login, authenticate
|
||||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth import login, authenticate
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
|
from django.utils.http import urlsafe_base64_decode
|
||||||
|
|
||||||
from .models import Setting, ComicBook, ComicStatus
|
|
||||||
from .util import generate_breadcrumbs_from_path, generate_breadcrumbs_from_menu, generate_title_from_path, Menu
|
|
||||||
from .forms import SettingsForm, AccountForm, EditUserForm, AddUserForm, InitialSetupForm
|
from .forms import SettingsForm, AccountForm, EditUserForm, AddUserForm, InitialSetupForm
|
||||||
|
from .models import Setting, ComicBook, ComicStatus, Directory
|
||||||
from os import path
|
from .util import generate_breadcrumbs_from_path, generate_breadcrumbs_from_menu, \
|
||||||
|
generate_title_from_path, Menu, generate_directory, scan_directory
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def comic_list(request, comic_path=''):
|
def comic_list(request, directory_selector=False):
|
||||||
try:
|
try:
|
||||||
base_dir = Setting.objects.get(name='BASE_DIR').value
|
base_dir = Setting.objects.get(name='BASE_DIR').value
|
||||||
except Setting.DoesNotExist:
|
except Setting.DoesNotExist:
|
||||||
@@ -22,16 +23,28 @@ def comic_list(request, comic_path=''):
|
|||||||
if not path.isdir(base_dir):
|
if not path.isdir(base_dir):
|
||||||
return redirect('/comic/settings/')
|
return redirect('/comic/settings/')
|
||||||
|
|
||||||
comic_path = urlsafe_base64_decode(comic_path).decode()
|
if directory_selector:
|
||||||
title = generate_title_from_path(comic_path)
|
directory_selector = uuid.UUID(bytes=urlsafe_base64_decode(directory_selector))
|
||||||
files = ComicBook.generate_directory(request.user, base_dir, comic_path)
|
directory = Directory.objects.get(selector=directory_selector)
|
||||||
context = {
|
else:
|
||||||
|
directory = False
|
||||||
|
|
||||||
|
scan_directory(directory)
|
||||||
|
|
||||||
|
if directory:
|
||||||
|
title = generate_title_from_path(directory.path)
|
||||||
|
breadcrumbs = generate_breadcrumbs_from_path(directory)
|
||||||
|
else:
|
||||||
|
title = generate_title_from_path('Home')
|
||||||
|
breadcrumbs = generate_breadcrumbs_from_path()
|
||||||
|
files = generate_directory(request.user, directory)
|
||||||
|
|
||||||
|
return render(request, 'comic/comic_list.html', {
|
||||||
'file_list': files,
|
'file_list': files,
|
||||||
'breadcrumbs': generate_breadcrumbs_from_path(comic_path),
|
'breadcrumbs': breadcrumbs,
|
||||||
'menu': Menu(request.user, 'Browse'),
|
'menu': Menu(request.user, 'Browse'),
|
||||||
'title': title,
|
'title': title,
|
||||||
}
|
})
|
||||||
return render(request, 'comic/comic_list.html', context)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -184,16 +197,19 @@ def settings_page(request):
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def read_comic(request, comic_path, page):
|
def read_comic(request, comic_selector, page):
|
||||||
base_dir = Setting.objects.get(name='BASE_DIR').value
|
base_dir = Setting.objects.get(name='BASE_DIR').value
|
||||||
page = int(page)
|
page = int(page)
|
||||||
decoded_path = urlsafe_base64_decode(comic_path).decode()
|
selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector))
|
||||||
breadcrumbs = generate_breadcrumbs_from_path(decoded_path)
|
book = get_object_or_404(ComicBook, selector=selector)
|
||||||
_, comic_file_name = path.split(decoded_path)
|
|
||||||
try:
|
breadcrumbs = generate_breadcrumbs_from_path(book.directory)
|
||||||
book = ComicBook.objects.get(file_name=comic_file_name)
|
#comic_file_path, comic_file_name = path.split(decoded_path)
|
||||||
except ComicBook.DoesNotExist:
|
#d = Directory.get_dir_from_path(comic_file_path)
|
||||||
book = ComicBook.process_comic_book(base_dir, decoded_path, comic_file_name)
|
#try:
|
||||||
|
# book = ComicBook.objects.get(file_name=comic_file_name)
|
||||||
|
#except ComicBook.DoesNotExist:
|
||||||
|
# book = ComicBook.process_comic_book(comic_file_name, d)
|
||||||
status, _ = ComicStatus.objects.get_or_create(comic=book, user=request.user)
|
status, _ = ComicStatus.objects.get_or_create(comic=book, user=request.user)
|
||||||
status.unread = False
|
status.unread = False
|
||||||
status.last_read_page = page
|
status.last_read_page = page
|
||||||
@@ -202,7 +218,7 @@ def read_comic(request, comic_path, page):
|
|||||||
context = {
|
context = {
|
||||||
'book': book,
|
'book': book,
|
||||||
'orig_file_name': book.page_name(page),
|
'orig_file_name': book.page_name(page),
|
||||||
'nav': book.nav(comic_path, page),
|
'nav': book.nav(page, request.user),
|
||||||
'breadcrumbs': breadcrumbs,
|
'breadcrumbs': breadcrumbs,
|
||||||
'menu': Menu(request.user),
|
'menu': Menu(request.user),
|
||||||
'title': title,
|
'title': title,
|
||||||
@@ -211,17 +227,10 @@ def read_comic(request, comic_path, page):
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def get_image(_, comic_path, page):
|
def get_image(_, comic_selector, page):
|
||||||
base_dir = Setting.objects.get(name='BASE_DIR').value
|
selector = uuid.UUID(bytes=urlsafe_base64_decode(comic_selector))
|
||||||
page = int(page)
|
book = ComicBook.objects.get(selector=selector)
|
||||||
decoded_path = urlsafe_base64_decode(comic_path).decode()
|
img, content = book.get_image(int(page))
|
||||||
_, comic_file_name = path.split(decoded_path)
|
|
||||||
try:
|
|
||||||
book = ComicBook.objects.get(file_name=comic_file_name)
|
|
||||||
except ComicBook.DoesNotExist:
|
|
||||||
book = ComicBook.process_comic_book(base_dir, decoded_path, comic_file_name)
|
|
||||||
full_path = path.join(base_dir, decoded_path)
|
|
||||||
img, content = book.get_image(full_path, page)
|
|
||||||
return HttpResponse(img.read(), content_type=content)
|
return HttpResponse(img.read(), content_type=content)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user