Как стать автором
Обновить

Экспорт в mail.app из приложения Qt

Время на прочтение5 мин
Количество просмотров851
Возникла необходимость делать экспорт неких файлов в email (пользователи очень просят). Проблема в том как это сделать в Mac OS X. Ну предположим мы не предполагаем никаких других почтовиков кроме Mail.app. На developer.apple.com нашел описание как это делается без Qt. Первая попытка реализовать сие породила массу вопросов, ответов на которые в developer.apple.com не найти. Вобщем помучился я достаточно и тут выкладываю готовый рецепт реализации такой фичи при помощи AppleScript.

1. Подключаем заголовочный файл карбона:
# include <Carbon/Carbon.h>


2. Объявляем две статические переменные (константы) содержащих два полный AppleScript текста — один для отправки одного аттачмента и другой для отправки двух аттачментов:

char sendMailScript_singAtt[] = "on send_email_message(subjectLine, messageText, fileAttachment)\n"
" tell application \"Mail\"\n"
" (* CREATE THE MESSAGE *)\n"
" activate\n"
" set newMessage to make new outgoing message at end of outgoing messages with properties {subject:subjectLine, content:messageText & return, visible:true}\n"
" \n"
" (* SPECIFY THE ATTACHMENT *)\n"
" tell newMessage\n"
" make new attachment with properties {file name:fileAttachment} at after last paragraph\n"
" end tell\n"
" (*set frontmost to true*)\n"
" end tell\n"
"end send_email_message";
char sendMailScript_doubAtt[] = "on send_email_message(subjectLine, messageText, fileAttachmentA, fileAttachmentB)\n"
" tell application \"Mail\"\n"
" (* CREATE THE MESSAGE *)\n"
" activate\n"
" set newMessage to make new outgoing message at end of outgoing messages with properties {subject:subjectLine, content:messageText & return, visible:true}\n"
" \n"
" (* SPECIFY THE ATTACHMENT *)\n"
" tell newMessage\n"
" make new attachment with properties {file name:fileAttachmentA} at after last paragraph\n"
" make new attachment with properties {file name:fileAttachmentB} at after last paragraph\n"
" end tell\n"
" (*set frontmost to true*)\n"
" end tell\n"
"end send_email_message";


Как видите, скрипты представляют из себя две функции принимающие в качестве параметров текст для сабжа, текст тела сообщения и один или два аттачмента. Способ отправки почти полностью отличается от способа описанного в вышеупомянутой статье, по-видимому статья устарела, а apple не удосужилась обновить сведения. Собственно сам скрипт достаточно тривиален, единственное, на что хочу обратить внимание — это вызов activate. Без этого окна Mail.app открываются на заднем плане.

3. Обратите внимание на последние параметры скриптов — это не просто пути к файлу, это так называемые алиасы. Поэтому следующая функция предназначена для получения этих самых алиасов из пути:

GetAliasHandleForFile(QString fname, AliasHandle * ahandle)
{
FSRef fref;
if(FSPathMakeRef((const UInt8 *)fname.toLocal8Bit().data(), &fref, NULL) == noErr)
  if(FSNewAlias(0, &fref, ahandle) == noErr && (*ahandle))
    return true;
return false;
}

При успешном получении алиаса из пути к файлу переданному в аргументе типа QString функция возвращает true, иначе false

4. Для вызова AppleScript нам необходимо создать из текста скрипта объект-событие, который мы и будем вызывать, передавая в него аргументы для экспорта файла в email. Вот функции создания таких событий для обоих скриптов:


OSStatus CreateEmailMessageEvent1(AppleEvent *theEvent, char* subjectLine, char* messageText, AliasHandle fileAttachment)
{
  OSStatus err;
  ProcessSerialNumber PSN = {0, kCurrentProcess};
  /* create the container list */
  err = AEBuildAppleEvent(
    'ascr', kASSubroutineEvent,
    typeProcessSerialNumber, (Ptr) &PSN, sizeof(PSN),
    kAutoGenerateReturnID, kAnyTransactionID,
    theEvent,
    NULL,
    "'----':[TEXT(@),TEXT(@),alis(@@)],"
    "'snam':TEXT(@)",
    subjectLine, messageText, fileAttachment, "send_email_message");
  return err;
}
OSStatus CreateEmailMessageEvent2(AppleEvent *theEvent, char* subjectLine, char* messageText, AliasHandle fileAttachmentA, AliasHandle fileAttachmentB)
{
  OSStatus err;
  ProcessSerialNumber PSN = {0, kCurrentProcess};
  /* create the container list */
  err = AEBuildAppleEvent(
    'ascr', kASSubroutineEvent,
    typeProcessSerialNumber, (Ptr) &PSN, sizeof(PSN),
    kAutoGenerateReturnID, kAnyTransactionID,
    theEvent,
    NULL,
    "'----':[TEXT(@),TEXT(@),alis(@@),alis(@@)],"
    "'snam':TEXT(@)",
    subjectLine, messageText, fileAttachmentA, fileAttachmentB, "send_email_message");
  return err;
}

