Могу ли я перетаскивать элементы из Outlook в мое SWT-приложение?

#drag-and-drop #outlook #swt

#перетаскивание #outlook #swt

Вопрос:

Фон

Наше приложение на основе Eclipse RCP 3.6 позволяет пользователям перетаскивать файлы для хранения / обработки. Это отлично работает, когда файлы перетаскиваются из файловой системы, но не тогда, когда люди перетаскивают элементы (сообщения или вложения) непосредственно из Outlook.

Похоже, это связано с тем, что Outlook хочет передать нашему приложению файлы через FileGroupDescriptorW и FileContents , но SWT включает только FileTransfer тип. (В FileTransfer передаются только пути к файлам, при условии, что получатель может их найти и прочитать. Подход FileGroupDescriptorW / FileContents позволяет передавать файлы непосредственно из приложения в приложение без записи временных файлов на диск.)

Мы попытались создать ByteArrayTransfer подкласс, который мог бы принимать FileGroupDescriptorW и FileContents . Основываясь на некоторых примерах в Интернете, мы смогли получить и проанализировать FileGroupDescriptorW , который (как следует из названия) описывает файлы, доступные для передачи. (См. Эскиз кода ниже.) Но мы не смогли принять FileContents .

Похоже, это связано с тем, что Outlook предлагает FileContents данные только как TYMED_ISTREAM или TYMED_ISTORAGE , но SWT понимает, как обмениваться данными только как TYMED_HGLOBAL . Из них, похоже, TYMED_ISTORAGE было бы предпочтительнее, поскольку неясно, как TYMED_ISTREAM можно было бы обеспечить доступ к содержимому нескольких файлов.

(У нас также есть некоторые опасения по поводу желания SWT выбирать и преобразовывать только один TransferData тип, учитывая, что нам нужно обработать два, но мы думаем, что, вероятно, мы могли бы как-то обойти это в Java: кажется, что все TransferData s доступны на других этапах процесса.)

Вопросы

Мы на правильном пути? Кому-нибудь уже удалось принять FileContents в SWT? Есть ли шанс, что мы могли бы обработать TYMED_ISTORAGE данные, не покидая Java (даже если путем создания исправления на основе фрагментов для SWT или производной версии), или нам также пришлось бы создавать какой-то новый собственный код поддержки?

Соответствующие фрагменты кода

Эскиз кода, который извлекает имена файлов:

     // THIS IS NOT PRODUCTION-QUALITY CODE - FOR ILLUSTRATION ONLY
    final Transfer transfer = new ByteArrayTransfer() {
        private final String[] typeNames = new String[] { "FileGroupDescriptorW", "FileContents" };
        private final int[] typeIds = new int[] { registerType(typeNames[0]), registerType(typeNames[1]) };

        @Override
        protected String[] getTypeNames() {
            return typeNames;
        }

        @Override
        protected int[] getTypeIds() {
            return typeIds;
        }

        @Override
        protected Object nativeToJava(TransferData transferData) {
            if (!isSupportedType(transferData))
                return null;

            final byte[] buffer = (byte[]) super.nativeToJava(transferData);
            if (buffer == null)
                return null;

            try {
                final DataInputStream in = new DataInputStream(new ByteArrayInputStream(buffer));

                long count = 0;
                for (int i = 0; i < 4; i  ) {
                    count  = in.readUnsignedByte() << i;
                }

                for (int i = 0; i < count; i  ) {
                    final byte[] filenameBytes = new byte[260 * 2];
                    in.skipBytes(72); // probable architecture assumption(s) - may be wrong outside standard 32-bit Win XP
                    in.read(filenameBytes);
                    final String fileNameIncludingTrailingNulls = new String(filenameBytes, "UTF-16LE");
                    int stringLength = fileNameIncludingTrailingNulls.indexOf('');
                    if (stringLength == -1)
                        stringLength = 260;
                    final String fileName = fileNameIncludingTrailingNulls.substring(0, stringLength);
                    System.out.println("File "   i   ": "   fileName);
                }

                in.close();

                return buffer;
            }
            catch (final Exception e) {
                return null;
            }
        }
    };
  

В отладчике мы видим, что ByteArrayTransfer ‘s isSupportedType() в конечном итоге возвращает false для FileContents , потому что следующий тест не пройден (поскольку его tymed есть TYMED_ISTREAM | TYMED_ISTORAGE ):

     if (format.cfFormat == types[i] amp;amp;
        (format.dwAspect amp; COM.DVASPECT_CONTENT) == COM.DVASPECT_CONTENT amp;amp; 
        (format.tymed amp; COM.TYMED_HGLOBAL) == COM.TYMED_HGLOBAL  )
        return true;
  

Этот отрывок из org.eclipse.swt.internal.ole.win32.COM оставляет нам меньше надежды на простое решение:

 public static final int TYMED_HGLOBAL = 1;
//public static final int TYMED_ISTORAGE = 8;
//public static final int TYMED_ISTREAM = 4;
  

Спасибо.

Ответ №1:

даже если

 //public static final int TYMED_ISTREAM = 4;
  

