package org.smartly.commons.network.socket.client; import org.smartly.commons.Delegates; import org.smartly.commons.async.Async; import org.smartly.commons.cryptograph.GUID; import org.smartly.commons.io.filetokenizer.FileChunkInfo; import org.smartly.commons.io.filetokenizer.FileTokenizer; import org.smartly.commons.logging.Level; import org.smartly.commons.logging.Logger; import org.smartly.commons.logging.util.LoggingUtils; import org.smartly.commons.network.socket.messages.UserToken; import org.smartly.commons.network.socket.messages.multipart.Multipart; import org.smartly.commons.network.socket.messages.multipart.MultipartMessagePart; import org.smartly.commons.network.socket.messages.multipart.MultipartPool; import org.smartly.commons.network.socket.server.Server; import org.smartly.commons.network.socket.messages.tools.MultipartMessageUtils; import org.smartly.commons.util.CollectionUtils; import org.smartly.commons.util.FileUtils; import org.smartly.commons.util.PathUtils; import org.smartly.commons.util.StringUtils; import java.io.EOFException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Socket; import java.net.SocketAddress; /** * Socket Client */ public class Client { private static final String DEFAULT_HOST = "localhost"; private static final int DEFAULT_PORT = 10000; // file download private static final int DEFAULT_CHUNK_SIZE = 1 * 3000 * 1024; // 3Mb private static final int DEFAULT_TIMEOUT = 60 * 30 * 1000; // 30 minute timeout private static final Class EVENT_ON_PART = Multipart.OnPartListener.class; private static final Class EVENT_ON_FULL = Multipart.OnFullListener.class; private static final Class EVENT_ON_TIME_OUT = Multipart.OnTimeOutListener.class; private static final Class EVENT_ON_ERROR = Delegates.ExceptionCallback.class; // -------------------------------------------------------------------- // f i e l d s // -------------------------------------------------------------------- private Socket _socket; private Proxy _proxy; private SocketAddress _address; private int _timeOut; private int _chunkSize; private final MultipartPool _multipartPool; // manage downloads private final Delegates.Handlers _eventHandlers; // -------------------------------------------------------------------- // c o n s t r u c t o r // -------------------------------------------------------------------- public Client() { this(Proxy.NO_PROXY); } public Client(final Proxy proxy) { this(proxy, DEFAULT_TIMEOUT, DEFAULT_CHUNK_SIZE); } public Client(final Proxy proxy, final int timeOut, final int chunkSize) { _timeOut = timeOut; _chunkSize = chunkSize; _proxy = proxy; _address = new InetSocketAddress(DEFAULT_HOST, DEFAULT_PORT); _multipartPool = new MultipartPool(_timeOut); _eventHandlers = new Delegates.Handlers(); this.init(); } @Override protected void finalize() throws Throwable { try { _multipartPool.clear(); _eventHandlers.clear(); } catch (Throwable ignore) { } super.finalize(); } // -------------------------------------------------------------------- // e v e n t // -------------------------------------------------------------------- public void onError(final Delegates.ExceptionCallback listener) { _eventHandlers.add(listener); } public void onMultipartPart(final Multipart.OnPartListener listener) { _eventHandlers.add(listener); } public void onMultipartFull(final Multipart.OnFullListener listener) { _eventHandlers.add(listener); } public void onMultipartTimeOut(final Multipart.OnTimeOutListener listener) { _eventHandlers.add(listener); } // -------------------------------------------------------------------- // p u b l i c // -------------------------------------------------------------------- public int getChunkSize() { return _chunkSize; } public void setChunkSize(final int value) { _chunkSize = value; } public int getTimeOut() { return _timeOut; } public void setTimeOut(final int value) { _timeOut = value; if (null != _multipartPool) { _multipartPool.setTimeout(_timeOut); } } public SocketAddress getAddress() { return _address; } public Socket getSocket() { return _socket; } public void setAddress(final SocketAddress value) { _address = value; } public boolean isConnected() { try { return null != _socket && _socket.isConnected(); } catch (Throwable ignored) { } return false; } public void connect() throws IOException { this.connect(_address); } public void connect(final String host, final int port) throws IOException { final SocketAddress address = new InetSocketAddress(host, port); this.connect(address); } public void connect(final SocketAddress address) throws IOException { if (null != _socket) { try { _socket.close(); _socket = null; _socket = new Socket(_proxy); } catch (Throwable ignored) { _socket = null; _socket = new Socket(_proxy); } } else { _socket = new Socket(_proxy); } _address = address; _socket.connect(address, 3000); } public void close() { try { if (null != _socket) { _socket.close(); } } catch (Throwable ignored) { _socket = null; } } public Object send(final Object request) throws Exception { return send(_socket, request); } public Thread[] sendFile(final UserToken userToken, final boolean useMultipleConnections, final Delegates.ProgressCallback progressCallback, final Delegates.ExceptionCallback errorHandler) throws Exception { final String fileName = userToken.getSourceAbsolutePath(); Thread[] result = new Thread[0]; if (this.isConnected() && FileUtils.exists(fileName)) { final String uid = GUID.create(); final String[] chunks = FileTokenizer.splitFromChunkSize(fileName, uid, _chunkSize, null); try { result = this.sendFileChunks(PathUtils.getFilename(fileName, true), userToken, chunks, useMultipleConnections, progressCallback, errorHandler); } finally { this.clearFolder(chunks); } } return result; } public Thread[] getFile(final UserToken userToken, final Delegates.ProgressCallback progressCallback, final Delegates.ExceptionCallback errorHandler) throws Exception { final FileChunkInfo info = new FileChunkInfo(userToken.getLength(), _chunkSize); return this.getFileChunks(userToken, info, progressCallback, errorHandler); } public void addMultipartMessagePart(final MultipartMessagePart part) { _multipartPool.add(part); } // -------------------------------------------------------------------- // p r i v a t e // -------------------------------------------------------------------- private Logger getLogger() { return LoggingUtils.getLogger(this); } private void init() { //-- init multipart pool --// _multipartPool.onPart(new Multipart.OnPartListener() { @Override public void handle(final Multipart sender, final MultipartMessagePart part) { onMultipartPart(sender, part); } }); _multipartPool.onFull(new Multipart.OnFullListener() { @Override public void handle(Multipart sender) { onMultipartFull(sender); } }); _multipartPool.onTimeOut(new Multipart.OnTimeOutListener() { @Override public void handle(Multipart sender) { onMultipartTimeout(sender); } }); } private void clearFolder(final String[] chunks) throws IOException { // clean temp files final String file = CollectionUtils.get(chunks, 0); if (StringUtils.hasText(file)) { FileUtils.delete(PathUtils.getParent(file)); } } private Thread[] getFileChunks(final UserToken userToken, final FileChunkInfo chunkInfo, final Delegates.ProgressCallback progressCallback, final Delegates.ExceptionCallback errorHandler) { final Client self = this; final int len = chunkInfo.getChunkCount(); final String transactionId = GUID.create(); // creates array of workers to download file chunks return Async.maxConcurrent(len, 3, new Delegates.CreateRunnableCallback() { @Override public Runnable handle(int index, int length) { return new DownloadRunnable(index, transactionId, self, userToken, chunkInfo, errorHandler); } }); } private Thread[] sendFileChunks(final String fileName, final UserToken userToken, final String[] chunks, final boolean useMultipleConnections, final Delegates.ProgressCallback progressCallback, final Delegates.ExceptionCallback errorHandler) { final Client self = this; final int len = chunks.length; final String transactionId = GUID.create(); return Async.maxConcurrent(len, 3, new Delegates.CreateRunnableCallback() { @Override public Runnable handle(final int index, final int length) { return new UploadRunnable(index, transactionId, self, fileName, userToken, chunks, useMultipleConnections, errorHandler); /*return new Runnable() { @Override public void run() { try { final String chunk = chunks[index]; final MultipartInfo info = new MultipartInfo(fileName, MultipartInfo.MultipartInfoType.File, chunk, index, len); final MultipartMessagePart part = new MultipartMessagePart(); part.setUserToken(userToken); part.setInfo(info); part.setUid(transactionId); part.setData(FileUtils.copyToByteArray(new File(chunk))); //-- send part --// if (useMultipleConnections) { send(getAddress(), part); } else { send(part); } } catch (Throwable t) { if (null != errorHandler) { errorHandler.handle(null, t); } else { LoggingUtils.getLogger(Client.class).log(Level.SEVERE, null, t); } } } };*/ } }); } void onError(final String message, final Throwable error) { if (_eventHandlers.contains(EVENT_ON_ERROR)) { _eventHandlers.trigger(EVENT_ON_ERROR, message, error); } else { this.getLogger().log(Level.SEVERE, message, error); } } private void onMultipartPart(final Multipart multipart, final MultipartMessagePart part) { try { if (_eventHandlers.contains(EVENT_ON_PART)) { _eventHandlers.triggerAsync(EVENT_ON_PART, multipart, part); } else { // no external handlers. } } catch (Throwable ignored) { } } private void onMultipartFull(final Multipart multipart) { try { if (_eventHandlers.contains(EVENT_ON_FULL)) { _eventHandlers.triggerAsync(EVENT_ON_FULL, multipart); } else { // no external handlers. // handle internally try { saveOnDisk(multipart); } catch (Throwable t) { this.onError(null, t); } } } catch (Throwable ignored) { } } private void onMultipartTimeout(final Multipart multipart) { // timeout try { if (_eventHandlers.contains(EVENT_ON_TIME_OUT)) { _eventHandlers.triggerAsync(EVENT_ON_TIME_OUT, multipart); } else { // no external handlers. // handle internally try { MultipartMessageUtils.remove(multipart); } catch (Throwable t) { this.onError(null, t); } } } catch (Throwable ignored) { } } // -------------------------------------------------------------------- // S T A T I C // -------------------------------------------------------------------- private static String saveOnDisk(final Multipart multipart) throws Exception { try { final UserToken userToken = new UserToken(multipart.getUserToken()); final String target = userToken.getTargetAbsolutePath(); // save file on disk and remove temp return MultipartMessageUtils.saveOnDisk(multipart, target); } catch (Exception t) { MultipartMessageUtils.remove(multipart); throw t; } } public static String sendString(final String request) throws Exception { return sendString("localhost", Server.DEFAULT_PORT, request); } public static String sendString(final String server, final int port, final String request) throws Exception { return (String) send(server, port, (Object) request); } public static Object send(final String host, final int port, final Object request) throws Exception { final SocketAddress address = new InetSocketAddress(host, port); return send(address, request); } public static Object send(final SocketAddress address, final Object request) throws Exception { Object response; final Client cli = new Client(); cli.connect(address); response = cli.send(request); cli.close(); return response; } public static Object send(final Socket socket, final Object request) throws Exception { Object response = null; final ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); final ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); try { out.writeObject(request); out.flush(); try { response = in.readObject(); } catch (EOFException ignored) { // no response } } finally { out.close(); in.close(); } return response; } public static boolean ping(final String server, final int port) { try { final Client cli = new Client(); cli.connect(server, port); cli.close(); return true; } catch (Throwable ignored) { return false; } } }