Add experimental SANE scanner source.

This commit is contained in:
Roberto Rosario
2017-02-05 04:02:12 -04:00
parent b58fa7e241
commit 25f1f7d067
10 changed files with 227 additions and 52 deletions

View File

@@ -24,6 +24,7 @@ from .handlers import (
from .links import (
link_document_create_multiple, link_setup_sources,
link_setup_source_create_imap_email, link_setup_source_create_pop3_email,
link_setup_source_create_sane_scanner,
link_setup_source_create_watch_folder, link_setup_source_create_webform,
link_setup_source_create_staging_folder, link_setup_source_delete,
link_setup_source_edit, link_setup_source_logs, link_staging_file_delete,
@@ -44,6 +45,7 @@ class SourcesApp(MayanAppConfig):
IMAPEmail = self.get_model('IMAPEmail')
Source = self.get_model('Source')
SourceLog = self.get_model('SourceLog')
SaneScanner = self.get_model('SaneScanner')
StagingFolderSource = self.get_model('StagingFolderSource')
WatchFolderSource = self.get_model('WatchFolderSource')
WebFormSource = self.get_model('WebFormSource')
@@ -119,8 +121,8 @@ class SourcesApp(MayanAppConfig):
link_setup_source_edit, link_setup_source_delete,
link_transformation_list, link_setup_source_logs
), sources=(
POP3Email, IMAPEmail, StagingFolderSource, WatchFolderSource,
WebFormSource
POP3Email, IMAPEmail, SaneScanner, StagingFolderSource,
WatchFolderSource, WebFormSource
)
)
menu_object.bind_links(
@@ -129,6 +131,7 @@ class SourcesApp(MayanAppConfig):
menu_secondary.bind_links(
links=(
link_setup_sources, link_setup_source_create_webform,
link_setup_source_create_sane_scanner,
link_setup_source_create_staging_folder,
link_setup_source_create_pop3_email,
link_setup_source_create_imap_email,

View File

@@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _
from documents.forms import DocumentForm
from .models import (
IMAPEmail, POP3Email, StagingFolderSource, WebFormSource,
IMAPEmail, POP3Email, SaneScanner, StagingFolderSource, WebFormSource,
WatchFolderSource
)
@@ -79,6 +79,16 @@ class WebFormUploadFormHTML5(WebFormUploadForm):
)
class SaneScannerUploadForm(UploadBaseForm):
pass
class SaneScannerSetupForm(forms.ModelForm):
class Meta:
fields = ('label', 'device_name', 'mode', 'resolution', 'enabled')
model = SaneScanner
class WebFormSetupForm(forms.ModelForm):
class Meta:
fields = ('label', 'enabled', 'uncompress')

View File

@@ -10,7 +10,7 @@ from navigation import Link
from .literals import (
SOURCE_CHOICE_WEB_FORM, SOURCE_CHOICE_EMAIL_IMAP, SOURCE_CHOICE_EMAIL_POP3,
SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WATCH
SOURCE_CHOICE_SANE_SCANNER, SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WATCH
)
from .permissions import (
permission_sources_setup_create, permission_sources_setup_delete,
@@ -59,6 +59,11 @@ link_setup_source_create_webform = Link(
text=_('Add new webform source'), view='sources:setup_source_create',
args='"%s"' % SOURCE_CHOICE_WEB_FORM
)
link_setup_source_create_sane_scanner = Link(
permissions=(permission_sources_setup_create,),
text=_('Add new SANE scanner'), view='sources:setup_source_create',
args='"%s"' % SOURCE_CHOICE_SANE_SCANNER
)
link_setup_source_delete = Link(
permissions=(permission_sources_setup_delete,), tags='dangerous',
text=_('Delete'), view='sources:setup_source_delete',

View File

@@ -2,6 +2,16 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
SCANNER_MODE_LINEART = 'lineart'
SCANNER_MODE_MONOCHROME = 'monochrome'
SCANNER_MODE_COLOR = 'color'
SCANNER_MODE_CHOICES = (
(SCANNER_MODE_LINEART, _('Lineart')),
(SCANNER_MODE_MONOCHROME, _('Monochrome')),
(SCANNER_MODE_COLOR, _('Color'))
)
SOURCE_UNCOMPRESS_CHOICE_Y = 'y'
SOURCE_UNCOMPRESS_CHOICE_N = 'n'
SOURCE_UNCOMPRESS_CHOICE_ASK = 'a'
@@ -22,8 +32,10 @@ SOURCE_CHOICE_STAGING = 'staging'
SOURCE_CHOICE_WATCH = 'watch'
SOURCE_CHOICE_EMAIL_POP3 = 'pop3'
SOURCE_CHOICE_EMAIL_IMAP = 'imap'
SOURCE_CHOICE_SANE_SCANNER = 'sane'
SOURCE_CHOICES = (
(SOURCE_CHOICE_SANE_SCANNER, _('Scanner')),
(SOURCE_CHOICE_WEB_FORM, _('Web form')),
(SOURCE_CHOICE_STAGING, _('Staging folder')),
(SOURCE_CHOICE_WATCH, _('Watch folder')),

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-05 04:19
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sources', '0010_auto_20151001_0055'),
]
operations = [
migrations.CreateModel(
name='SaneScanner',
fields=[
('interactivesource_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='sources.InteractiveSource')),
('device_name', models.CharField(help_text='Device name as returned by the SANE backend.', max_length=255, verbose_name='Device name')),
],
options={
'verbose_name': 'SANE Scanner',
'verbose_name_plural': 'SANE Scanners',
},
bases=('sources.interactivesource',),
),
]

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-05 07:43
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sources', '0011_sanescanner'),
]
operations = [
migrations.AddField(
model_name='sanescanner',
name='mode',
field=models.CharField(choices=[('lineart', 'Lineart'), ('monochrome', 'Monochrome'), ('color', 'Color')], default='color', max_length=16, verbose_name='Mode'),
),
migrations.AddField(
model_name='sanescanner',
name='resolution',
field=models.PositiveIntegerField(default=300, help_text='Sets the resolution of the scanned image in DPI (dots per inch).', verbose_name='Resolution'),
),
]

View File

@@ -8,19 +8,28 @@ import json
import logging
import os
import poplib
import subprocess
import sh
import yaml
try:
scanimage = sh.Command('/usr/bin/scanimage')
except sh.CommandNotFound:
scanimage = None
from django.core.exceptions import ValidationError
from django.core.files import File
from django.core.files.base import ContentFile
from django.db import models, transaction
from django.utils.encoding import python_2_unicode_compatible
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from model_utils.managers import InheritanceManager
from common.compressed_files import CompressedFile, NotACompressedFile
from common.utils import TemporaryFile
from converter.literals import DIMENSION_SEPARATOR
from converter.models import Transformation
from djcelery.models import PeriodicTask, IntervalSchedule
@@ -30,15 +39,17 @@ from metadata.api import save_metadata_list, set_bulk_metadata
from metadata.models import MetadataType
from tags.models import Tag
from .classes import Attachment, SourceUploadedFile, StagingFile
from .classes import Attachment, PseudoFile, SourceUploadedFile, StagingFile
from .literals import (
DEFAULT_INTERVAL, DEFAULT_POP3_TIMEOUT, DEFAULT_IMAP_MAILBOX,
DEFAULT_METADATA_ATTACHMENT_NAME, SOURCE_CHOICES, SOURCE_CHOICE_STAGING,
SOURCE_CHOICE_WATCH, SOURCE_CHOICE_WEB_FORM,
SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES, SOURCE_UNCOMPRESS_CHOICES,
SOURCE_UNCOMPRESS_CHOICE_N, SOURCE_UNCOMPRESS_CHOICE_Y,
SOURCE_CHOICE_EMAIL_IMAP, SOURCE_CHOICE_EMAIL_POP3
DEFAULT_METADATA_ATTACHMENT_NAME, SCANNER_MODE_COLOR, SCANNER_MODE_CHOICES,
SOURCE_CHOICES,SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WATCH,
SOURCE_CHOICE_WEB_FORM, SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES,
SOURCE_UNCOMPRESS_CHOICES, SOURCE_UNCOMPRESS_CHOICE_N,
SOURCE_UNCOMPRESS_CHOICE_Y, SOURCE_CHOICE_EMAIL_IMAP,
SOURCE_CHOICE_EMAIL_POP3, SOURCE_CHOICE_SANE_SCANNER
)
from .settings import setting_scanimage_path
logger = logging.getLogger(__name__)
@@ -153,7 +164,64 @@ class InteractiveSource(Source):
verbose_name_plural = _('Interactive sources')
class SaneScanner(InteractiveSource):
can_compress = False
is_interactive = True
source_type = SOURCE_CHOICE_SANE_SCANNER
device_name = models.CharField(
max_length=255,
help_text=_('Device name as returned by the SANE backend.'),
verbose_name=_('Device name')
)
mode = models.CharField(
choices=SCANNER_MODE_CHOICES, default=SCANNER_MODE_COLOR,
max_length=16, verbose_name=_('Mode')
)
resolution = models.PositiveIntegerField(
default=300, help_text=_(
'Sets the resolution of the scanned image in DPI (dots per inch).'
), verbose_name=_('Resolution')
)
class Meta:
verbose_name = _('SANE Scanner')
verbose_name_plural = _('SANE Scanners')
def clean_up_upload_file(self, upload_file_object):
pass
def get_upload_file_object(self, form_data):
temporary_file_object = TemporaryFile()
try:
command_line = [
setting_scanimage_path.value, '-d', self.device_name,
'--resolution', '{}'.format(self.resolution), '--mode',
self.mode, '--format', 'tiff'
]
logger.debug('Scan command line: %s', command_line)
result = subprocess.check_call(
command_line, stdout=temporary_file_object
)
except subprocess.CalledProcessError as exception:
logger.error(
'Exception while scanning from source:%s ; %s', self,
exception
)
self.logs.create(
message=_('Error while scanning; %s') % exception
)
return SourceUploadedFile(
source=self, file=PseudoFile(
file=temporary_file_object, name='scan {}'.format(now())
)
)
class StagingFolderSource(InteractiveSource):
can_compress = True
is_interactive = True
source_type = SOURCE_CHOICE_STAGING
@@ -234,6 +302,7 @@ class StagingFolderSource(InteractiveSource):
class WebFormSource(InteractiveSource):
can_compress = True
is_interactive = True
source_type = SOURCE_CHOICE_WEB_FORM

View File

@@ -0,0 +1,15 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from smart_settings import Namespace
namespace = Namespace(name='sources', label=_('Sources'))
setting_scanimage_path = namespace.add_setting(
global_name='SOURCE_SCANIMAGE_PATH', default='/usr/bin/scanimage',
help_text=_(
'File path to the scanimage program used to control image scanners.'
),
is_path=True
)

View File

@@ -1,14 +1,16 @@
from .forms import (
POP3EmailSetupForm, IMAPEmailSetupForm,
StagingFolderSetupForm, StagingUploadForm, WatchFolderSetupForm,
WebFormSetupForm, WebFormUploadForm
POP3EmailSetupForm, IMAPEmailSetupForm, SaneScannerSetupForm,
SaneScannerUploadForm, StagingFolderSetupForm, StagingUploadForm,
WatchFolderSetupForm, WebFormSetupForm, WebFormUploadForm
)
from .literals import (
SOURCE_CHOICE_EMAIL_IMAP, SOURCE_CHOICE_EMAIL_POP3, SOURCE_CHOICE_STAGING,
SOURCE_CHOICE_WATCH, SOURCE_CHOICE_WEB_FORM
SOURCE_CHOICE_EMAIL_IMAP, SOURCE_CHOICE_EMAIL_POP3,
SOURCE_CHOICE_SANE_SCANNER, SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WATCH,
SOURCE_CHOICE_WEB_FORM
)
from .models import (
IMAPEmail, POP3Email, StagingFolderSource, WatchFolderSource, WebFormSource
IMAPEmail, POP3Email, SaneScanner, StagingFolderSource, WatchFolderSource,
WebFormSource
)
@@ -23,6 +25,8 @@ def get_class(source_type):
return POP3Email
elif source_type == SOURCE_CHOICE_EMAIL_IMAP:
return IMAPEmail
elif source_type == SOURCE_CHOICE_SANE_SCANNER:
return SaneScanner
def get_form_class(source_type):
@@ -36,6 +40,8 @@ def get_form_class(source_type):
return POP3EmailSetupForm
elif source_type == SOURCE_CHOICE_EMAIL_IMAP:
return IMAPEmailSetupForm
elif source_type == SOURCE_CHOICE_SANE_SCANNER:
return SaneScannerSetupForm
def get_upload_form_class(source_type):
@@ -43,3 +49,5 @@ def get_upload_form_class(source_type):
return WebFormUploadForm
elif source_type == SOURCE_CHOICE_STAGING:
return StagingUploadForm
elif source_type == SOURCE_CHOICE_SANE_SCANNER:
return SaneScannerUploadForm

View File

@@ -25,16 +25,17 @@ from metadata.api import decode_metadata_from_url
from navigation import Link
from .forms import (
NewDocumentForm, NewVersionForm, WebFormUploadForm,
NewDocumentForm, NewVersionForm, SaneScannerUploadForm, WebFormUploadForm,
WebFormUploadFormHTML5
)
from .literals import (
SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WEB_FORM,
SOURCE_CHOICE_STAGING, SOURCE_CHOICE_SANE_SCANNER, SOURCE_CHOICE_WEB_FORM,
SOURCE_UNCOMPRESS_CHOICE_ASK,
SOURCE_UNCOMPRESS_CHOICE_Y
)
from .models import (
InteractiveSource, Source, StagingFolderSource, WebFormSource
InteractiveSource, Source, SaneScanner, StagingFolderSource,
WebFormSource
)
from .permissions import (
permission_sources_setup_create, permission_sources_setup_delete,
@@ -88,27 +89,10 @@ class UploadBaseView(MultiFormView):
@staticmethod
def get_active_tab_links(document=None):
tab_links = []
web_forms = WebFormSource.objects.filter(enabled=True)
for web_form in web_forms:
tab_links.append(
UploadBaseView.get_tab_link_for_source(web_form, document)
)
staging_folders = StagingFolderSource.objects.filter(enabled=True)
for staging_folder in staging_folders:
tab_links.append(
UploadBaseView.get_tab_link_for_source(
staging_folder, document
)
)
return {
'tab_links': tab_links,
SOURCE_CHOICE_WEB_FORM: web_forms,
SOURCE_CHOICE_STAGING: staging_folders,
}
return [
UploadBaseView.get_tab_link_for_source(source, document)
for source in InteractiveSource.objects.filter(enabled=True).select_subclasses()
]
def dispatch(self, request, *args, **kwargs):
if 'source_id' in kwargs:
@@ -163,6 +147,16 @@ class UploadBaseView(MultiFormView):
}
},
]
elif isinstance(self.source, SaneScanner):
subtemplates_list.append({
'name': 'sources/upload_multiform_subtemplate.html',
'context': {
'forms': context['forms'],
'is_multipart': True,
'title': _('Document properties'),
'submit_label': _('Scan'),
},
})
else:
subtemplates_list.append({
'name': 'sources/upload_multiform_subtemplate.html',
@@ -173,8 +167,8 @@ class UploadBaseView(MultiFormView):
},
})
menu_facet.bound_links['sources:upload_interactive'] = self.tab_links['tab_links']
menu_facet.bound_links['sources:upload_version'] = self.tab_links['tab_links']
menu_facet.bound_links['sources:upload_interactive'] = self.tab_links
menu_facet.bound_links['sources:upload_version'] = self.tab_links
context.update({
'subtemplates_list': subtemplates_list,
@@ -206,6 +200,7 @@ class UploadInteractiveView(UploadBaseView):
).dispatch(request, *args, **kwargs)
def forms_valid(self, forms):
if self.source.can_compress:
if self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK:
expand = forms['source_form'].cleaned_data.get('expand')
else:
@@ -213,6 +208,8 @@ class UploadInteractiveView(UploadBaseView):
expand = True
else:
expand = False
else:
expand = False
uploaded_file = self.source.get_upload_file_object(
forms['source_form'].cleaned_data
@@ -260,12 +257,15 @@ class UploadInteractiveView(UploadBaseView):
return HttpResponseRedirect(self.request.get_full_path())
def create_source_form_form(self, **kwargs):
if hasattr(self.source, 'uncompress'):
show_expand = self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK
else:
show_expand = False
return self.get_form_classes()['source_form'](
prefix=kwargs['prefix'],
source=self.source,
show_expand=(
self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK
),
show_expand=show_expand,
data=kwargs.get('data', None),
files=kwargs.get('files', None),
)
@@ -295,7 +295,7 @@ class UploadInteractiveView(UploadBaseView):
context['title'] = _(
'Upload a local document from source: %s'
) % self.source.label
if not isinstance(self.source, StagingFolderSource):
if not isinstance(self.source, StagingFolderSource) and not isinstance(self.source, SaneScanner) :
context['subtemplates_list'][0]['context'].update(
{
'form_action': self.request.get_full_path(),