/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jkiss.dbeaver.ui.editors.binary; import org.jkiss.dbeaver.Log; import org.eclipse.swt.dnd.*; import org.eclipse.swt.widgets.Display; import org.jkiss.dbeaver.utils.ContentUtils; import org.jkiss.utils.StandardConstants; import java.io.*; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; /** * A clipboard for binary content. Data up to 4Mbytes is made available as text as well * * @author Jordi */ public class BinaryClipboard { private static final Log log = Log.getLog(HexEditControl.class); static class FileByteArrayTransfer extends ByteArrayTransfer { static final String FORMAT_NAME = "BinaryFileByteArrayTypeName"; static final int FORMAT_ID = registerType(FORMAT_NAME); static final FileByteArrayTransfer instance = new FileByteArrayTransfer(); private FileByteArrayTransfer() { } static FileByteArrayTransfer getInstance() { return instance; } @Override public void javaToNative(Object object, TransferData transferData) { if (object == null || !(object instanceof File)) return; if (isSupportedType(transferData)) { File myType = (File) object; try { // write data to a byte array and then ask super to convert to pMedium ByteArrayOutputStream out = new ByteArrayOutputStream(); DataOutputStream writeOut = new DataOutputStream(out); byte[] buffer = myType.getAbsolutePath().getBytes(Charset.defaultCharset()); writeOut.writeInt(buffer.length); writeOut.write(buffer); buffer = out.toByteArray(); writeOut.close(); super.javaToNative(buffer, transferData); } catch (IOException e) { log.warn(e); } // copy nothing then } } @Override public Object nativeToJava(TransferData transferData) { if (!isSupportedType(transferData)) return null; byte[] buffer = (byte[]) super.nativeToJava(transferData); if (buffer == null) { return null; } DataInputStream readIn = new DataInputStream(new ByteArrayInputStream(buffer)); try { int size = readIn.readInt(); if (size <= 0) { return null; } byte[] nameBytes = new byte[size]; if (readIn.read(nameBytes) < size){ return null; } return new File(new String(nameBytes)); } catch (IOException ex) { log.warn(ex); return null; } } @Override protected String[] getTypeNames() { return new String[]{FORMAT_NAME}; } @Override protected int[] getTypeIds() { return new int[]{FORMAT_ID}; } } static class MemoryByteArrayTransfer extends ByteArrayTransfer { static final String FORMAT_NAME = "BinaryMemoryByteArrayTypeName"; static final int FORMAT_ID = registerType(FORMAT_NAME); static final MemoryByteArrayTransfer instance = new MemoryByteArrayTransfer(); private MemoryByteArrayTransfer() { } static MemoryByteArrayTransfer getInstance() { return instance; } @Override public void javaToNative(Object object, TransferData transferData) { if (object == null || !(object instanceof byte[])) return; if (isSupportedType(transferData)) { byte[] buffer = (byte[]) object; super.javaToNative(buffer, transferData); } } @Override public Object nativeToJava(TransferData transferData) { Object result = null; if (isSupportedType(transferData)) { result = super.nativeToJava(transferData); } return result; } @Override protected String[] getTypeNames() { return new String[]{FORMAT_NAME}; } @Override protected int[] getTypeIds() { return new int[]{FORMAT_ID}; } } private static final File clipboardDir = new File(System.getProperty(StandardConstants.ENV_TMP_DIR, ".")); private static final File clipboardFile = new File(clipboardDir, "dbeaver-binary-clipboard.tmp"); private static final long maxClipboardDataInMemory = 4 * 1024 * 1024; // 4 Megs for byte[], 4 Megs for text private final Map<File, Integer> filesReferencesCounter = new HashMap<>(); private final Clipboard clipboard; /** * Init system resources for the clipboard */ public BinaryClipboard(Display aDisplay) { clipboard = new Clipboard(aDisplay); } /** * Dispose system clipboard and file resources * * @see Clipboard#dispose() */ public void dispose() throws IOException { if (!clipboard.isDisposed()) { File lastPaste = (File) clipboard.getContents(FileByteArrayTransfer.getInstance()); clipboard.dispose(); if (!clipboardFile.equals(lastPaste)) // null emptyClipboardFile(); } for (File aFile : filesReferencesCounter.keySet()) { int count = filesReferencesCounter.get(aFile); File lock = getLockFromFile(aFile); if (updateLock(lock, -count)) { // lock deleted if (!aFile.delete()) { aFile.deleteOnExit(); } } } } void emptyClipboardFile() { if (clipboardFile.canWrite() && clipboardFile.length() > 0L) { try { RandomAccessFile file = new RandomAccessFile(clipboardFile, "rw"); try { file.setLength(0L); } finally { ContentUtils.close(file); } } catch (IOException e) { log.warn(e); } // ok, leave it alone } } /** * Dispose system clipboard resources * * @see Object#finalize() */ @Override protected void finalize() throws Throwable { dispose(); super.finalize(); } /** * Paste the clipboard contents into a BinaryContent */ public long getContents(BinaryContent content, long start, boolean insert) { long total = tryGettingFiles(content, start, insert); if (total >= 0L) return total; total = tryGettingMemoryByteArray(content, start, insert); if (total >= 0L) return total; total = tryGettingFileByteArray(content, start, insert); if (total >= 0L) return total; return 0L; } static File getLockFromFile(File lastPaste) { String name = lastPaste.getAbsolutePath(); return new File(name.substring(0, name.length() - 3) + "lock"); } /** * Tells whether there is valid data in the clipboard * * @return true: data is available */ public boolean hasContents() { TransferData[] available = clipboard.getAvailableTypes(); for (int i = 0; i < available.length; ++i) { if (MemoryByteArrayTransfer.getInstance().isSupportedType(available[i]) || TextTransfer.getInstance().isSupportedType(available[i]) || FileByteArrayTransfer.getInstance().isSupportedType(available[i]) || FileTransfer.getInstance().isSupportedType(available[i])) return true; } return false; } /** * Set the clipboard contents with a BinaryContent */ public void setContents(BinaryContent content, long start, long length) { if (length < 1L) return; Object[] data; Transfer[] transfers; try { if (length <= maxClipboardDataInMemory) { byte[] byteArrayData = new byte[(int) length]; content.get(ByteBuffer.wrap(byteArrayData), start); String textData = new String(byteArrayData); transfers = new Transfer[]{MemoryByteArrayTransfer.getInstance(), TextTransfer.getInstance()}; data = new Object[]{byteArrayData, textData}; } else { content.get(clipboardFile, start, length); transfers = new Transfer[]{FileByteArrayTransfer.getInstance()}; data = new Object[]{clipboardFile}; } } catch (IOException e) { clipboard.setContents(new Object[]{new byte[1]}, new Transfer[]{MemoryByteArrayTransfer.getInstance()}); clipboard.clearContents(); emptyClipboardFile(); return; // copy nothing then } clipboard.setContents(data, transfers); } /* * The file is being reference counted. It will be deleted as soon as no binary process is * referencing it anymore. */ long tryGettingFileByteArray(BinaryContent content, long start, boolean insert) { File lastPaste = (File) clipboard.getContents(FileByteArrayTransfer.getInstance()); if (lastPaste == null) return -1L; long total = lastPaste.length(); if (!insert && total > content.length() - start) return 0L; File lock; if (clipboardFile.equals(lastPaste)) { for (int i = 0; ; ++i) { StringBuilder name = new StringBuilder("binaryPasted").append(i); lastPaste = new File(clipboardDir, name.toString() + ".tmp"); lock = new File(clipboardDir, name.append(".lock").toString()); if (!lock.exists()) if (!lastPaste.exists() || lastPaste.delete()) break; } if (lastPaste.exists() || lock.exists()) { return 0L; } if (!clipboardFile.renameTo(lastPaste)) { log.warn("Can't rename clipboard temp file"); } clipboard.setContents( new Object[]{lastPaste}, new Transfer[]{FileByteArrayTransfer.getInstance()}); } else { lock = getLockFromFile(lastPaste); } try { if (insert) content.insert(lastPaste, start); else content.overwrite(lastPaste, start); } catch (IOException e) { total = 0L; } if (total > 0L) { try { updateLock(lock, 1); } catch (IOException e) { filesReferencesCounter.remove(lastPaste); return total; } Integer value = filesReferencesCounter.put(lastPaste, 1); if (value != null) filesReferencesCounter.put(lastPaste, value + 1); } return total; } long tryGettingFiles(BinaryContent content, long start, boolean insert) { String[] files = (String[]) clipboard.getContents(FileTransfer.getInstance()); if (files == null) return -1L; long total = 0L; if (!insert) { for (int i = 0; i < files.length; ++i) { File file = new File(files[i]); total += file.length(); if (total > content.length() - start) { return 0L; // would overflow } } } total = 0L; for (int i = files.length - 1; i >= 0; --i) { // for some reason they are given in reverse order File file = new File(files[i]); try { file = file.getCanonicalFile(); } catch (IOException e) { log.warn(e); } // use non-canonical one then boolean success = true; try { if (insert) content.insert(file, start); else content.overwrite(file, start); } catch (IOException e) { success = false; } if (success) { start += file.length(); total += file.length(); } } return total; } long tryGettingMemoryByteArray(BinaryContent content, long start, boolean insert) { byte[] byteArray = (byte[]) clipboard.getContents(MemoryByteArrayTransfer.getInstance()); if (byteArray == null) { String text = (String) clipboard.getContents(TextTransfer.getInstance()); if (text != null) { byteArray = text.getBytes(Charset.defaultCharset()); } } if (byteArray == null) return -1L; long total = byteArray.length; ByteBuffer buffer = ByteBuffer.wrap(byteArray); if (insert) { content.insert(buffer, start); } else if (total <= content.length() - start) { content.overwrite(buffer, start); } else { total = 0L; } return total; } boolean updateLock(File lock, int references) throws IOException { RandomAccessFile file = new RandomAccessFile(lock, "rw"); try { if (file.length() >= 4) references += file.readInt(); if (references > 0) { file.seek(0); file.writeInt(references); } } finally { ContentUtils.close(file); } if (references < 1) { if (!lock.delete()) { log.warn("Cannot delete lock file '" + lock.getAbsolutePath() + "'"); } return true; } return false; } }