/******************************************************************************* * Jimm - Mobile Messaging - J2ME ICQ clone * Copyright (C) 2003-05 Jimm Project * * 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. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ******************************************************************************** * File: src/jimm/FileTransfer.java * Version: ###VERSION### Date: ###DATE### * Author(s): Andreas Rossbacher, Dmitry Tunin *******************************************************************************/ // #sijapp cond.if modules_FILES="true"# package jimm; import java.io.*; import javax.microedition.io.*; import javax.microedition.lcdui.Image; import jimmui.model.chat.ChatModel; import jimmui.model.chat.MessData; import jimm.comm.*; import jimm.modules.*; // #sijapp cond.if modules_FILES="true"# import jimm.modules.fs.*; // #sijapp cond.end# import jimm.modules.photo.*; import jimmui.view.UIBuilder; import jimmui.view.form.Form; import jimmui.view.form.FormListener; import jimm.util.JLocale; import protocol.Contact; import protocol.Protocol; import protocol.net.TcpSocket; import protocol.xmpp.XmppContact; public final class FileTransfer implements FormListener, FileBrowserListener, PhotoListener, Runnable { // Form for entering the name and description private Form name_Desc; // File data private String filename; private String description; private int sendMode; private InputStream fis; private int fsize; // File path and description TextField private static final int descriptionField = 1000; private static final int transferMode = 1001; private MessData progressInstance; private boolean canceled = false; private Protocol protocol; private Contact cItem; private ChatModel chat; // #sijapp cond.if modules_ANDROID isnot "true" # private ViewFinder vf; // #sijapp cond.end # private JSR75FileSystem file; // Constructor public FileTransfer(Protocol p, Contact _cItem) { protocol = p; cItem = _cItem; } // Return the cItem belonging to this FileTransfer public Contact getReceiver() { return cItem; } // Set the file data private void setData(InputStream is, int size) { fis = is; fsize = size; } public InputStream getFileIS() { return fis; } public int getFileSize() { return fsize; } // Start the file transfer procedure depening on the ft type public void startFileTransfer() { // #sijapp cond.if modules_ANDROID is "true" # if (ru.net.jimm.JimmActivity.getInstance().externalApi.pickFile(this)) { return; } jimm.modules.DebugLog.panic("show file browser"); // #sijapp cond.end # FileBrowser fsBrowser = new FileBrowser(false); fsBrowser.setListener(this); fsBrowser.activate(); } public static boolean isPhotoSupported() { String supports = System.getProperty("video.snapshot.encodings"); return !StringUtils.isEmpty(supports); } public void startPhotoTransfer() { // #sijapp cond.if modules_ANDROID isnot "true" # vf = new ViewFinder(); vf.setPhotoListener(this); vf.show(); // #sijapp cond.else # ru.net.jimm.JimmActivity.getInstance().externalApi.startCamera(this, 1024, 768); // #sijapp cond.end # } // #sijapp cond.if modules_ANDROID is "true" # public void onFileSelect(InputStream in, String fileName) { try { DebugLog.println("onFileSelect setFileName"); setFileName(fileName); DebugLog.println("onFileSelect setData"); int fileSize = in.available(); setData(in, fileSize); DebugLog.println("onFileSelect askForNameDesc"); askForNameDesc(); DebugLog.println("onFileSelect done"); } catch (Exception e) { DebugLog.panic("onFileSelect", e); closeFile(); handleException(new JimmException(191, 6)); } } // #sijapp cond.end # public void onFileSelect(String filename) throws JimmException { file = FileSystem.getInstance(); try { file.openFile(filename); setFileName(file.getName()); // FIXME resource leak InputStream is = file.openInputStream(); int fileSize = (int)file.fileSize(); byte[] image = null; // #sijapp cond.if modules_ANDROID is "true" # if ((fileSize < MAX_IMAGE_SIZE) && isImageFile()) { image = FileSystem.getInstance().getFileContent(filename); } // #sijapp cond.end # setData(is, fileSize); askForNameDesc(); showPreview(image); } catch (Exception e) { closeFile(); throw new JimmException(191, 3); } } // public void onDirectorySelect(String s0) {} /* Helpers for options UI: */ private void askForNameDesc() { name_Desc = UIBuilder.createForm("name_desc", "ok", "back", this); name_Desc.addString("filename", filename); name_Desc.addTextField(descriptionField, "description", "", 255); String items = "jimm.net.ru|www.jimm.net.ru|jimm.org"; // #sijapp cond.if protocols_JABBER is "true" # if (cItem instanceof XmppContact) { if (cItem.isSingleUserContact() && cItem.isOnline()) { items += "|ibb"; } } // #sijapp cond.end # name_Desc.addSelector(transferMode, "send_via", items, 0); name_Desc.addString(JLocale.getString("size") + ": ", String.valueOf(getFileSize()/1024)+" kb"); // #sijapp cond.if modules_TRAFFIC is "true" # int cost = Traffic.calcCost(getFileSize()); if (0 < cost) { name_Desc.addString(JLocale.getString("cost") + ": ", Traffic.costToString(cost)); } // #sijapp cond.end # name_Desc.show(); } // Command listener public void formAction(Form form, boolean apply) { if (apply) { description = name_Desc.getTextFieldValue(descriptionField); sendMode = name_Desc.getSelectorValue(transferMode); addProgress(); if (name_Desc.getSelectorValue(transferMode) == IBB_MODE) { try { protocol.sendFile(this, filename, description); } catch (Exception ignored) { } } else { setProgress(0); new Thread(this).start(); } } else { destroy(); Jimm.getJimm().getCL().activate(); } } public boolean is(MessData par) { return (progressInstance == par); } private String getProgressText() { return filename + " - " + StringUtils.bytesToSizeString(getFileSize(), false); } private void changeFileProgress(String message) { if (cItem.hasChat()) { Jimm.getJimm().getChatUpdater().changeFileProgress(chat, progressInstance, JLocale.getEllipsisString("sending_file"), getProgressText() + "\n" + JLocale.getString(message)); } } public void cancel() { canceled = true; changeFileProgress("canceled"); if (0 < sendMode) { closeFile(); } } public boolean isCanceled() { return canceled; } private void addProgress() { chat = protocol.getChatModel(cItem); progressInstance = Jimm.getJimm().getChatUpdater().addFileProgress(chat, JLocale.getEllipsisString("sending_file"), getProgressText()); Jimm.getJimm().getChatUpdater().activate(chat); Jimm.getJimm().jimmModel.addTransfer(this); } public void setProgress(int percent) { try { _setProgress(percent); } catch (Exception ignored) { } } public void _setProgress(int percent) { if (isCanceled()) { return; } if (-1 == percent) { percent = 100; } if ((0 == percent) && (null == progressInstance)) { return; } if (100 == percent) { Jimm.getJimm().jimmModel.removeTransfer(progressInstance, false); changeFileProgress("complete"); return; } progressInstance.par.setProgress((byte)percent); if (cItem.hasChat()) { Jimm.getJimm().getChatUpdater().invalidate(chat); } } private void handleException(JimmException e) { destroy(); if (isCanceled()) { return; } if (null != progressInstance) { changeFileProgress(JLocale.getString("error") + "\n" + e.getMessage()); } } private void closeFile() { if (null != file) { file.close(); file = null; } TcpSocket.close(fis); fis = null; } public void destroy() { try { closeFile(); Jimm.getJimm().jimmModel.removeTransfer(progressInstance, false); // #sijapp cond.if modules_ANDROID isnot "true" # if (null != vf) { vf.dismiss(); vf = null; } // #sijapp cond.end # name_Desc.clearForm(); Jimm.gc(); } catch (Exception ignored) { } } /** ************************************************************************* */ public void run() { try { InputStream in = getFileIS(); int size = getFileSize(); switch (sendMode) { case JNR_SOCKET: sendFileThroughServer("files.jimm.net.ru", 2000, in, size); break; case JNR_HTTP: sendFileThroughWeb("files.jimm.net.ru:81", in, size); break; case JO_HTTP: sendFileThroughWeb("filetransfer.jimm.org", in, size); break; } } catch(JimmException e) { handleException(e); } catch(Exception e) { // #sijapp cond.if modules_DEBUGLOG is "true" # DebugLog.panic("FileTransfer.run", e); // #sijapp cond.end# handleException(new JimmException(194, 2)); } destroy(); } public void processPhoto(final byte[] data) { setData(new ByteArrayInputStream(data), data.length); String timestamp = Util.getLocalDateString(Jimm.getCurrentGmtTime(), false); String photoName = "photo-" + timestamp.replace('.', '-').replace(' ', '-') + ".jpg"; setFileName(photoName); askForNameDesc(); showPreview(data); } private void showPreview(final byte[] image) { // #sijapp cond.if modules_ANDROID is "true" # new Thread(new Runnable() { @Override public void run() { try { Image img = Image.createImage(image, 0, image.length); name_Desc.addThumbnailImage(img); } catch (Throwable ignored) { // IOException or OutOfMemoryError } } }).start(); // #sijapp cond.end # } private void setFileName(String name) { // Windows fix name = name.replace(':', '.'); name = name.replace('/', '_'); name = name.replace('\\', '_'); name = name.replace('%', '_'); filename = name; } private String getTransferClient() { return "jimm-multi"; } private void sendFileThroughServer(String server, int port, InputStream fis, int fileSize) throws JimmException { TcpSocket socket = new TcpSocket(); try { socket.connectTo(server, port); final int version = 1; OutStream header = new OutStream(); header.writeWordBE(version); header.writeLenAndUtf8String(filename); header.writeLenAndUtf8String(description); header.writeLenAndUtf8String(getTransferClient()); header.writeDWordBE(fileSize); socket.write(header.toByteArray()); socket.flush(); byte[] buffer = new byte[4*1024]; int counter = fileSize; while (counter > 0) { int read = fis.read(buffer); socket.write(buffer, 0, read); counter -= read; if (isCanceled()) { throw new JimmException(194, 1); } socket.flush(); setProgress((100 - 2) * (fileSize - counter) / fileSize); } socket.flush(); int length = socket.read(); if (-1 == length) { throw new JimmException(120, 13); } socket.readFully(buffer, 0, length); String url = StringUtils.utf8beByteArrayToString(buffer, 0, length); if (isCanceled()) { throw new JimmException(194, 1); } // Send info about file StringBuilder messText = new StringBuilder(); if (!StringUtils.isEmpty(description)) { messText.append(description).append("\n"); } messText.append("File: ").append(filename).append("\n"); messText.append("Size: ") .append(StringUtils.bytesToSizeString(fileSize, false)) .append("\n"); messText.append("Link: ").append(url); protocol.sendMessage(cItem, messText.toString(), true); setProgress(100); socket.close(); } catch (JimmException e) { // #sijapp cond.if modules_DEBUGLOG is "true" # DebugLog.panic("send file", e); // #sijapp cond.end# socket.close(); throw e; } catch (Exception e) { // #sijapp cond.if modules_DEBUGLOG is "true" # DebugLog.panic("send file", e); // #sijapp cond.end# socket.close(); throw new JimmException(194, 0); } } private void sendFileThroughWeb(String host, InputStream fis, int fsize) throws JimmException { InputStream is; OutputStream os; HttpConnection sc = null; final String url = "http://" + host + "/__receive_file.php"; try { sc = (HttpConnection) Connector.open(url, Connector.READ_WRITE); sc.setRequestMethod(HttpConnection.POST); String boundary = "a9f843c9b8a736e53c40f598d434d283e4d9ff72"; sc.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); os = sc.openOutputStream(); // Send post header StringBuilder headers = new StringBuilder(); headers.append("--").append(boundary).append("\r\n"); headers.append("Content-Disposition: form-data; name=\"filedesc\"\r\n"); headers.append("\r\n"); headers.append(description); headers.append("\r\n"); headers.append("--").append(boundary).append("\r\n"); headers.append("Content-Disposition: form-data; name=\"jimmfile\"; filename=\""); headers.append(filename).append("\"\r\n"); headers.append("Content-Type: application/octet-stream\r\n"); headers.append("Content-Transfer-Encoding: binary\r\n"); headers.append("\r\n"); os.write(StringUtils.stringToByteArrayUtf8(headers.toString())); // Send file data and show progress byte[] buffer = new byte[1024*2]; int counter = fsize; while (counter > 0) { int read = fis.read(buffer); os.write(buffer, 0, read); counter -= read; if (isCanceled()) { throw new JimmException(194, 1); } setProgress((100 - 2) * (fsize - counter) / fsize); } // Send end of header String end = "\r\n--" + boundary + "--\r\n"; os.write(StringUtils.stringToByteArrayUtf8(end)); // Read response is = sc.openInputStream(); int respCode = sc.getResponseCode(); if (HttpConnection.HTTP_OK != respCode) { throw new JimmException(194, respCode); } StringBuilder response = new StringBuilder(); for (;;) { int read = is.read(); if (read == -1) break; response.append((char)(read & 0xFF)); } String respString = response.toString(); int dataPos = respString.indexOf("http://"); if (-1 == dataPos) { // #sijapp cond.if modules_DEBUGLOG is "true" # DebugLog.println("server say '" + respString + "'"); // #sijapp cond.end# throw new JimmException(194, 1); } if (isCanceled()) { throw new JimmException(194, 1); } respString = Util.replace(respString, "\r", ""); respString = Util.replace(respString, "\n", ""); // Send info about file StringBuilder messText = new StringBuilder(); if (!StringUtils.isEmpty(description)) { messText.append(description).append("\n"); } messText.append("File: ").append(filename).append("\n"); messText.append("Size: ") .append(StringUtils.bytesToSizeString(fsize, false)) .append("\n"); messText.append("Link: ").append(respString); protocol.sendMessage(cItem, messText.toString(), true); setProgress(100); TcpSocket.close(sc); TcpSocket.close(os); TcpSocket.close(is); } catch (IOException e) { TcpSocket.close(sc); // #sijapp cond.if modules_DEBUGLOG is "true" # DebugLog.panic("send file", e); // #sijapp cond.end# throw new JimmException(194, 0); } } private static final int JNR_SOCKET = 0; private static final int JNR_HTTP = 1; private static final int JO_HTTP = 2; private static final int IBB_MODE = 3; private static final int MAX_IMAGE_SIZE = 2*1024*1024; private boolean isImageFile() { return filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".png"); } } // #sijapp cond.end#