2011-02-04 22:24:55 +01:00
/**
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* @ file main . cpp
* @ author The OpenPilot Team , http : //www.openpilot.org Copyright (C) 2010.
* Parts by Nokia Corporation ( qt - info @ nokia . com ) Copyright ( C ) 2009.
* @ brief
* @ see The GNU Public License ( GPL ) Version 3
* @ defgroup
* @ {
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* 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 3 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
*/
# include "qtsingleapplication.h"
2011-05-29 14:01:20 +02:00
# include "utils/xmlconfig.h"
2013-01-22 07:16:14 +01:00
# include "gcssplashscreen.h"
2011-02-04 22:24:55 +01:00
# include <extensionsystem/pluginmanager.h>
# include <extensionsystem/pluginspec.h>
# include <extensionsystem/iplugin.h>
# include <QtCore/QDir>
# include <QtCore/QTextStream>
# include <QtCore/QFileInfo>
# include <QtCore/QDebug>
# include <QtCore/QTimer>
# include <QtCore/QLibraryInfo>
# include <QtCore/QTranslator>
# include <QtCore/QSettings>
# include <QtCore/QVariant>
# include <QtGui/QMessageBox>
# include <QtGui/QApplication>
# include <QtGui/QMainWindow>
2013-01-15 23:22:44 +01:00
# include <QtGui/QSplashScreen>
2013-01-22 07:16:14 +01:00
# include <QtGui/QPainter>
2011-02-04 22:24:55 +01:00
2013-03-24 16:50:17 +01:00
# include <QElapsedTimer>
2011-02-04 22:24:55 +01:00
enum { OptionIndent = 4 , DescriptionIndent = 24 } ;
static const char * appNameC = " OpenPilot GCS " ;
static const char * corePluginNameC = " Core " ;
static const char * fixedOptionsC =
" [OPTION]... [FILE]... \n "
" Options: \n "
" -help Display this help \n "
" -version Display program version \n "
" -client Attempt to connect to already running instance \n "
2011-02-05 22:00:48 +01:00
" -clean-config Delete all existing configuration settings \n "
" -exit-after-config Exit GCS after manipulating configuration settings \n "
2012-08-21 15:41:53 +02:00
" -D key=value Override configuration settings e.g: -D General/OverrideLanguage=de \n "
2012-09-10 20:44:20 +02:00
" -configfile=value Default configuration file to load if settings file is empty \n " ;
2011-02-04 22:24:55 +01:00
static const char * HELP_OPTION1 = " -h " ;
static const char * HELP_OPTION2 = " -help " ;
static const char * HELP_OPTION3 = " /h " ;
static const char * HELP_OPTION4 = " --help " ;
static const char * VERSION_OPTION = " -version " ;
static const char * CLIENT_OPTION = " -client " ;
2011-02-05 22:00:48 +01:00
static const char * CONFIG_OPTION = " -D " ;
static const char * CLEAN_CONFIG_OPTION = " -clean-config " ;
static const char * EXIT_AFTER_CONFIG_OPTION = " -exit-after-config " ;
2011-02-04 22:24:55 +01:00
typedef QList < ExtensionSystem : : PluginSpec * > PluginSpecSet ;
2013-04-07 22:25:10 +02:00
static const char * DEFAULT_CONFIG_FILENAME = " OpenPilotGCS.xml " ;
2011-02-04 22:24:55 +01:00
// Helpers for displaying messages. Note that there is no console on Windows.
# ifdef Q_OS_WIN
// Format as <pre> HTML
static inline void toHtml ( QString & t )
{
t . replace ( QLatin1Char ( ' & ' ) , QLatin1String ( " & " ) ) ;
t . replace ( QLatin1Char ( ' < ' ) , QLatin1String ( " < " ) ) ;
t . replace ( QLatin1Char ( ' > ' ) , QLatin1String ( " > " ) ) ;
t . insert ( 0 , QLatin1String ( " <html><pre> " ) ) ;
t . append ( QLatin1String ( " </pre></html> " ) ) ;
}
static void displayHelpText ( QString t ) // No console on Windows.
{
toHtml ( t ) ;
QMessageBox : : information ( 0 , QLatin1String ( appNameC ) , t ) ;
}
static void displayError ( const QString & t ) // No console on Windows.
{
QMessageBox : : critical ( 0 , QLatin1String ( appNameC ) , t ) ;
}
# else
static void displayHelpText ( const QString & t )
{
qWarning ( " %s " , qPrintable ( t ) ) ;
}
static void displayError ( const QString & t )
{
qCritical ( " %s " , qPrintable ( t ) ) ;
}
# endif
static void printVersion ( const ExtensionSystem : : PluginSpec * coreplugin ,
const ExtensionSystem : : PluginManager & pm )
{
QString version ;
QTextStream str ( & version ) ;
str < < ' \n ' < < appNameC < < ' ' < < coreplugin - > version ( ) < < " based on Qt " < < qVersion ( ) < < " \n \n " ;
pm . formatPluginVersions ( str ) ;
str < < ' \n ' < < coreplugin - > copyright ( ) < < ' \n ' ;
displayHelpText ( version ) ;
}
static void printHelp ( const QString & a0 , const ExtensionSystem : : PluginManager & pm )
{
QString help ;
QTextStream str ( & help ) ;
str < < " Usage: " < < a0 < < fixedOptionsC ;
ExtensionSystem : : PluginManager : : formatOptions ( str , OptionIndent , DescriptionIndent ) ;
pm . formatPluginOptions ( str , OptionIndent , DescriptionIndent ) ;
displayHelpText ( help ) ;
}
static inline QString msgCoreLoadFailure ( const QString & why )
{
return QCoreApplication : : translate ( " Application " , " Failed to load core: %1 " ) . arg ( why ) ;
}
static inline QString msgSendArgumentFailed ( )
{
return QCoreApplication : : translate ( " Application " , " Unable to send command line arguments to the already running instance. It appears to be not responding. " ) ;
}
// Prepare a remote argument: If it is a relative file, add the current directory
// since the the central instance might be running in a different directory.
static inline QString prepareRemoteArgument ( const QString & a )
{
QFileInfo fi ( a ) ;
if ( ! fi . exists ( ) )
return a ;
if ( fi . isRelative ( ) )
return fi . absoluteFilePath ( ) ;
return a ;
}
// Send the arguments to an already running instance of OpenPilot GCS
static bool sendArguments ( SharedTools : : QtSingleApplication & app , const QStringList & arguments )
{
if ( ! arguments . empty ( ) ) {
// Send off arguments
const QStringList : : const_iterator acend = arguments . constEnd ( ) ;
for ( QStringList : : const_iterator it = arguments . constBegin ( ) ; it ! = acend ; + + it ) {
if ( ! app . sendMessage ( prepareRemoteArgument ( * it ) ) ) {
displayError ( msgSendArgumentFailed ( ) ) ;
return false ;
}
}
}
// Special empty argument means: Show and raise (the slot just needs to be triggered)
if ( ! app . sendMessage ( QString ( ) ) ) {
displayError ( msgSendArgumentFailed ( ) ) ;
return false ;
}
return true ;
}
static inline QStringList getPluginPaths ( )
{
QStringList rc ;
// Figure out root: Up one from 'bin'
QDir rootDir = QApplication : : applicationDirPath ( ) ;
rootDir . cdUp ( ) ;
const QString rootDirPath = rootDir . canonicalPath ( ) ;
// 1) "plugins" (Win/Linux)
QString pluginPath = rootDirPath ;
pluginPath + = QLatin1Char ( ' / ' ) ;
pluginPath + = QLatin1String ( GCS_LIBRARY_BASENAME ) ;
pluginPath + = QLatin1Char ( ' / ' ) ;
pluginPath + = QLatin1String ( " openpilotgcs " ) ;
pluginPath + = QLatin1Char ( ' / ' ) ;
pluginPath + = QLatin1String ( " plugins " ) ;
rc . push_back ( pluginPath ) ;
// 2) "PlugIns" (OS X)
pluginPath = rootDirPath ;
pluginPath + = QLatin1Char ( ' / ' ) ;
2011-06-01 20:58:49 +02:00
pluginPath + = QLatin1String ( " Plugins " ) ;
2011-02-04 22:24:55 +01:00
rc . push_back ( pluginPath ) ;
return rc ;
}
2013-04-07 22:25:10 +02:00
static void loadFactorySettings ( QSettings & settings )
{
QDir directory ( QCoreApplication : : applicationDirPath ( ) ) ;
2011-02-04 22:24:55 +01:00
# ifdef Q_OS_MAC
2013-04-07 22:25:10 +02:00
directory . cdUp ( ) ;
directory . cd ( " Resources " ) ;
2011-02-04 22:24:55 +01:00
# else
2013-04-07 22:25:10 +02:00
directory . cdUp ( ) ;
directory . cd ( " share " ) ;
directory . cd ( " openpilotgcs " ) ;
2011-02-04 22:24:55 +01:00
# endif
2013-04-07 22:25:10 +02:00
directory . cd ( " default_configurations " ) ;
2011-02-04 22:24:55 +01:00
2013-04-07 22:25:10 +02:00
qDebug ( ) < < " Looking for configuration files in: " < < directory . absolutePath ( ) ;
2011-02-04 22:24:55 +01:00
2013-04-07 22:25:10 +02:00
QString filename ;
// check if command line contains a config file name
QString commandLine ;
foreach ( QString str , qApp - > arguments ( ) ) {
if ( str . contains ( " configfile " ) ) {
commandLine = str . split ( " = " ) . at ( 1 ) ;
}
}
if ( ! commandLine . isEmpty ( ) & & QFile : : exists ( directory . absolutePath ( ) + QDir : : separator ( ) + commandLine ) ) {
// use file name specified on command line
filename = directory . absolutePath ( ) + QDir : : separator ( ) + commandLine ;
qDebug ( ) < < " Configuration file " < < filename < < " specified on command line will be loaded. " ;
} else if ( QFile : : exists ( directory . absolutePath ( ) + QDir : : separator ( ) + DEFAULT_CONFIG_FILENAME ) ) {
// use default file name
filename = directory . absolutePath ( ) + QDir : : separator ( ) + DEFAULT_CONFIG_FILENAME ;
qDebug ( ) < < " Default configuration file " < < filename < < " will be loaded. " ;
} else {
// TODO should we exit violently?
qWarning ( ) < < " No default configuration file found! " ;
return ;
}
// create settings from file
QSettings * qs = new QSettings ( filename , XmlConfig : : XmlSettingsFormat ) ;
// transfer loaded settings to application settings
QStringList keys = qs - > allKeys ( ) ;
foreach ( QString key , keys ) {
settings . setValue ( key , qs - > value ( key ) ) ;
}
// and delete loaded settings
delete qs ;
qDebug ( ) < < " Configuration file " < < filename < < " was loaded. " ;
}
static void overrideSettings ( QSettings & settings , int argc , char * * argv )
{
2011-02-04 22:24:55 +01:00
// Options like -DMy/setting=test
QRegExp rx ( " ([^=]+) = ( . * ) " ) ;
2013-04-07 22:25:10 +02:00
QMap < QString , QString > settingOptions ;
for ( int i = 0 ; i < argc ; + + i ) {
if ( QString ( CONFIG_OPTION ) . compare ( QString ( argv [ i ] ) ) = = 0 ) {
if ( rx . indexIn ( argv [ + + i ] ) > - 1 ) {
2011-02-04 22:24:55 +01:00
settingOptions . insert ( rx . cap ( 1 ) , rx . cap ( 2 ) ) ;
}
}
2013-04-07 22:25:10 +02:00
if ( QString ( CLEAN_CONFIG_OPTION ) . compare ( QString ( argv [ i ] ) ) = = 0 ) {
2011-02-05 22:00:48 +01:00
settings . clear ( ) ;
}
2011-02-04 22:24:55 +01:00
}
2011-02-05 22:00:48 +01:00
2011-02-04 22:24:55 +01:00
QList < QString > keys = settingOptions . keys ( ) ;
2013-04-07 22:25:10 +02:00
foreach ( QString key , keys ) {
2011-02-04 22:24:55 +01:00
settings . setValue ( key , settingOptions . value ( key ) ) ;
}
settings . sync ( ) ;
}
2013-04-07 22:25:10 +02:00
# ifdef Q_OS_MAC
# define SHARE_PATH " / .. / Resources"
# else
# define SHARE_PATH " / .. / share / openpilotgcs"
# endif
2011-02-04 22:24:55 +01:00
int main ( int argc , char * * argv )
{
2013-03-24 16:50:17 +01:00
QElapsedTimer timer ;
timer . start ( ) ;
2011-02-04 22:24:55 +01:00
# ifdef Q_OS_MAC
// increase the number of file that can be opened in OpenPilot GCS
struct rlimit rl ;
getrlimit ( RLIMIT_NOFILE , & rl ) ;
rl . rlim_cur = rl . rlim_max ;
setrlimit ( RLIMIT_NOFILE , & rl ) ;
# endif
2012-08-02 13:32:43 +02:00
# ifdef Q_OS_LINUX
2012-07-29 14:15:24 +02:00
QApplication : : setAttribute ( Qt : : AA_X11InitThreads , true ) ;
2012-08-02 13:32:43 +02:00
# endif
2012-11-24 14:55:46 +01:00
2013-04-07 22:25:10 +02:00
// Set the default locale to EN, if this is not set the system locale will be used
// and as of now we dont want that behaviour.
2012-11-24 14:55:46 +01:00
QLocale : : setDefault ( QLocale : : English ) ;
2011-02-04 22:24:55 +01:00
SharedTools : : QtSingleApplication app ( ( QLatin1String ( appNameC ) ) , argc , argv ) ;
2013-04-07 22:25:10 +02:00
// Open splash screen
2013-01-22 07:16:14 +01:00
GCSSplashScreen splash ;
2013-01-15 23:22:44 +01:00
splash . show ( ) ;
2011-02-04 22:24:55 +01:00
// Must be done before any QSettings class is created
2011-05-29 14:01:20 +02:00
QSettings : : setPath ( XmlConfig : : XmlSettingsFormat , QSettings : : SystemScope ,
2013-04-07 22:25:10 +02:00
QCoreApplication : : applicationDirPath ( ) + QLatin1String ( SHARE_PATH ) ) ;
2011-02-04 22:24:55 +01:00
// keep this in sync with the MainWindow ctor in coreplugin/mainwindow.cpp
2013-04-07 22:25:10 +02:00
QSettings settings ( XmlConfig : : XmlSettingsFormat , QSettings : : UserScope , QLatin1String ( " OpenPilot " ) ,
QLatin1String ( " OpenPilotGCS_config " ) ) ;
if ( ! settings . allKeys ( ) . count ( ) ) {
qDebug ( ) < < " No user setting, loading factory defaults. " ;
// no user settings, so try to load some default ones
loadFactorySettings ( settings ) ;
}
2011-02-04 22:24:55 +01:00
2013-04-07 22:25:10 +02:00
// override setting with command line provided settings
2011-02-04 22:24:55 +01:00
overrideSettings ( settings , argc , argv ) ;
2013-04-07 22:25:10 +02:00
QString locale = QLocale : : system ( ) . name ( ) ;
qDebug ( ) < < " main - system locale: " < < locale ;
QString language = QLocale : : system ( ) . name ( ) ;
language = settings . value ( " General/OverrideLanguage " , language ) . toString ( ) ;
qDebug ( ) < < " main - language: " < < language ;
2011-02-04 22:24:55 +01:00
2013-04-07 22:25:10 +02:00
QTranslator translator ;
const QString & creatorTrPath = QCoreApplication : : applicationDirPath ( ) + QLatin1String ( SHARE_PATH " /translations " ) ;
if ( translator . load ( QLatin1String ( " openpilotgcs_ " ) + language , creatorTrPath ) ) {
2011-02-04 22:24:55 +01:00
const QString & qtTrPath = QLibraryInfo : : location ( QLibraryInfo : : TranslationsPath ) ;
2013-04-07 22:25:10 +02:00
const QString & qtTrFile = QLatin1String ( " qt_ " ) + language ;
2011-02-04 22:24:55 +01:00
// Binary installer puts Qt tr files into creatorTrPath
2013-04-07 22:25:10 +02:00
QTranslator qtTranslator ;
2011-02-04 22:24:55 +01:00
if ( qtTranslator . load ( qtTrFile , qtTrPath ) | | qtTranslator . load ( qtTrFile , creatorTrPath ) ) {
QCoreApplication : : installTranslator ( & translator ) ;
QCoreApplication : : installTranslator ( & qtTranslator ) ;
} else {
2013-04-07 22:25:10 +02:00
// unload()
translator . load ( QString ( ) ) ;
2011-02-04 22:24:55 +01:00
}
}
app . setProperty ( " qtc_locale " , locale ) ; // Do we need this?
2013-01-22 12:42:07 +01:00
splash . showProgressMessage ( QObject : : tr ( " Application starting... " ) ) ;
2011-02-04 22:24:55 +01:00
// Load
ExtensionSystem : : PluginManager pluginManager ;
pluginManager . setFileExtension ( QLatin1String ( " pluginspec " ) ) ;
const QStringList pluginPaths = getPluginPaths ( ) ;
pluginManager . setPluginPaths ( pluginPaths ) ;
const QStringList arguments = app . arguments ( ) ;
QMap < QString , QString > foundAppOptions ;
if ( arguments . size ( ) > 1 ) {
QMap < QString , bool > appOptions ;
appOptions . insert ( QLatin1String ( HELP_OPTION1 ) , false ) ;
appOptions . insert ( QLatin1String ( HELP_OPTION2 ) , false ) ;
appOptions . insert ( QLatin1String ( HELP_OPTION3 ) , false ) ;
appOptions . insert ( QLatin1String ( HELP_OPTION4 ) , false ) ;
appOptions . insert ( QLatin1String ( VERSION_OPTION ) , false ) ;
appOptions . insert ( QLatin1String ( CLIENT_OPTION ) , false ) ;
2011-02-05 22:00:48 +01:00
appOptions . insert ( QLatin1String ( CONFIG_OPTION ) , true ) ;
appOptions . insert ( QLatin1String ( CLEAN_CONFIG_OPTION ) , false ) ;
appOptions . insert ( QLatin1String ( EXIT_AFTER_CONFIG_OPTION ) , false ) ;
2011-02-04 22:24:55 +01:00
QString errorMessage ;
2013-04-07 22:25:10 +02:00
if ( ! pluginManager . parseOptions ( arguments , appOptions , & foundAppOptions , & errorMessage ) ) {
2011-02-04 22:24:55 +01:00
displayError ( errorMessage ) ;
printHelp ( QFileInfo ( app . applicationFilePath ( ) ) . baseName ( ) , pluginManager ) ;
return - 1 ;
}
}
const PluginSpecSet plugins = pluginManager . plugins ( ) ;
ExtensionSystem : : PluginSpec * coreplugin = 0 ;
foreach ( ExtensionSystem : : PluginSpec * spec , plugins ) {
if ( spec - > name ( ) = = QLatin1String ( corePluginNameC ) ) {
coreplugin = spec ;
break ;
}
}
2013-04-07 22:25:10 +02:00
if ( ! coreplugin ) {
2011-02-04 22:24:55 +01:00
QString nativePaths = QDir : : toNativeSeparators ( pluginPaths . join ( QLatin1String ( " , " ) ) ) ;
2013-04-07 22:25:10 +02:00
const QString reason = QCoreApplication : : translate ( " Application " , " Could not find 'Core.pluginspec' in %1 " ) . arg (
nativePaths ) ;
2011-02-04 22:24:55 +01:00
displayError ( msgCoreLoadFailure ( reason ) ) ;
return 1 ;
}
if ( coreplugin - > hasError ( ) ) {
displayError ( msgCoreLoadFailure ( coreplugin - > errorString ( ) ) ) ;
return 1 ;
}
if ( foundAppOptions . contains ( QLatin1String ( VERSION_OPTION ) ) ) {
printVersion ( coreplugin , pluginManager ) ;
return 0 ;
}
2011-02-05 22:00:48 +01:00
if ( foundAppOptions . contains ( QLatin1String ( EXIT_AFTER_CONFIG_OPTION ) ) ) {
return 0 ;
}
2011-02-04 22:24:55 +01:00
if ( foundAppOptions . contains ( QLatin1String ( HELP_OPTION1 ) )
| | foundAppOptions . contains ( QLatin1String ( HELP_OPTION2 ) )
| | foundAppOptions . contains ( QLatin1String ( HELP_OPTION3 ) )
| | foundAppOptions . contains ( QLatin1String ( HELP_OPTION4 ) ) ) {
printHelp ( QFileInfo ( app . applicationFilePath ( ) ) . baseName ( ) , pluginManager ) ;
return 0 ;
}
const bool isFirstInstance = ! app . isRunning ( ) ;
2013-04-07 22:25:10 +02:00
if ( ! isFirstInstance & & foundAppOptions . contains ( QLatin1String ( CLIENT_OPTION ) ) ) {
2011-02-04 22:24:55 +01:00
return sendArguments ( app , pluginManager . arguments ( ) ) ? 0 : - 1 ;
2013-04-07 22:25:10 +02:00
}
2011-02-04 22:24:55 +01:00
2013-01-22 07:16:14 +01:00
QObject : : connect ( & pluginManager , SIGNAL ( pluginAboutToBeLoaded ( ExtensionSystem : : PluginSpec * ) ) ,
& splash , SLOT ( showPluginLoadingProgress ( ExtensionSystem : : PluginSpec * ) ) ) ;
2013-01-22 12:42:07 +01:00
2011-02-04 22:24:55 +01:00
pluginManager . loadPlugins ( ) ;
2013-01-22 12:42:07 +01:00
2011-02-04 22:24:55 +01:00
if ( coreplugin - > hasError ( ) ) {
displayError ( msgCoreLoadFailure ( coreplugin - > errorString ( ) ) ) ;
return 1 ;
}
2013-01-22 12:42:07 +01:00
2011-02-04 22:24:55 +01:00
{
QStringList errors ;
foreach ( ExtensionSystem : : PluginSpec * p , pluginManager . plugins ( ) )
if ( p - > hasError ( ) )
errors . append ( p - > errorString ( ) ) ;
if ( ! errors . isEmpty ( ) )
QMessageBox : : warning ( 0 ,
QCoreApplication : : translate ( " Application " , " OpenPilot GCS - Plugin loader messages " ) ,
errors . join ( QString : : fromLatin1 ( " \n \n " ) ) ) ;
}
if ( isFirstInstance ) {
// Set up lock and remote arguments for the first instance only.
// Silently fallback to unconnected instances for any subsequent
// instances.
app . initialize ( ) ;
QObject : : connect ( & app , SIGNAL ( messageReceived ( QString ) ) , coreplugin - > plugin ( ) , SLOT ( remoteArgument ( QString ) ) ) ;
}
QObject : : connect ( & app , SIGNAL ( fileOpenRequest ( QString ) ) , coreplugin - > plugin ( ) , SLOT ( remoteArgument ( QString ) ) ) ;
// Do this after the event loop has started
QTimer : : singleShot ( 100 , & pluginManager , SLOT ( startTests ( ) ) ) ;
2013-01-15 23:22:44 +01:00
2013-01-23 07:35:40 +01:00
//Update message and postpone closing of splashscreen 3 seconds
2013-01-22 12:42:07 +01:00
splash . showProgressMessage ( QObject : : tr ( " Application started. " ) ) ;
2013-01-24 15:54:25 +01:00
QTimer : : singleShot ( 1500 , & splash , SLOT ( close ( ) ) ) ;
2013-01-22 12:42:07 +01:00
2013-04-07 22:25:10 +02:00
qDebug ( ) < < " main - main took " < < timer . elapsed ( ) < < " ms " ;
2013-03-24 16:50:17 +01:00
int ret = app . exec ( ) ;
2013-04-07 22:25:10 +02:00
qDebug ( ) < < " main - GCS ran for " < < timer . elapsed ( ) < < " ms " ;
2013-03-24 16:50:17 +01:00
return ret ;
2011-02-04 22:24:55 +01:00
}