733 lines
23 KiB
C++
733 lines
23 KiB
C++
/***************************************************************************
|
|
kmessshared.cpp - description
|
|
-------------------
|
|
begin : Thu May 8 2008
|
|
copyright : (C) 2008 by Valerio Pilo
|
|
email : valerio@kmess.org
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "kmessshared.h"
|
|
|
|
#include "../kmess.h"
|
|
#include "../kmessapplication.h"
|
|
#include "../kmessdebug.h"
|
|
#include "../currentaccount.h"
|
|
#include "../network/soap/passportloginservice.h"
|
|
#include "kmessconfig.h"
|
|
|
|
#include <ctime>
|
|
|
|
#include <KRun>
|
|
#include <KUrl>
|
|
#include <KShell>
|
|
#include <KToolInvocation>
|
|
|
|
#include <QCryptographicHash>
|
|
#include <QFile>
|
|
#include <QUuid>
|
|
#include <QPainter>
|
|
#include <QList>
|
|
#include <QString>
|
|
|
|
#ifdef KMESSDEBUG_SHAREDMETHODS
|
|
#define KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
#endif
|
|
|
|
|
|
|
|
// Create a HMACSha1 hash
|
|
QByteArray KMessShared::createHMACSha1 ( const QByteArray &keyForHash , const QByteArray &secret )
|
|
{
|
|
// The algorithm is definited into RFC 2104
|
|
int blocksize = 64;
|
|
QByteArray key( keyForHash );
|
|
QByteArray opad( blocksize, 0x5c );
|
|
QByteArray ipad( blocksize, 0x36 );
|
|
|
|
// If key size is too larg, compute the hash
|
|
if( key.size() > blocksize )
|
|
{
|
|
key = QCryptographicHash::hash( key, QCryptographicHash::Sha1 );
|
|
}
|
|
|
|
// If too small, pad with 0x00
|
|
if( key.size() < blocksize )
|
|
{
|
|
key += QByteArray( blocksize - key.size() , 0x00 );
|
|
}
|
|
|
|
// Compute the XOR operations
|
|
for(int i=0; i < key.size() - 1; i++ )
|
|
{
|
|
ipad[i] = (char) ( ipad[i] ^ key[i] );
|
|
opad[i] = (char) ( opad[i] ^ key[i] );
|
|
}
|
|
|
|
// Append the data to ipad
|
|
ipad += secret;
|
|
|
|
// Compute result: hash sha1 of ipad and append the data to opad
|
|
opad += QCryptographicHash::hash( ipad, QCryptographicHash::Sha1 );;
|
|
|
|
// Return array contains the result of HMACSha1
|
|
return QCryptographicHash::hash( opad, QCryptographicHash::Sha1 );
|
|
}
|
|
|
|
|
|
|
|
// Create the html file to request the given service
|
|
QString KMessShared::createRequestFile( const QString &mailto, const QString &folder )
|
|
{
|
|
CurrentAccount *currentAccount = CurrentAccount::instance();
|
|
|
|
// Use one file for hml page with post method
|
|
QString filename( KMessConfig::instance()->getAccountDirectory( currentAccount-> getHandle() ) +
|
|
"/hotmail.htm" );
|
|
|
|
QFile file( filename );
|
|
if ( ! file.open( QIODevice::WriteOnly | QIODevice::Text ) )
|
|
{
|
|
return QString();
|
|
}
|
|
|
|
QString command( folder );
|
|
|
|
// Check if the user wants to send an email
|
|
if( ! mailto.isEmpty() )
|
|
{
|
|
command += "?mailto=1&to=" + mailto;
|
|
}
|
|
|
|
// Use the method into passport to compute the token
|
|
QString token( PassportLoginService::createHotmailToken( currentAccount->getToken( "Passport" ),
|
|
currentAccount->getToken( "PassportProof" ),
|
|
command ) );
|
|
QTextStream outBuffer( &file );
|
|
|
|
// Write the file contents
|
|
outBuffer << "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n"
|
|
" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
|
|
"<html>\n"
|
|
" <head></head>\n"
|
|
" <body onload=\"document.pform.submit(); \">\n"
|
|
" <form name=\"pform\""
|
|
"action=\"https://login.live.com/ppsecure/sha1auth.srf?lc=" +
|
|
currentAccount->getLanguageCode() +
|
|
"\" method=\"POST\">\n"
|
|
" <input type=\"hidden\" name=\"token\" value=\"" +
|
|
token +
|
|
"\">\n"
|
|
" <noscript><p>Please enable JavaScript!</p>\n"
|
|
" <input type=\"submit\" value=\"Click here to open Live Mail\"/>\n"
|
|
" </noscript>\n"
|
|
" </form>\n"
|
|
" </body>\n"
|
|
"</html>\n";
|
|
|
|
// Then close the buffer
|
|
file.close();
|
|
|
|
return filename;
|
|
}
|
|
|
|
|
|
|
|
// Return a derived key with HMACSha1 algorithm
|
|
QByteArray KMessShared::deriveKey ( const QByteArray &keyToDerive, const QByteArray &magic )
|
|
{
|
|
// create the four hashes needed for the implementation.
|
|
QByteArray hash1, hash2, hash3, hash4, temp;
|
|
QByteArray key(keyToDerive);
|
|
|
|
// append the magic string.
|
|
temp.append ( magic );
|
|
|
|
// create the HMAC Sha1 hash and assign it.
|
|
hash1 = createHMACSha1 ( key, temp );
|
|
temp.clear();
|
|
|
|
// append the first hash with the magic string.
|
|
temp.append ( hash1 + magic );
|
|
|
|
// create the HMAC Sha1 hash and assign it.
|
|
hash2 = createHMACSha1 ( key, temp );
|
|
temp.clear();
|
|
|
|
// append the first hash.
|
|
temp.append ( hash1 );
|
|
|
|
// create the HMAC Sha1 hash and assign it.
|
|
hash3 = createHMACSha1 ( key, temp );
|
|
temp.clear();
|
|
|
|
// assign the third hash with the magic string.
|
|
temp.append ( hash3 + magic );
|
|
// create the HMAC Sha1 hash and assign it.
|
|
hash4 = createHMACSha1 ( key, temp );
|
|
temp.clear();
|
|
|
|
// return the second hash with the first four bytes of the fourth hash.
|
|
return hash2 + hash4.left ( 4 );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Draws an icon with an overlay from two pixmaps.
|
|
*
|
|
* This method takes the two pixmaps and draws them over each other, returning a new pixmap with 'overlay'
|
|
* as an overlay, with given width and height. If -1 are given as width and height, the size of 'icon'
|
|
* is taken as the end size. -1 is the default for both width and height.
|
|
*
|
|
* Note that the icon always sticks to the top left, whatever the size is, and the overlay always sticks to
|
|
* the bottom right.
|
|
*
|
|
* @param icon The icon which will be displayed as the "main image".
|
|
* @param overlay The overlay which will be displayed over the icon.
|
|
* @param width Width of the returned icon.
|
|
* @param height Height of the returned icon.
|
|
* @param sizeFactor the size of the overlay, compared with the size of the result image; if 1, it fills the image, if .5, the overlay is half the size.
|
|
* @param iconSizeFactor the size of the icon, compared with the size of the result image; if 1, it fills the image (background), if .5, it's half the size.
|
|
* @returns the icon with the overlay overlayed on it.
|
|
*/
|
|
QPixmap KMessShared::drawIconOverlay( const QPixmap icon, const QPixmap overlay, int width, int height, float sizeFactor, float iconSizeFactor )
|
|
{
|
|
if( width == -1 )
|
|
{
|
|
width = icon.size().width();
|
|
}
|
|
if( height == -1 )
|
|
{
|
|
height = icon.size().height();
|
|
}
|
|
|
|
const int imageWidth = width * iconSizeFactor;
|
|
const int imageHeight = height * iconSizeFactor;
|
|
|
|
const int overlayWidth = width * sizeFactor;
|
|
const int overlayHeight = height * sizeFactor;
|
|
|
|
const int x = width - overlayWidth;
|
|
const int y = height - overlayHeight;
|
|
|
|
// thanks, Amarok, for the code and the idea :)
|
|
QPixmap result( width, height );
|
|
result.fill( Qt::transparent );
|
|
|
|
QPainter p( &result );
|
|
p.drawPixmap( 0, 0, icon. scaled( imageWidth, imageHeight ) );
|
|
p.drawPixmap( x, y, overlay.scaled( overlayWidth, overlayHeight ) );
|
|
p.end();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Generate a random GUID.
|
|
*
|
|
* This is used in MSNP2P for the Call ID and Branch ID.
|
|
*
|
|
* @return A randomly generated GUID value.
|
|
*/
|
|
QString KMessShared::generateGUID()
|
|
{
|
|
// much easier than the code below :)
|
|
return QUuid::createUuid().toString().toUpper();
|
|
|
|
/*
|
|
// This code is based on Kopete, but much shorter.
|
|
QString guid( "{"
|
|
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
|
|
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
|
|
+ "-"
|
|
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
|
|
+ "-"
|
|
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
|
|
+ "-"
|
|
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
|
|
+ "-"
|
|
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
|
|
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
|
|
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
|
|
+ "}" );
|
|
return guid.toUpper();
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Generate an random number to use as ID.
|
|
*
|
|
* For use in MSNP2P, the value will not be below 4
|
|
*
|
|
* @return A random value between 4 and RAND_MAX.
|
|
*/
|
|
quint32 KMessShared::generateID()
|
|
{
|
|
//return (rand() & 0x00FFFFFC);
|
|
// seed the RNG properly.
|
|
srand(time(NULL));
|
|
return (rand() + 100); // at least 3 digits for an ID (as per ticket #294??).
|
|
}
|
|
|
|
|
|
|
|
// Return the hash of a file name using the SHA1 algorithm
|
|
QByteArray KMessShared::generateFileHash( const QString &fileName )
|
|
{
|
|
// Read the file's contents into a byte array
|
|
QFile file( fileName );
|
|
if( ! file.open( QIODevice::ReadOnly ) )
|
|
{
|
|
return QByteArray();
|
|
}
|
|
|
|
QByteArray fileData( file.readAll() );
|
|
|
|
file.close();
|
|
|
|
// Retrieve and return the file's hash
|
|
return QCryptographicHash::hash( fileData, QCryptographicHash::Sha1 );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Converts a string with HTML to one with escaped entities
|
|
*
|
|
* Only the main HTML control characters are escaped; the string is made suitable for
|
|
* insertion within an HTML tag attribute.
|
|
* Neither KDE nor Qt have escape/unescape methods this thorough, they only replace the <>&. Annoying.
|
|
*
|
|
* @param string The string to modify
|
|
* @return A reference to the modified string
|
|
*/
|
|
QString &KMessShared::htmlEscape( QString &string )
|
|
{
|
|
string.replace( ";", ";" )
|
|
.replace( "&", "&" )
|
|
.replace( "&#59;", ";" ) // oh god :(
|
|
.replace( "<", "<" )
|
|
.replace( ">", ">" )
|
|
.replace( "'", "'" )
|
|
.replace( '"', """ ); // NOTE: by not using " this result is also usable for XML.
|
|
|
|
return string;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Converts a string constant with HTML to one with escaped entities
|
|
*
|
|
* This version is suitable for use with string constants, as it creates a copy of the original string.
|
|
*
|
|
* @param string The string to modify
|
|
* @return Another string with the escaped contents
|
|
*/
|
|
QString KMessShared::htmlEscape( const QString &string )
|
|
{
|
|
QString copy( string );
|
|
return htmlEscape( copy );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Converts a string with escaped entities to one with HTML
|
|
*
|
|
* Only the main HTML control characters are unescaped; the string is reverted back to its
|
|
* original state.
|
|
* Neither KDE nor Qt have HTML to text decoding methods. Annoying.
|
|
*
|
|
* @param string The string to modify
|
|
* @return A reference to the modified string
|
|
*/
|
|
QString &KMessShared::htmlUnescape( QString &string )
|
|
{
|
|
string.replace( """, "\"" )
|
|
.replace( "'", "'" )
|
|
.replace( ">", ">" )
|
|
.replace( "<", "<" )
|
|
.replace( ";", "&#59;" )
|
|
.replace( "&", "&" )
|
|
.replace( ";", ";" );
|
|
|
|
return string;
|
|
}
|
|
|
|
/**
|
|
* Converts a string with escaped entities to one with HTML
|
|
*
|
|
* This version is suitable for use with string constants, as it creates a copy of the original string.
|
|
*
|
|
* @param string The string to modify
|
|
* @return Another string with the escaped contents
|
|
*/
|
|
QString KMessShared::htmlUnescape( const QString &string )
|
|
{
|
|
QString copy( string );
|
|
return htmlUnescape( copy );
|
|
}
|
|
|
|
/**
|
|
* @brief Open the given URL respecting the user's preference
|
|
*
|
|
* The program which will open the URL is the one selected in the Global Settings
|
|
*
|
|
* @param url The URL to open
|
|
*/
|
|
void KMessShared::openBrowser( const KUrl &url )
|
|
{
|
|
// Open the options
|
|
KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "General" );
|
|
|
|
QString browser;
|
|
QString fallbackBrowser( group.readEntry( "customBrowser", "konqueror" ) );
|
|
|
|
// Obtain the widget of the main KMess window, to correctly link it to the opened browser
|
|
KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp );
|
|
QWidget *mainWindow = kmessApp->getContactListWindow()->window();
|
|
|
|
// Read which browser has to be opened
|
|
QString browserChoice( group.readEntry( "useBrowser", "KDE" ) );
|
|
|
|
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
kDebug() << "Opening URL" << url << "- The setting is" << browserChoice;
|
|
#endif
|
|
|
|
// If the preference had an invalid choice, use the KDE default browser.
|
|
if( browserChoice != "custom"
|
|
&& browserChoice != "listed"
|
|
&& browserChoice != "KDE" )
|
|
{
|
|
kWarning() << "Invalid browser choice:" << browserChoice << ", using KDE default.";
|
|
|
|
browserChoice = "KDE";
|
|
}
|
|
|
|
// Launch the appropriate browser
|
|
if( browserChoice == "custom" )
|
|
{
|
|
browser = group.readEntry( "customBrowser", fallbackBrowser );
|
|
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
kDebug() << "Custom browser selected:" << browser;
|
|
#endif
|
|
}
|
|
else if( browserChoice == "listed" )
|
|
{
|
|
browser = group.readEntry( "listedBrowser", fallbackBrowser );
|
|
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
kDebug() << "Listed browser selected:" << browser;
|
|
#endif
|
|
}
|
|
|
|
// If the option isn't valid use the KDE default
|
|
if( browser.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
kDebug() << "Invalid browser option, using KDE default.";
|
|
#endif
|
|
browserChoice = "KDE";
|
|
}
|
|
|
|
if( browserChoice == "KDE" )
|
|
{
|
|
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
kDebug() << "KDE default browser selected.";
|
|
#endif
|
|
KToolInvocation::invokeBrowser( url.url() );
|
|
return;
|
|
}
|
|
|
|
// Replace the %u in the command line with the actual URL, and launch the browser.
|
|
browser.replace( "%u", KShell::quoteArg( url.url() ), Qt::CaseInsensitive );
|
|
KRun::runCommand( browser, mainWindow );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Open the given URL mail respecting the user's preference
|
|
*
|
|
* The program which will open the URL is the one selected in the Global Settings
|
|
*
|
|
* @param mailto The address to compose a message to (can also be empty to open the folder instead).
|
|
* @param folder The folder to open.
|
|
* @param isHotmailAvailable Whether Hotmail can be used to access the email for this account.
|
|
*/
|
|
void KMessShared::openEmailClient( const QString &mailto, const QString &folder, bool isHotmailAvailable )
|
|
{
|
|
// Open the options
|
|
KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "General" );
|
|
|
|
// When using Live! Mail accounts, the webmail can be used
|
|
if( isHotmailAvailable && group.readEntry( "useLiveMail", true ) )
|
|
{
|
|
// Create the request file
|
|
QString filename( createRequestFile( mailto, folder ) );
|
|
if( ! filename.isEmpty() )
|
|
{
|
|
// Open the webmail in the browser
|
|
openBrowser( KUrl( "file://" + filename ) );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
QString mailClient;
|
|
QString fallbackMailClient( group.readEntry( "customMailClient", "kmail" ) );
|
|
|
|
// Obtain the widget of the main KMess window, to correctly link it to the opened app
|
|
KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp );
|
|
QWidget *mainWindow = kmessApp->getContactListWindow()->window();
|
|
|
|
// Read which mail client has to be opened
|
|
QString mailClientChoice( group.readEntry( "useMailClient", "KDE" ) );
|
|
|
|
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
kDebug() << "Opening mail at" << folder << "- The setting is" << mailClientChoice;
|
|
#endif
|
|
|
|
// If the preference had an invalid choice, use the KDE default mail client.
|
|
if( mailClientChoice != "custom"
|
|
&& mailClientChoice != "listed"
|
|
&& mailClientChoice != "KDE" )
|
|
{
|
|
kWarning() << "Invalid mail client choice:" << mailClientChoice << ", using KDE default.";
|
|
|
|
mailClientChoice = "KDE";
|
|
}
|
|
|
|
// Launch the appropriate mail client
|
|
if( mailClientChoice == "custom" )
|
|
{
|
|
mailClient = group.readEntry( "customMailClient", fallbackMailClient );
|
|
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
kDebug() << "Custom mail client selected:" << mailClient;
|
|
#endif
|
|
}
|
|
else if( mailClientChoice == "listed" )
|
|
{
|
|
mailClient = group.readEntry( "listedMailClient", fallbackMailClient );
|
|
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
kDebug() << "Listed mail client selected:" << mailClient;
|
|
#endif
|
|
}
|
|
|
|
// If the option isn't valid use the KDE default
|
|
if( mailClient.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
kDebug() << "Invalid mail client option, using KDE default.";
|
|
#endif
|
|
mailClientChoice = "KDE";
|
|
}
|
|
|
|
// Retrieve the mail address to open
|
|
if( mailClientChoice == "KDE" )
|
|
{
|
|
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
|
|
kDebug() << "KDE default mail client selected.";
|
|
#endif
|
|
KToolInvocation::invokeMailer( KUrl( "mailto:" + mailto ) );
|
|
return;
|
|
}
|
|
|
|
// Replace the %u in the command line with the contact address, and launch the application.
|
|
mailClient.replace( "%u", KShell::quoteArg( mailto ), Qt::CaseInsensitive );
|
|
KRun::runCommand( mailClient, mainWindow );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Select the next available file.
|
|
*
|
|
* This method searches for numbered files and tries to find the last *existing* file.
|
|
* If it returns false, ( baseName + "." + extension ) can be created directly; otherwise,
|
|
* ( baseName + suffix + "." + extension ) is the last file that does exist and to create
|
|
* a new file, use ( baseName + "." + startingNumber + "." + extension ).
|
|
*
|
|
* If startingNumber is 1, there is actually no suffix; but this is never a problem if you
|
|
* correctly handle this method returning false, reading the existing file using the suffix,
|
|
* and writing a new file using startingNumber.
|
|
*
|
|
* For example, calling selectNextFile( "/", "name", suffix, "ext", startingNumber ):
|
|
* - if startingNumber is zero and "/name.ext" doesn't exist, returns false,
|
|
* suffix = "", startingNumber varies depending on if anything after name.ext exists
|
|
* (ie if name.1.ext exists but name.2.ext doesn't, startingNumber = 2)
|
|
* - else if "/name.1.ext" doesn't exist, returns true, suffix = "", startingNumber = 1
|
|
* (so the last existing file will be "name.ext", which is true)
|
|
* - else if "/name.2.ext" doesn't exist, returns true, suffix = ".1", startingNumber = 2
|
|
* - and so forth
|
|
*
|
|
* <code>// Create a new unique file:
|
|
int startingNumber = 0;
|
|
QString suffix;
|
|
if( KMessShared::selectNextFile( "/kmess/data/directory/", "logFile", suffix, "html", startingNumber ) )
|
|
{
|
|
// The base name exists, create a numbered file
|
|
path = "/kmess/data/directory/logFile." + QString::number( startingNumber ) . ".html";
|
|
}
|
|
else
|
|
{
|
|
// The base name doesn't exist, use it instead
|
|
path = "/kmess/data/directory/logFile.html";
|
|
}
|
|
</code>
|
|
* @param directory (in) Path where the files will be searched, followed by '/'.
|
|
* May be relative.
|
|
* @param baseName (in) Common name for the files, without the extension, see above.
|
|
* @param suffix (out) Append to the baseName to get the name of the last
|
|
* existing numbered file.
|
|
* @param extension (in) Extension of the file to search for, see above.
|
|
* @param startingNumber (in/out) File number to start from, set to zero to start with
|
|
* the common name alone. Gets updated with the number of
|
|
* the first available file.
|
|
* @return bool If the file in the updated fileName param exists or not.
|
|
*/
|
|
bool KMessShared::selectNextFile( const QString &directory, const QString &baseName, QString &suffix, const QString &extension, int &startingNumber )
|
|
{
|
|
QString path( directory + baseName + "." );
|
|
|
|
// if startingNumber is nonzero, we never allow to return the base file
|
|
// this is important if the base file exists but .1.xml doesn't exist;
|
|
// if this boolean is true it will return .1.xml and return false, if it's
|
|
// false it will be allowed to return .xml and return true.
|
|
// HACK XXX FIXME - Fix this damn method for 2.0! We can't have this legacy code like this.
|
|
bool returnBaseFileAllowed = startingNumber == 0;
|
|
|
|
if( returnBaseFileAllowed && ! QFile::exists( path + extension ) )
|
|
{
|
|
// "baseName.ext" doesn't exist, so return false
|
|
// but first find the next available file for writing
|
|
while( QFile::exists( path + QString::number( ++startingNumber ) + "." + extension ) ) ;
|
|
|
|
#ifdef KMESSDEBUG_SHAREDMETHODS
|
|
kDebug() << "Base file " << ( baseName + "." + extension) << "does not exist. Next sequential file is: " << ( baseName + "." + QString::number( startingNumber ) + "." + extension );
|
|
#endif
|
|
|
|
suffix = "";
|
|
return false;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_SHAREDMETHODS
|
|
kDebug() << "Base file" << ( baseName + "." + extension ) << "exists, finding the last existing numbered file";
|
|
#endif
|
|
|
|
// Start the search sequence number from 1
|
|
if( startingNumber < 1 )
|
|
{
|
|
startingNumber = 1;
|
|
}
|
|
|
|
// find the last existing file
|
|
while( QFile::exists( path + QString::number( startingNumber ) + "." + extension ) )
|
|
{
|
|
startingNumber++;
|
|
}
|
|
|
|
// Found the first non-existing file
|
|
if( startingNumber == 1 )
|
|
{
|
|
if( returnBaseFileAllowed )
|
|
{
|
|
// the last existing file is the base filename, without a suffix
|
|
suffix = QString();
|
|
}
|
|
else
|
|
{
|
|
suffix = ".1";
|
|
#ifdef KMESSDEBUG_SHAREDMETHODS
|
|
kDebug() << "Called with nonzero startingNumber, so I'm not allowed to return the base file!"
|
|
<< "Returning " << ( baseName + suffix + "." + extension ) << " which doesn't exist.";
|
|
#endif
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the last existing file is the previously checked file in the loop
|
|
suffix = "." + QString::number( startingNumber - 1 );
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_SHAREDMETHODS
|
|
kDebug() << "Last existing file name:" << ( baseName + suffix + "." + extension );
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
// Truncate an UTF-8 multibyte string to a fixed byte size
|
|
QString KMessShared::fixStringToByteSize( const QString &string, quint32 size )
|
|
{
|
|
// Check if truncating is not needed
|
|
if( string.isEmpty() || size == 0 )
|
|
{
|
|
return string;
|
|
}
|
|
|
|
// Seek through the string one multibyte char at a time, and count their actual size in bytes
|
|
quint32 validPos = 0;
|
|
for( quint32 pos = 0; (qint32)pos < string.length(); ++pos )
|
|
{
|
|
QChar character( string.at( pos ) );
|
|
|
|
quint32 skip = 0;
|
|
quint32 more;
|
|
|
|
// Character ranges from http://en.wikipedia.org/wiki/UTF-8
|
|
if( character <= 0x007f )
|
|
{
|
|
more = 1;
|
|
}
|
|
else if( character <= 0x07FF )
|
|
{
|
|
more = 2;
|
|
}
|
|
else if( character <= 0xd7ff )
|
|
{
|
|
more = 3;
|
|
}
|
|
else if( character <= 0xDFFF )
|
|
{
|
|
// Surrogate area, consume next char as well
|
|
more = 4;
|
|
skip = 1;
|
|
}
|
|
else
|
|
{
|
|
more = 3;
|
|
}
|
|
|
|
// The next character would make the string too long: cut it here
|
|
if( validPos + more > size )
|
|
{
|
|
return QString::fromUtf8( string.toUtf8().left( validPos ) );
|
|
}
|
|
|
|
validPos += more;
|
|
pos += skip;
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
|