diff --git a/product/ERP5/Converter/Converter.py b/product/ERP5/Converter/Converter.py new file mode 100644 index 0000000000000000000000000000000000000000..74f32576a12bd9fa632cc28b9322994f2ed6e423 --- /dev/null +++ b/product/ERP5/Converter/Converter.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +class Converter: + """ + Converter classes implement document conversion + from a given format to another format. + + ARCHITECTURE: because most document processing + software, and potentially libraries, are unstable, + do not always support multithreading and may lead + to memory leaks, the recommend approach to create + a Converter is to simply execute a command with + popenX in a separate process and return the result. + """ + + # Introspection API Implementation + def getSourceFormatItemList(self): + """ + Return the list of supported input format + (format, name) + """ + raise NotImplementedError + + def getDestinationFormatItemList(self): + """ + Return the list of supported output format + (format, name) + """ + raise NotImplementedError + + # Conversion API Implementation + def convertFile(self, file, source_format, destination_format): + """ + """ + raise NotImplementedError + + def getFileMetadataItemList(self, file, source_format): + """ + """ + raise NotImplementedError + + def updateFileMetadata(self, file, source_format, **kw): + """ + """ + raise NotImplementedError diff --git a/product/ERP5/Converter/OpenOffice.py b/product/ERP5/Converter/OpenOffice.py new file mode 100644 index 0000000000000000000000000000000000000000..716469eaca91bbad5513bad1f033bd70632d55b3 --- /dev/null +++ b/product/ERP5/Converter/OpenOffice.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +MAX_LAUNCH = 100 + +class OpenOffice: + + # Private methods + def _getAvailableOpenOfficeInstancePort(self): + """ + This method starts a collection of + headless OpenOffice in bacground and attaches + them to the server process. Each time + a headless Openoffice is returned, a counter + is incremented. After MAX_LAUNCH times, + the server is closed and recreated. + + The method returns a port number + """ + + def _getCommand(self, param): + """ + """ + return "/usr/bin/openoffice.convert %s" + + # Introspection API Implementation + def getSourceFormatItemList(self): + """ + Return the list of supported input format + (format, name) + """ + port = self._getAvailableOpenOfficeInstancePort() + + def getDestinationFormatItemList(self): + """ + Return the list of supported output format + (format, name) + """ + port = self._getAvailableOpenOfficeInstancePort() + + # Conversion API Implementation + def convertFile(self, file, source_format, destination_format): + """ + """ + # XXX - just call a command line (python script) + # which does all the work + port = self._getAvailableOpenOfficeInstancePort() + input, output = popen(self._getCommand('--convert'), file) + + def getFileMetadataItemList(self, file, source_format): + """ + """ + # XXX - just call a command line (python script) + # which does all the work + port = self._getAvailableOpenOfficeInstancePort() + input, output = popen(self._getCommand('--metadata'), file) + + def updateFileMetadata(self, file, source_format, **kw): + """ + """ + # XXX - just call a command line (python script) + # which does all the work + port = self._getAvailableOpenOfficeInstancePort() + input, output = popen(self._getCommand('--update'), file) diff --git a/product/ERP5/Converter/bin/openoffice.convert b/product/ERP5/Converter/bin/openoffice.convert new file mode 100644 index 0000000000000000000000000000000000000000..f77269d8aa0c4cfdafd2af1f0e17b67b7e2f1488 --- /dev/null +++ b/product/ERP5/Converter/bin/openoffice.convert @@ -0,0 +1,9 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import pyuno + +# Invoke OpenOffice with appropriate port number provided by OpenOffice converter + + +# Return result diff --git a/product/ERP5/Tool/ConversionTool.py b/product/ERP5/Tool/ConversionTool.py new file mode 100644 index 0000000000000000000000000000000000000000..94ddc13c4787217b01c634289476c61588d770a5 --- /dev/null +++ b/product/ERP5/Tool/ConversionTool.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes <jp@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# 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. +# +############################################################################## + +from Products.CMFCore.utils import getToolByName + +from AccessControl import ClassSecurityInfo +from Globals import InitializeClass, DTMLFile +from Products.CMFCore.utils import getToolByName +from Products.ERP5Type import Permissions +from Products.ERP5Type.Tool.BaseTool import BaseTool + +from Products.ERP5 import _dtmldir + +from zLOG import LOG + +class ConversionTool(BaseTool): + """ + The ConversionTool class will provide in the future an + API to unify file conversion and metadata handling in ERP5. + + The first version consists of a tool which acts both as central + point for conversion services and metadata handling services + for all ERP5 Document classes and scripts, as well as a Web + Service for external applications. + + The Tool calls itself, through XML-RPC protocol. A dedicated + Zope instance can be setup for handling conversions. + + In the future, the tool will be splitted in 2 parts: + - a Tool + - a WSGI service + + The tool reuses the portal_web_services to connect through + XML-RPC to the conversion server. + + ARCHITECTURE PHASE 1: all Converter classes are stored + in the Converter directory part of ERP5 Product. The tool + serves both as caller and recipient, and calls itself + through XML-RPC. + + ARCHITECTURE PHASE 2: all Converter classes are moved to + a dedicated directory in /usr/share/oood/converter + (or new name). The Web Service API is moved away from + ConverterTool class and turned to a WSGI independent service + + NOTE: this class is experimental and is subject to be removed + NOTE2: the code is only pseudo-code + """ + id = 'portal_conversions' + meta_type = 'ERP5 Conversion Tool' + portal_type = 'Conversion Tool' + allowed_types = () + + # Declarative Security + security = ClassSecurityInfo() + + # + # ZMI methods + # + security.declareProtected( Permissions.ManagePortal, 'manage_overview' ) + manage_overview = DTMLFile( 'explainConversionTool', _dtmldir ) + + def filtered_meta_types(self, user=None): + # Filters the list of available meta types. + all = SolverTool.inheritedAttribute('filtered_meta_types')(self) + meta_types = [] + for meta_type in self.all_meta_types(): + if meta_type['name'] in self.allowed_types: + meta_types.append(meta_type) + return meta_types + + def tpValues(self): + """ show the content in the left pane of the ZMI """ + return self.objectValues() + + # Internal API - called by Document classes and scripts + def convert(self, file, source_format, destination_format, zip=False): + """ + Returns the converted file in the given format + + zip parameter can be specified to return the result of conversion + in the form of a zip archive (which may contain multiple parts). + This can be useful to convert a single ODF file to HMTL + and png images. + """ + # Just call XML-RPC + preference_tool = getToolByName(self, 'portal_preferences') + web_service_tool = getToolByName(self, 'portal_web_services') + conversion_url = preference_tool.getPreferredConversionServiceUrl() + conversion_service = web_service_tool.connect(conversion_url) + # XXX - no exception handling - wrong + return conversion_service.convertFile(file, source_format, destination_format) + + + def getMetadataDict(self, file, source_format): + """ + Returns a dict of metadata values for the + document. The structure of this dict is "unpredictable" + and follows the convention of each file. + """ + # Just call XML-RPC + preference_tool = getToolByName(self, 'portal_preferences') + web_service_tool = getToolByName(self, 'portal_web_services') + conversion_url = preference_tool.getPreferredConversionServiceUrl() + conversion_service = web_service_tool.connect(conversion_url) + # XXX - no exception handling - wrong + return conversion_service.getFileMetadataItemList(file, source_format) + + + def updateMetadata(self, file, source_format, **kw): + """ + Updates the file in the given source_format + with provided metadata and return the resulting new file + """ + # Just call XML-RPC + preference_tool = getToolByName(self, 'portal_preferences') + web_service_tool = getToolByName(self, 'portal_web_services') + conversion_url = preference_tool.getPreferredConversionServiceUrl() + conversion_service = web_service_tool.connect(conversion_url) + # XXX - no exception handling - wrong + return conversion_service.updateFileMetadata(file, source_format, **kw) + + + # Web Service API - called by any application through XML-RPC + # Will be removed in the future and moved to WSGI service + def convertFile(self, file, source_format, destination_format, zip=False): + """ + Returns the converted file in the given format + """ + converter = self._findConverter(source_format, destination_format) + return converter.convertFile(file, source_format, destination_format, zip=zip) + + + def getFileMetadataItemList(self, file, source_format): + """ + Returns a list key, value pairs representing the + metadata values for the document. The structure of this + list is "unpredictable" and follows the convention of each file. + """ + converter = self._findConverter(source_format, destination_format) + return converter.getFileMetadataItemList(file, source_format) + + + def updateFileMetadata(self, file, source_format, **kw): + """ + Updates the file in the given source_format + with provided metadata and return the resulting new file + """ + converter = self._findConverter(source_format, destination_format) + return converter.updateFileMetadata(file, source_format, destination_format, zip=zip) + + # Private methods + def _findConverter(self, source_format, destination_format): + """ + Browses all converter classes, initialised the repository of + converters and finds the appropriate class + """ \ No newline at end of file