Une interface de scan pour Feng Office
Dans mon article précédent, j’avais présenté les fonctions principales de Feng Office. J’ai depuis uploadé mes documents pour les gérer dans l’application. Mais j’ai voulu faire une petite application qui me faciliterait la vie : une application qui permettrait de scanner des documents et de les envoyer directement dans Feng Office, sans avoir à d’abord passer par une interface de scan, puis d’envoyer ensuite les documents sur Feng Office. Il a donc d’abord fallu trouver une librairie d’interfaçage avec les scanners, puis réussir à comprendre les web services proposés par Feng Office (ils ne sont pas documentés), et enfin, coder l’application.
La librairie d’interfaçage avec les scanners
Il y a deux manières principales de s’interfacer avec un scanner depuis une application : Sane et Twain. La première est beaucoup utilisé sous GNU/Linux et la seconde plus sous Windows©.
Le soucis, c’est qu’il y a peu de librairies Java open source qui permettent d’utiliser ces interfaces. En fait, il n’y en a pas (à ma connaissance) qui bénéficie d’un développement actif. J’ai donc choisi d’utiliser une librairie dont le développement n’est plus assuré, avec le risque qu’un jour, cette librairie soit incompatible avec de nouvelles spécifications. Je ne connais pas le nom exact de la librairie, elle a été développée par une boite qui s’appelait mms computing, qui n’a plus l’air d’exister, leur site n’est qu’une page de login. J’ai pu trouver les jar de la librairie ici.
La librairie s’utilise assez facilement. Il faut d’abord déclarer une classe qui implémente ScannerListener. L’interface ne possède qu’une méthode à redéfinir, dont la signature est : public void update(ScannerIOMetadata.Type type, ScannerIOMetadata metadata).
Voici donc un exemple de listener :
public class MyScannerListener implements ScannerListener { public void update(ScannerIOMetadata.Type type, ScannerIOMetadata metadata) { if (type.equals(ScannerIOMetadata.ACQUIRED)) { BufferedImage img = metadata.getImage(); File scannedFile = new File(System.getProperty("java.io.tmpdir") + "/" + new Date().getTime() + ".png"); System.out.println("Écriture de " + scannedFile.getAbsolutePath()); try { ImageIO.write(img, "png", scannedFile); } catch (IOException e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "Erreur lors de l'écriture de " + scannedFile.getAbsolutePath() + ".\\n Consulter la stack trace", "Erreur", JOptionPane.ERROR_MESSAGE); } } else if (type.equals(ScannerIOMetadata.STATECHANGE)) { System.out.println("State " + metadata.getStateStr()); } else if (type.equals(ScannerIOMetadata.INFO)) { System.out.println(metadata.getInfo()); } else if (type.equals(ScannerIOMetadata.EXCEPTION)) { System.out.println(metadata.getException()); } } }
Cette classe ne fait qu’une chose : quand un scanner a envoyé une image, elle la sauvegarde sous forme de fichier .png dans le dossier temporaire. J’ai fait une classe un peu plus complète qui permet de sélectionner un appareil, puis de lancer le scan d’une image : MyScanner.java
Les web services de Feng Office
Feng Office ne propose des web services que pour la gestion des documents, des workspaces et des tags. Ça tombe bien, c’est juste ce dont j’ai besoin pour l’appli. Il n’existe pas de documentation pour ces web services, donc il a fallu regarder dans le code pour savoir comment générer le WSDL (Feng Office utilise SOAP).
On peut ensuite générer les classes java à partir des WSDL. Par exemple, pour générer les classes pour le web service des workspaces :
wsimport -d test/ -keep http:/url-vers-fengoffice/public/webservices/WorkspaceServices.php?wsdl
Et là c’est le drame, on fait face à une première erreur, puis si on corrige, à une autre erreur. Ces erreurs sont dues, entre autres, au fait que JAXB ne gère plus les WSDL « rpc/encoded ». Il faut donc faire quelques modifications du côté serveur. Il faut modifier le fichier Disco.php qui se trouve dans library/PEAR/SOAP.
Voici le fichier modifié : Disco.php.
Une fois les modifications apportées, les classes java sont générées. (les différentes URL pour la génération sont http://url-vers-fengoffice/public/webservices/WorkspaceServices.php?wsdl, http://url-vers-fengoffice/public/webservices/FilesServices.php?wsdl et http://url-vers-fengoffice/public/webservices/TagsServices.php?wsdl).
L’utilisation des classes générées est simple. Par exemple, pour afficher la liste des tags, on peut faire ça :
FengWebServicesTagsService service = new FengWebServicesTagsService(); FengWebServicesTagsPort tags = service.getFengWebServicesTagsPort(); System.out.println(tags.listTags("utilisateur", "motdepasse"));
L’application
L’application que j’ai faite est assez petite (1000 lignes de code et 1.4 Mo, avec les librairies). Elle permet :
- d’afficher une fenêtre qui propose de choisir des tags, des workspaces, une description et un titre pour le document
- de choisir de convertir le document en pdf à la volée (png par défaut)
- de lancer le scan et d’envoyer le document quand le scan est réussi
Voici quelques images :
Entrée des données
Sélection de l’appareil
Interface propre à Sane
Je n’ai pas testé avec Twain, mais si la librairie est bien faite, ça devrait fonctionner. A noter que pour que ça fonctionne avec Sane, j’ai dû installer le paquet :
libsane-dev
L’exécutable est téléchargeable ici.
Petit plus : ajouter une applet de scan directement dans l’application
Il est possible d’ajouter une applet qui permettra de scanner des documents directement depuis l’application. Il suffit d’ajouter une applet dans le projet. Voilà ma classe d’applet :
public class ScanApplet extends Applet { @Override public void init() { Main.main(null); } }
Elle fait juste appel au main, c’est un peu laid mais c’est le plus simple. Il faut ensuite un peu trifouiller le code de Feng Office. Tout d’abord en éditant le fichier public/assets/javascript/og/FileManager.js, pour ajouter :
scan: new Ext.Action({ text: lang('scan'), tooltip: lang('scan new object'), iconCls: 'ico-scan-obj', disabled: false, handler: function() { var url = og.getUrl('files', 'scan_document'); og.openLink(url); }, scope: this }),
aux alentours de la ligne 522. Puis ajouter :
tbar.push(actions.scan);
aux alentours de la ligne 565.
On édite ensuite le fichier language/fr_fr/lang.js Pour y ajouter :
'scan':'Scanner', 'scan new object':'Scanner un nouveau document', 'new scan':'Scan de document',
Puis on édite le fichier public/assets/themes/default/stylesheets/og/og.css pour y ajouter :
.ico-scan-obj { background: transparent url(../../images/16x16/scanner.png) no-repeat scroll 0 !important; }
Il reste à trouver l’icône pour le menu (j’ai utilisé celle-ci : http://www.iconfinder.com/icondetails/1506/128/scanner_icon), et la placer dans le bon dossier (public/assets/themes/default/images/16×16).
Ensuite, on peut éditer le fichier application/controllers/FilesController.class.php pour y ajouter :
function scan_document() { $this->setTemplate('scan_document'); }
Et enfin, on crée un fichier « scan_document.php » dans application/views/files/ qui contient ceci :
<?php set_page_title(lang('new scan')); ?> <applet code = 'fr.mael.jfengservice.ScanApplet.class' codebase = 'http://url-vers-fengoffice/application/views/files/', archive = 'scan_applet.jar,commons-codec-1.4.jar,itext-2.1.7.jar,uk.co.mmscomputing.device.sane.jar,uk.co.mmscomputing.device.twain.jar', width = 1, height = 1 /> <?php tpl_display(get_template_path('form_errors')) ?>
Il faut que les jars soient signés. Je les ai signés et mis dans un zip : ici. Il n’y a plus qu’à les placer dans le même dossier que le fichier scan_document.php. (ou alors à recompiler soi même l’applet, et à la signer, si on ne me fait pas confiance, ce que je comprendrais).
Maintenant, quand on se rend dans l’onglet de gestion des documents, on a un nouveau menu :
Si l’on clique dessus, on est redirigé vers une page qui lance l’applet.
J’ai constaté un problème avec l’utilisation de l’applet : quand on la lance, on dirait que la session de Feng Office est détruite, puisqu’une pop-up indique qu’une inactivité a été détectée. J’ignore d’où ça vient.
La totalité du projet est téléchargeable ici.