Попробуйте приведенный ниже код .. он должен работать

 package com.nagarro.jsag.poc.swtdrag;

imports ... 

public class MyTransfer extends ByteArrayTransfer {
private static int BYTES_COUNT = 592;
private static int SKIP_BYTES = 72;

private final String[] typeNames = new String[] { "FileGroupDescriptorW", "FileContents" };
private final int[] typeIds = new int[] { registerType(typeNames[0]), registerType(typeNames[1]) };

@Override
protected String[] getTypeNames() {
    return typeNames;
}

@Override
protected int[] getTypeIds() {
    return typeIds;
}

@Override
protected Object nativeToJava(TransferData transferData) {
    String[] result = null;

    if (!isSupportedType(transferData) || transferData.pIDataObject == 0)
        return null;

    IDataObject data = new IDataObject(transferData.pIDataObject);
    data.AddRef();
    // Check for descriptor format type
    try {
        FORMATETC formatetcFD = transferData.formatetc;
        STGMEDIUM stgmediumFD = new STGMEDIUM();
        stgmediumFD.tymed = COM.TYMED_HGLOBAL;
        transferData.result = data.GetData(formatetcFD, stgmediumFD);

        if (transferData.result == COM.S_OK) {
            // Check for contents format type
            long hMem = stgmediumFD.unionField;
            long fileDiscriptorPtr = OS.GlobalLock(hMem);
            int[] fileCount = new int[1];
            try {
                OS.MoveMemory(fileCount, fileDiscriptorPtr, 4);
                fileDiscriptorPtr  = 4;
                result = new String[fileCount[0]];
                for (int i = 0; i < fileCount[0]; i  ) {
                    String fileName = handleFile(fileDiscriptorPtr, data);
                    System.out.println("FileName : = "   fileName);
                    result[i] = fileName;
                    fileDiscriptorPtr  = BYTES_COUNT;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                OS.GlobalFree(hMem);
            }
        }
    } finally {
        data.Release();
    }
    return resu<
}

private String handleFile(long fileDiscriptorPtr, IDataObject data) throws Exception {

    // GetFileName
    char[] fileNameChars = new char[OS.MAX_PATH];
    byte[] fileNameBytes = new byte[OS.MAX_PATH];
    COM.MoveMemory(fileNameBytes, fileDiscriptorPtr, BYTES_COUNT);
    // Skip some bytes.
    fileNameBytes = Arrays.copyOfRange(fileNameBytes, SKIP_BYTES, fileNameBytes.length);
    String fileNameIncludingTrailingNulls = new String(fileNameBytes, "UTF-16LE");
    fileNameChars = fileNameIncludingTrailingNulls.toCharArray();
    StringBuilder builder = new StringBuilder(OS.MAX_PATH);
    for (int i = 0; fileNameChars[i] != 0 amp;amp; i < fileNameChars.length; i  ) {
        builder.append(fileNameChars[i]);
    }
    String name = builder.toString();

    try {
        File file = saveFileContent(name, data);
        if (file != null) {
            System.out.println("File Saved @ "   file.getAbsolutePath());
            ;
        }
    } catch (IOException e) {
        System.out.println("Count not save file content");
        ;
    }

    return name;
}

private File saveFileContent(String fileName, IDataObject data) throws IOException {
    File file = null;
    FORMATETC formatetc = new FORMATETC();
    formatetc.cfFormat = typeIds[1];
    formatetc.dwAspect = COM.DVASPECT_CONTENT;
    formatetc.lindex = 0;
    formatetc.tymed = 4; // content.

    STGMEDIUM stgmedium = new STGMEDIUM();
    stgmedium.tymed = 4;

    if (data.GetData(formatetc, stgmedium) == COM.S_OK) {
        file = new File(fileName);
        IStream iStream = new IStream(stgmedium.unionField);
        iStream.AddRef();

        try (FileOutputStream outputStream = new FileOutputStream(file)) {

            int increment = 1024 * 4;
            long pv = COM.CoTaskMemAlloc(increment);
            int[] pcbWritten = new int[1];
            while (iStream.Read(pv, increment, pcbWritten) == COM.S_OK amp;amp; pcbWritten[0] > 0) {
                byte[] buffer = new byte[pcbWritten[0]];
                OS.MoveMemory(buffer, pv, pcbWritten[0]);
                outputStream.write(buffer);
            }
            COM.CoTaskMemFree(pv);

        } finally {
            iStream.Release();
        }
        return file;
    } else {
        return null;
    }
}
}
  

Ответ №2:

Вы смотрели наhttps://bugs.eclipse.org/bugs/show_bug.cgi?id=132514 ?

К этой записи в bugzilla прилагается исправление (для довольно старой версии SWT), которое может представлять интерес.

Комментарии:

1. Я не уверен, как я пропустил это — спасибо! Пара замечаний о патче (после его просмотра): 1) На самом деле он не такой большой, как кажется, потому что, похоже, содержит много строк, содержащих только изменение форматирования. 2) Это касается как случаев Mac OS Carbon, так и Win32. 3) Он вводит новый собственный код. 4) Похоже, что в случае Win32 используется несколько меньше кода, чем в случае Mac OS Carbon. 5) Кажется, что Win32 (по крайней мере) работает, перекачивая содержимое каждого удаляемого файла (предоставляемого в виде потока) в свой собственный временный файл (вместо того, чтобы каким-то образом напрямую предоставлять поток).