Подробности о вызываемых функциях смотрите в документации Apple (я сильно в детали не вдавался, надергал из разных примеров, найденных в интернете)

5. Теперь собственно вызов созданного при помощи описанных выше функций события:


OSStatus ExecuteAppleScriptEvent(const void* text, long textLength, AppleEvent *theEvent, AEDesc *resultData)
{
  ComponentInstance theComponent;
  AEDesc scriptTextDesc;
  OSStatus err;
  OSAID contextID, resultID;
  /* set up locals to a known state */
  theComponent = NULL;
  AECreateDesc(typeNull, NULL, 0, &scriptTextDesc);
  contextID = kOSANullScript;
  resultID = kOSANullScript;
  /* open the scripting component */
  theComponent = OpenDefaultComponent(kOSAComponentType, typeAppleScript);
  if(theComponent != NULL)
  {
    /* put the script text into a Apple event descriptor record */
    err = AECreateDesc(typeChar, text, textLength, &scriptTextDesc);
    if(err == noErr)
    {
      /* compile the script into a new context. The flag
      'kOSAModeCompileIntoContext' is used when compiling a
      script containing a handler into a context. */
      err = OSACompile(theComponent, &scriptTextDesc, kOSAModeCompileIntoContext, &contextID);
      if(err == noErr)
      {
        /* run the script */
        err = OSAExecuteEvent( theComponent, theEvent, contextID, kOSAModeNull, &resultID);
        /* collect the results - if any */
        if(resultData != NULL)
        {
          AECreateDesc(typeNull, NULL, 0, resultData);
          if(err == errOSAScriptError)
            OSAScriptError(theComponent, kOSAErrorMessage, typeChar, resultData);
          else
            if(err == noErr && resultID != kOSANullScript)
              OSADisplay(theComponent, resultID, typeChar, kOSAModeDisplayForHumans, resultData);
        }
      }
    }
  }
  else
    err = paramErr;
  AEDisposeDesc(&scriptTextDesc);
  if(contextID != kOSANullScript)
    OSADispose(theComponent, contextID);
  if(resultID != kOSANullScript)
    OSADispose(theComponent, resultID);
  if(theComponent != NULL)
    CloseComponent(theComponent);
  return err;
}


Как видите функция может возвращать результаты вызова через объект типа AEDesc. В функцию передается текст скрипта и его длина. Именно в этой функции происходит компиляция(OSACompile) и запуск(OSAExecuteEvent) скрипта, а предыдущая функция создания события только создает описание вызова и контейнер для параметров, которые используются при выполнении скрипта.

6. Ну и теперь собираем все вместе:


void SendSingleFileToEmail(QString subj, QString body, QString attPath)
{
  AliasHandle ahandle;
  AppleEvent theEvent;
  if(!GetAliasHandleForFile(attPath, &ahandle))
    return;
  if(CreateEmailMessageEvent1(&theEvent, subj.toLocal8Bit().data(), body.toLocal8Bit().data(), ahandle) == noErr)
    ExecuteAppleScriptEvent(sendMailScript_singAtt, strlen(sendMailScript_singAtt), &theEvent, NULL);
}
void SendDoubleFileToEmail(QString subj, QString body, QString attPathA, QString attPathB)
{
  AliasHandle ahandleA, ahandleB;
  AppleEvent theEvent;
  if(!GetAliasHandleForFile(attPathA, &ahandleA) || !GetAliasHandleForFile(attPathB, &ahandleB))
    return;
  if(CreateEmailMessageEvent2(&theEvent, subj.toLocal8Bit().data(), body.toLocal8Bit().data(), ahandleA, ahandleB) == noErr)
    ExecuteAppleScriptEvent(sendMailScript_doubAtt, strlen(sendMailScript_doubAtt), &theEvent, NULL);
}


Тут у нас две функции соответственно для отправки одного аттачмента и для отправки двух аттачментов. Напоследок хочу заметить что данный код не отправляет файлы, а создает новое сообщение и открывает его для редактирования, ввода адреса получателя или отмены.
Ну и еще один важный момент. Вы не можете использовать этот код если работаете в привилегированном режиме (т.е. от root), как не можете собственно запускать любой AppleScript.
На сегодня всё.
Теги:
Хабы:
Всего голосов 8: ↑7 и ↓1+6
Комментарии4

Публикации

Истории

Работа

QT разработчик
8 вакансий

Ближайшие события