#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.