Ответ №3:

У меня была такая же проблема, и я создал небольшую библиотеку, предоставляющую класс переноса перетаскивания для JAVA SWT. Его можно найти здесь:

https://github.com/HendrikHoetker/OutlookItemTransfer

В настоящее время он поддерживает удаление почтовых элементов из Outlook в ваше Java-SWT-приложение и предоставит список элементов Outlook с именем файла и массив байтов содержимого файла.

Все это чистая Java и в памяти (без временных файлов).

Использование в вашем SWT-Java-приложении:

   if (OutlookItemTransfer.getInstance().isSupportedType(event.currentDataType)) {
      Object o = OutlookItemTransfer.getInstance().nativeToJava(event.currentDataType);
      if (o != null amp;amp; o instanceof OutlookMessage[]) {
          OutlookMessage[] outlookMessages = (OutlookMessage[])o;
          for (OutlookMessage msg: outlookMessages) {
              //...
          }
      }
  }
  

Затем OutlookItem предоставит два элемента: filename в виде строки и file contents в виде массива байтов.

С этого момента можно записать это в файл или дополнительно обработать массив байтов.

На ваш вопрос выше: — То, что вы находите в файловом дескрипторе, — это имя файла элемента Outlook и указатель на IDataObject — IDataObject может быть проанализирован и предоставит объект IStorage — В этом случае IStorageObject будет корневым контейнером, предоставляющим дополнительные подисторические объекты или IStreams, похожие на файловую систему (directory = IStorage, file = IStream

Вы найдете эти элементы в следующих строках кода:

  • Получить содержимое файла, см. OutlookItemTransfer.java , метод nativeToJava:

     FORMATETC format = new FORMATETC();
    format.cfFormat = getTypeIds()[1];
    format.dwAspect = COM.DVASPECT_CONTENT;
    format.lindex = <fileIndex>;
    format.ptd = 0;
    format.tymed = TYMED_ISTORAGE | TYMED_ISTREAM | COM.TYMED_HGLOBAL;
    
    STGMEDIUM medium = new STGMEDIUM();
    
    if (data.GetData(format, medium) == COM.S_OK) {
        // medium.tymed will now contain TYMED_ISTORAGE
        // in medium.unionfield you will find the root IStorage
    }
      
  • Прочитайте корневую базу данных, см. CompoundStorage, метод readOutlookStorage:

         // open IStorage object
    IStorage storage = new IStorage(pIStorage);
    storage.AddRef();
    
    
    // walk through the content of the IStorage object
    long[] pEnumStorage = new long[1];
    if (storage.EnumElements(0, 0, 0, pEnumStorage) == COM.S_OK) {
    
        // get storage iterator
        IEnumSTATSTG enumStorage = new IEnumSTATSTG(pEnumStorage[0]);
        enumStorage.AddRef();
        enumStorage.Reset();
    
        // prepare statstg structure which tells about the object found by the iterator
        long pSTATSTG = OS.GlobalAlloc(OS.GMEM_FIXED | OS.GMEM_ZEROINIT, STATSTG.sizeof);
        int[] fetched = new int[1];
    
        while (enumStorage.Next(1, pSTATSTG, fetched) == COM.S_OK amp;amp; fetched[0] == 1) {
            // get the description of the the object found
            STATSTG statstg = new STATSTG();
            COM.MoveMemory(statstg, pSTATSTG, STATSTG.sizeof);
    
            // get the name of the object found
            String name = readPWCSName(statstg);
    
            // depending on type of object
            switch (statstg.type) {
                case COM.STGTY_STREAM: {    // load an IStream (=File)
                    long[] pIStream = new long[1];
    
                    // get the pointer to the IStream
                    if (storage.OpenStream(name, 0, COM.STGM_DIRECT | COM.STGM_READ | COM.STGM_SHARE_EXCLUSIVE, 0, pIStream) == COM.S_OK) {
                        // load the IStream
                    }
                }
    
                case COM.STGTY_STORAGE: {   // load an IStorage (=SubDirectory) - requires recursion to traverse the sub dies
    
                    }
    
                }
            }
        }
    
        // close the iterator
        enumStorage.Release();
    }
    
    // close the IStorage object
    storage.Release();
      

Комментарии:

1. Если вы можете, пожалуйста, также предоставьте фрагмент с ключевыми моментами, чтобы ответ не стал бесполезным, если в репозитории будут внесены изменения в будущем.

2. Подробности, представленные в этом сообщении, описывают упомянутый пакет, а не предоставляют решение проблемы в вопросе.

3. У меня была точно такая же проблема, и я начинал с приведенного выше кода. Результат — это то, что вы найдете в GitHub.