/* * Copyright 2009 Perforce Software Inc., All Rights Reserved. */ package com.perforce.p4java.impl.mapbased.rpc.func.client; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import java.util.Properties; import com.perforce.p4java.CharsetDefs; import com.perforce.p4java.CharsetConverter; import com.perforce.p4java.Log; import com.perforce.p4java.ILookahead; import com.perforce.p4java.exception.ConnectionException; import com.perforce.p4java.exception.MessageGenericCode; import com.perforce.p4java.exception.MessageSeverityCode; import com.perforce.p4java.impl.generic.client.ClientLineEnding; import com.perforce.p4java.impl.generic.sys.ISystemFileCommandsHelper; import com.perforce.p4java.impl.mapbased.rpc.CommandEnv; import com.perforce.p4java.impl.mapbased.rpc.CommandEnv.RpcHandler; import com.perforce.p4java.impl.mapbased.rpc.connection.RpcConnection; import com.perforce.p4java.impl.mapbased.rpc.func.RpcFunctionMapKey; import com.perforce.p4java.impl.mapbased.rpc.func.client.ClientMessage.ClientMessageId; import com.perforce.p4java.impl.mapbased.rpc.func.helper.MD5Digester; import com.perforce.p4java.impl.mapbased.rpc.msg.RpcMessage; import com.perforce.p4java.impl.mapbased.rpc.packet.RpcPacket; import com.perforce.p4java.impl.mapbased.rpc.packet.RpcPacketDispatcher.RpcPacketDispatcherResult; import com.perforce.p4java.impl.mapbased.rpc.sys.RpcInputStream; import com.perforce.p4java.impl.mapbased.rpc.sys.RpcLineEndFilterInputStream; import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SysFileHelperBridge; /** * Implements some specialised file methods for sending file * data back to the Perforce server. May be refactored into * other classes later when the scope of the methods here * is clearer -- HR. * * */ public class ClientSendFile { /** * TRACE_PREFIX */ public static final String TRACE_PREFIX = "ClientSendFile"; /** * DEFAULT_SENDBUF_SIZE */ public static final int DEFAULT_SENDBUF_SIZE = 1024; // in bytes @SuppressWarnings("unused") // used for debugging private Properties props = null; private ISystemFileCommandsHelper fileCommands = SysFileHelperBridge.getSysFileCommands(); // Keeping track of file data progress info private String filePath = null; private long fileSize = 0; private long currentSize = 0; /** * Create a new rpc file sender * * @param props */ protected ClientSendFile(Properties props) { this.props = props; } private long sendRaw(InputStream stream, RpcConnection connection, String handle, String write, MD5Digester digester, CommandEnv cmdEnv) throws ConnectionException, IOException { long fileLength = 0; Map<String, Object> sendMap = new HashMap<String, Object>(); byte[] bytes = new byte[DEFAULT_SENDBUF_SIZE]; int bytesRead = 0; while ((bytesRead = stream.read(bytes)) > 0) { byte[] readBytes = new byte[bytesRead]; System.arraycopy(bytes, 0, readBytes, 0, bytesRead); fileLength += bytesRead; sendMap.clear(); sendMap.put(RpcFunctionMapKey.DATA, readBytes); sendMap.put(RpcFunctionMapKey.HANDLE, handle); RpcPacket sendPacket = RpcPacket.constructRpcPacket(write, sendMap, null); connection.putRpcPacket(sendPacket); digester.update(readBytes); // Send back the data bytes written (accumulated) // This is for the progress indicator if (cmdEnv.getProtocolSpecs().isEnableProgress()) { if (fileSize > 0 && bytesRead > 0) { currentSize += bytesRead; Map<String, Object> dataSizeMap = new HashMap<String, Object>(); dataSizeMap.put("path", filePath); dataSizeMap.put("fileSize", fileSize); dataSizeMap.put("currentSize", currentSize); cmdEnv.handleResult(dataSizeMap); } } } return fileLength; } private ILookahead createLookahead(final InputStream stream, Charset charset) { CharsetConverter newlineConverter = new CharsetConverter( CharsetDefs.DEFAULT, charset, true); ByteBuffer newlineBuffer = newlineConverter.convert(CharBuffer .wrap(new char[] { ClientLineEnding.FST_L_LF_CHAR })); final int lookaheadLength = newlineBuffer.limit(); ILookahead lookahead = new ILookahead() { public byte[] bytesToAdd(char lastDecodedChar) { byte[] add = null; if (lastDecodedChar == ClientLineEnding.FST_L_CR_CHAR) { add = new byte[lookaheadLength]; try { int read = stream.read(add); if (read != add.length) { byte[] realAdd = new byte[read]; System.arraycopy(add, 0, realAdd, 0, read); add = realAdd; } } catch (IOException e) { add = null; } } return add; } }; return lookahead; } private long sendConverted(Charset charset,final InputStream stream, RpcConnection connection, String handle, String write, MD5Digester digester, CommandEnv cmdEnv) throws ConnectionException, IOException { long fileLength = 0; Map<String, Object> sendMap = new HashMap<String, Object>(); byte[] bytes = new byte[DEFAULT_SENDBUF_SIZE]; int bytesRead = 0; ILookahead lookahead = null; if (ClientLineEnding.CONVERT_TEXT) { lookahead = createLookahead(stream, charset); } CharsetConverter converter = new CharsetConverter(charset, CharsetDefs.UTF8); while ((bytesRead = stream.read(bytes)) > 0) { ByteBuffer inBuffer = ByteBuffer.wrap(bytes, 0, bytesRead); ByteBuffer converted = converter.convert(inBuffer, lookahead); byte[] sendBytes = converted.array(); bytesRead = converted.limit(); int start = converted.position(); //Send packet if at least one byte of data was converted if (bytesRead > 0) { // Convert line endings if necessary if (ClientLineEnding.CONVERT_TEXT) { // Create intermediate stream for converting line ending // based on the last byte buffer of converted data ByteArrayInputStream byteStream = new ByteArrayInputStream( sendBytes, 0, bytesRead); RpcLineEndFilterInputStream lineEndStream = new RpcLineEndFilterInputStream( byteStream, null); bytesRead = lineEndStream.read(sendBytes, 0, bytesRead); lineEndStream.close(); } byte[] dataBytes = new byte[bytesRead]; System.arraycopy(sendBytes, start, dataBytes, 0, bytesRead); fileLength += bytesRead; sendMap.clear(); sendMap.put(RpcFunctionMapKey.DATA, dataBytes); sendMap.put(RpcFunctionMapKey.HANDLE, handle); RpcPacket sendPacket = RpcPacket.constructRpcPacket( write, sendMap, null); connection.putRpcPacket(sendPacket); digester.update(dataBytes); // Send back the data bytes written (accumulated) // This is for the progress indicator if (cmdEnv.getProtocolSpecs().isEnableProgress()) { if (fileSize > 0 && bytesRead > 0) { currentSize += bytesRead; Map<String, Object> dataSizeMap = new HashMap<String, Object>(); dataSizeMap.put("path", filePath); dataSizeMap.put("fileSize", fileSize); dataSizeMap.put("currentSize", currentSize); cmdEnv.handleResult(dataSizeMap); } } } } return fileLength; } /** * Send a file's contents back to the Perforce server. Notably * assumes a late model server... * * FIXME: digest stuff is not yet implemented -- HR. * FIXME: error handling is typically nearly non-existent -- HR. * FIXME: charset issues -- HR. * FIXME: rework to use proper flush protocol implementation -- HR. * * @param rpcConnection * @param cmdEnv * @param resultsMap * @return - result * @throws ConnectionException */ protected RpcPacketDispatcherResult sendFile(RpcConnection rpcConnection, CommandEnv cmdEnv, Map<String, Object> resultsMap) throws ConnectionException { cmdEnv.newHandler(); String clientPath = (String) resultsMap.get(RpcFunctionMapKey.PATH); String type = (String) resultsMap.get(RpcFunctionMapKey.TYPE); String perms = (String) resultsMap.get(RpcFunctionMapKey.PERMS); String handle = (String) resultsMap.get(RpcFunctionMapKey.HANDLE); String open = (String) resultsMap.get(RpcFunctionMapKey.OPEN); String write = (String) resultsMap.get(RpcFunctionMapKey.WRITE); String confirm = (String) resultsMap.get(RpcFunctionMapKey.CONFIRM); String decline = (String) resultsMap.get(RpcFunctionMapKey.DECLINE); @SuppressWarnings("unused") // used for debugging String serverDigest = (String) resultsMap.get(RpcFunctionMapKey.SERVERDIGEST); @SuppressWarnings("unused") // used for debugging String revertUnchanged = (String) resultsMap.get(RpcFunctionMapKey.REVERTUNCHANGED); String reopen = (String) resultsMap.get(RpcFunctionMapKey.REOPEN); RpcPerforceFileType fileType = RpcPerforceFileType.decodeFromServerString(type); RpcHandler handler = cmdEnv.getHandler(handle); if (handler == null) { handler = cmdEnv.new RpcHandler(handle, false, null); cmdEnv.addHandler(handler); } RpcPerforceFile file = null; InputStream inStream = null; MD5Digester digester = null; long fileLength = 0; // Oh for a Java unsigned type... long modTime = 0; try { file = new RpcPerforceFile(clientPath, fileType); modTime = file.lastModified(); // Symlink to an non-existing target will reuturn '0' from the // 'File.lastModified()' method. Also, this method only returns the // last modified time of it's target. So, we must use the new // 'SymbolicLinkHelper.getLastModifiedTime()' method to get the last // modified time of the symlink (not it's target). if (fileType == RpcPerforceFileType.FST_SYMLINK) { // Java returns '0' if the file does not exist or if an I/O error occurs // Use the symbolic link helper to get the last modified time. modTime = SymbolicLinkHelper.getLastModifiedTime(clientPath); // If all else fails, use the current time milli. if (modTime == 0) { modTime = System.currentTimeMillis(); } } // Initialize file data info for progress indicator filePath = clientPath != null ? clientPath : null; fileSize = file.length(); currentSize = 0; if (!handler.isError()) { // If everything's OK, send an open (lbr-Open) packet with passed-through // map entries, then write the file to the server using looped // lbr-WriteFile packets, then send a dm-SubmitFile. Map<String,Object> respMap = new HashMap<String, Object>(); for (Map.Entry<String, Object> entry : resultsMap.entrySet()) { if ((entry.getKey() != null) && !entry.getKey().equalsIgnoreCase(RpcFunctionMapKey.FUNCTION)) { respMap.put(entry.getKey(), entry.getValue()); } } RpcPacket respPacket = RpcPacket.constructRpcPacket( open, respMap, null); rpcConnection.putRpcPacket(respPacket); // Note: as per job 036870, we need to defer the existence testing for the // file to here (we could have detected its non-existence on the first line // of the method) as we always need to send an lbr-Open function back to // the server to keep things straight. This is apparently a vestige of the // early protocol and state machine design -- HR. if (!file.exists() && fileType != RpcPerforceFileType.FST_SYMLINK) { handler.setError(true); cmdEnv.handleResult( new RpcMessage( ClientMessageId.OS_FILE_READ_ERROR, MessageSeverityCode.E_INFO, MessageGenericCode.EV_CLIENT, new String[] {"open for read", clientPath + ": No such file or directory"} ).toMap() ); } else { // Now to send the file contents: String symbolicLinkTarget = null; // Check if the file is a symlink type if (fileType == RpcPerforceFileType.FST_SYMLINK) { // Read the target of the symlink if (SymbolicLinkHelper.isSymbolicLinkCapable()) { symbolicLinkTarget = SymbolicLinkHelper.readSymbolicLink(clientPath); } if (symbolicLinkTarget == null) { handler.setError(true); cmdEnv.handleResult( new RpcMessage( ClientMessageId.FILE_SEND_ERROR, MessageSeverityCode.E_FAILED, MessageGenericCode.EV_CLIENT, new String[] {"symlink", clientPath} ).toMap() ); return RpcPacketDispatcherResult.CONTINUE_LOOP; } } digester = new MD5Digester(); Charset charset = null; if (RpcPerforceFileType.FST_UTF16 == fileType) { charset = CharsetDefs.UTF16; } else if (RpcPerforceFileType.FST_UNICODE == fileType) { charset = rpcConnection.getClientCharset(); } if (!rpcConnection.isUnicodeServer() || charset == null || charset.equals(CharsetDefs.UTF8)) { inStream = symbolicLinkTarget != null ? new ByteArrayInputStream( symbolicLinkTarget.getBytes()) : new RpcInputStream(file); fileLength = sendRaw(inStream, rpcConnection, handle, write, digester, cmdEnv); } else { inStream = symbolicLinkTarget != null ? new ByteArrayInputStream( symbolicLinkTarget.getBytes()) : new FileInputStream(file); fileLength = sendConverted(charset, inStream, rpcConnection, handle, write, digester, cmdEnv); } // All sent; now try to set the perms properly if appropriate: if (!handler.isError() && (perms != null) && (reopen == null)) { if (perms.equalsIgnoreCase(ClientSystemFileCommands.PERMS_RW)) { fileCommands.setWritable(clientPath, true); } else { fileCommands.setWritable(clientPath, false); } } } } // Now try to send a finalise message: String finalise = (handler.isError() ? decline : confirm); Map<String, Object> finaliseMap = new HashMap<String,Object>(); for (Map.Entry<String, Object> entry : resultsMap.entrySet()) { if ((entry.getKey() != null) && !entry.getKey().equalsIgnoreCase(RpcFunctionMapKey.FUNCTION)) { finaliseMap.put(entry.getKey(), entry.getValue()); } } if (digester != null) { finaliseMap.put(RpcFunctionMapKey.DIGEST, digester.digestAs32ByteHex()); finaliseMap.put(RpcFunctionMapKey.FILESIZE, "" + fileLength); if (modTime != 0) { finaliseMap.put(RpcFunctionMapKey.TIME, "" + (modTime / 1000)); } } RpcPacket finalisePacket = RpcPacket.constructRpcPacket( finalise, finaliseMap, null); rpcConnection.putRpcPacket(finalisePacket); // We now clear any errors, again as a vestige of earlier protocol // design decisions; we'd normally let the error be ack'd back to the // server as a client-side abort, but we need to let the server think // everything went fine here and let it send its own error message(s) -- HR. handler.setError(false); // Clear data file info for progress indicator filePath = null; fileSize = 0; currentSize = 0; } catch (Exception exc) { Log.exception(exc); handler.setError(true); cmdEnv.handleResult( new RpcMessage( ClientMessageId.FILE_SEND_ERROR, MessageSeverityCode.E_FAILED, MessageGenericCode.EV_CLIENT, new String[] {clientPath, exc.getLocalizedMessage()} ).toMap() ); } finally { try { if (inStream != null) { inStream.close(); } } catch (IOException exc) { Log.warn("Unexpected exception on send file close; file: '" + clientPath + "'; message: "+ exc.getLocalizedMessage()); } } return RpcPacketDispatcherResult.CONTINUE_LOOP; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 19903 | stuartrowe |
Branching //guest/perforce_software/p4java/... to //guest/stuartrowe/p4java/... |
||
//guest/perforce_software/p4java/r14.1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/func/client/ClientSendFile.java | |||||
#1 | 12541 | Matt Attaway | Initial add of the 14.1 p4java source code |