FileDocCategorySizeDatePackage
Handler.javaAPI DocJMF 2.1.1e20278Mon May 12 12:21:00 BST 2003com.sun.media.datasink.file

Handler

public class Handler extends BasicDataSink implements Seekable, Runnable, SourceTransferHandler, RandomAccess, Syncable

Fields Summary
private static final boolean
DEBUG
protected static final int
NOT_INITIALIZED
protected static final int
OPENED
protected static final int
STARTED
protected static final int
CLOSED
protected int
state
protected DataSource
source
protected SourceStream[]
streams
protected SourceStream
stream
protected boolean
push
protected boolean
errorEncountered
protected String
errorReason
protected Control[]
controls
protected File
file
protected File
tempFile
protected RandomAccessFile
raFile
protected RandomAccessFile
qtStrRaFile
protected boolean
fileClosed
protected FileDescriptor
fileDescriptor
protected MediaLocator
locator
protected String
contentType
protected int
fileSize
protected int
filePointer
protected int
bytesWritten
protected static final int
BUFFER_LEN
protected boolean
syncEnabled
protected byte[]
buffer1
protected byte[]
buffer2
protected boolean
buffer1Pending
protected long
buffer1PendingLocation
protected int
buffer1Length
protected boolean
buffer2Pending
protected long
buffer2PendingLocation
protected int
buffer2Length
protected long
nextLocation
protected Thread
writeThread
private Integer
bufferLock
private boolean
receivedEOS
private static JMFSecurity
jmfSecurity
private static boolean
securityPrivelege
private Method[]
m
private Class[]
cl
private Object[]
args
public int
WRITE_CHUNK_SIZE
private boolean
streamingEnabled
private boolean
errorCreatingStreamingFile
long
lastSyncTime
Constructors Summary
Methods Summary
public voidclose()

	close(null); // No Error;
    
protected final voidclose(java.lang.String reason)

	synchronized(this) {
	    if ( state == CLOSED )
		return;
	    setState(CLOSED);
	}

	if (push) {
	    for (int i = 0; i < streams.length; i++) {
		((PushSourceStream)streams[i]).setTransferHandler(null);
	    }
	}

	if (reason != null) {
	    errorEncountered = true;
	    sendDataSinkErrorEvent(reason);
	    // Wake up the write thread
	    synchronized (bufferLock) {
		bufferLock.notifyAll();
	    }
	}

	// Wait for all buffers to be written
	/*
	synchronized (bufferLock) {
	    while (reason == null && (buffer2Pending || buffer1Pending)) {
		try {
		    bufferLock.wait(250);
		} catch (InterruptedException ie) {
		}
	    }
	}
	*/
	
	try {
	    source.stop();
	} catch (IOException e) {
	    System.err.println("IOException when stopping source " + e);
	}
	


	try {
	    if (raFile != null) {
		raFile.close();
	    }

	    if (streamingEnabled) {
		if (qtStrRaFile != null) {
		    qtStrRaFile.close();
		}
	    }

	    // 	Disconnect the data source 
	    if (source != null)
		source.disconnect();
	    ////////////////////////////////////////////////////////////


	    if (streamingEnabled && (tempFile != null) ) {
		// Delete the temp file if no errors creating streamable file.
		// If errors creating streamable file, delete streamable file.
		if (!errorCreatingStreamingFile) {
		    boolean status = deleteFile(tempFile);
		} else {
		    boolean status = deleteFile(file);
		}
	    }
// 	    fileClosed = true;
// 	    sendEndofStreamEvent();
	} catch (IOException e) {
	    System.out.println("close: " + e);
	}
	
	raFile = null; // Should be done after setting the state to CLOSED
	qtStrRaFile = null;

	removeAllListeners();
    
private booleandeleteFile(java.io.File file)


	boolean fileDeleted=false;
	try {
	    if ( /*securityPrivelege &&*/ (jmfSecurity != null) ) {
		try {
		    if (jmfSecurity.getName().startsWith("jmf-security")) {
			jmfSecurity.requestPermission(m, cl, args, JMFSecurity.DELETE_FILE);
			m[0].invoke(cl[0], args[0]);
		    } else if (jmfSecurity.getName().startsWith("internet")) {
			PolicyEngine.checkPermission(PermissionID.FILEIO);
			PolicyEngine.assertPermission(PermissionID.FILEIO);
		    }
		} catch (Exception e) {
		    if (JMFSecurityManager.DEBUG) {
			System.err.println("Unable to get DELETE_FILE " +
					   " privilege  " + e);
		    }
		    securityPrivelege = false;
		    // TODO: Do the right thing if permissions cannot be obtained.
		    // User should be notified via an event, if applicable
		}
	    }
 	    if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) {
		Constructor cons = jdk12DeleteFileAction.cons;
		Boolean success;
		success = (Boolean) jdk12.doPrivM.invoke(
					 jdk12.ac,
					 new Object[] {
			        cons.newInstance(
					 new Object[] {
				           file
                                         })
				});
		fileDeleted = success.booleanValue();
	    } else {
		fileDeleted = file.delete();
	    }
	} catch (Throwable e) {
	}
        return fileDeleted;
    
public longdoSeek(long where)

	if (raFile != null) {
	    try {
		raFile.seek(where);
		filePointer = (int) where;
		return where;
	    } catch (IOException ioe) {
		close("Error in seek: " + ioe);
	    }
	}
	return -1;
    
public longdoTell()

	if (raFile != null) {
	    try {
		return raFile.getFilePointer();
	    } catch (IOException ioe) {
		close("Error in tell: " + ioe);
	    }
	}
	return -1;
    
public java.lang.StringgetContentType()

	return contentType;
    
public java.lang.ObjectgetControl(java.lang.String controlName)

	return null;
    
public java.lang.Object[]getControls()

	if (controls == null) {
	    controls = new Control[0];
	}
	return controls;
    
public javax.media.MediaLocatorgetOutputLocator()

	return locator;
    
public booleanisRandomAccess()

	return true;
    
public voidopen()

	try {
	    if ( state == NOT_INITIALIZED ) {

		if (locator != null) {
		    String pathName = locator.getRemainder(); // getFileName(locator);
		    // Strip off excess /'s 
		    while (pathName.charAt(0) == '/" &&
			   (pathName.charAt(1) == '/" || pathName.charAt(2) == ':")) {
			pathName = pathName.substring(1);
		    }
		    String fileSeparator = System.getProperty("file.separator");
		    if (fileSeparator.equals("\\")) {
			pathName = pathName.replace('/", '\\");
		    }
		
		    com.sun.media.JMFSecurityManager.checkFileSave();
		    if ( securityPrivelege && (jmfSecurity != null) ) {
			String permission = null;
			try {

			    if (jmfSecurity.getName().startsWith("jmf-security")) {
				permission = "read file";
				jmfSecurity.requestPermission(m, cl, args, JMFSecurity.READ_FILE);
				m[0].invoke(cl[0], args[0]);
			
				permission = "write file";
				jmfSecurity.requestPermission(m, cl, args, JMFSecurity.WRITE_FILE);
				m[0].invoke(cl[0], args[0]);
			    } else if (jmfSecurity.getName().startsWith("internet")) {
				PolicyEngine.checkPermission(PermissionID.FILEIO);
				PolicyEngine.assertPermission(PermissionID.FILEIO);
			    }
			} catch (Exception e) {
			    securityPrivelege = false;
			    if (push)
				((PushSourceStream)stream).setTransferHandler(null);
			    throw new SecurityException(e.getMessage());
			} catch (Error e) {
			    securityPrivelege = false;
			    if (push)
				((PushSourceStream)stream).setTransferHandler(null);
			    throw new SecurityException(e.getMessage());
			}
		    }
		    if (!securityPrivelege) {
			if (push)
			    ((PushSourceStream)stream).setTransferHandler(null);
			throw new IOException("Datasink: Unable to get security privileges for file writing");
		    }

		    // In jdk1.2, RandomAccessFile has a useful method called
		    // setLength() which can be used to truncate an existing
		    // file. But this won't work on jdk1.1. So we have to
		    // delete a file if it exists

		    // On Windows, you cannot delete a file if some process
		    // is using it.

		    file = new File(pathName);
		    if (file.exists()) {
			if (!deleteFile(file)) {
			    System.err.println("datasink open: Existing file "
					       + pathName +
					       " cannot be deleted. Check if " +
					       "some other process is using " +
					       " this file");
			    if (push)
				((PushSourceStream)stream).setTransferHandler(null);
			    throw new IOException("Existing file " + pathName +
						  " cannot be deleted");
			}
		    }

		    String parent = file.getParent();
		    if (parent != null) {
			new File(parent).mkdirs();
		    }
		    try {
			if (!streamingEnabled) {
			    raFile = new RandomAccessFile(file, "rw");
			    fileDescriptor = raFile.getFD();
			} else {
			    String fileqt;
			    int index;
			    if ( (index = pathName.lastIndexOf(".")) > 0 ) {
				// TODO: maybe $$$ add random string
				fileqt = pathName.substring(0, index) + ".nonstreamable" +
				    pathName.substring(index, pathName.length());
			    } else {
				// TODO: maybe $$$ add random string
				// TODO: Don't assume .mov if the file doesn't have
				// and extension. Try to use content type if possible
				// to guess the extension.
				// However, extensions will be there as JMF provides it
				// even if user doesn't.
				fileqt =  file + ".nonstreamable.mov"; 
			    }
			    tempFile = new File(fileqt);

			    raFile = new RandomAccessFile(tempFile, "rw");
			    fileDescriptor = raFile.getFD();
			    qtStrRaFile = new RandomAccessFile(file, "rw");
			}
		    } catch (IOException e) {
			// Catch the exception for debugging purpose and
			// throw it again
			System.err.println("datasink open: IOException when creating RandomAccessFile "
					   + pathName + " : " + e);
			if (push)
			    ((PushSourceStream)stream).setTransferHandler(null);
			throw e;
		    }

		    setState(OPENED);
		}
	    }
	} finally {
	    if ( (state == NOT_INITIALIZED) && (stream != null) ) {
		((PushSourceStream)stream).setTransferHandler(null);
	    }
	}
    
public voidrun()

	while (!(state == CLOSED || errorEncountered)) {
	    synchronized (bufferLock) {
		// Wait for some data or error
		while (!buffer1Pending && !buffer2Pending &&
		       !errorEncountered && state != CLOSED && !receivedEOS) {
		    if (DEBUG) System.err.println("Waiting for filled buffer");
		    try {
			bufferLock.wait(500);
		    } catch (InterruptedException ie) {
		    }
		    if (DEBUG) System.err.println("Consumer notified");
		}
	    }
	    // Something's pending
	    if (buffer2Pending) {
		if (DEBUG) System.err.println("Writing Buffer2");
		// write that first
		write(buffer2, buffer2PendingLocation, buffer2Length);
		if (DEBUG) System.err.println("Done writing Buffer2");
		buffer2Pending = false;
	    }

	    synchronized (bufferLock) {
		if (buffer1Pending) {
		    byte [] tempBuffer = buffer2;
		    buffer2 = buffer1;
		    buffer2Pending = true;
		    buffer2PendingLocation = buffer1PendingLocation;
		    buffer2Length = buffer1Length;
		    buffer1Pending = false;
		    buffer1 = tempBuffer;
		    if (DEBUG) System.err.println("Notifying producer");
		    bufferLock.notifyAll();
		} else {
		    if (receivedEOS)
			break;
		}
	    }
	}
	if (receivedEOS) {
	    if (DEBUG) System.err.println("Sending EOS: streamingEnabled is " + streamingEnabled);
	    // Close the file and flag it
	    if (raFile != null) {
		if (!streamingEnabled) {
		    try {
			raFile.close();
		    } catch (IOException ioe) {
		    }
		    raFile = null;
		}
		fileClosed = true;
	    }
	    if (!streamingEnabled) {
		sendEndofStreamEvent();
	    }
	}
	if (errorEncountered && state != CLOSED) {
	    close(errorReason);
	}
    
public synchronized longseek(long where)

	nextLocation = where;
	return where;
    
public voidsetEnabled(boolean b)

	streamingEnabled = b;
    
public voidsetOutputLocator(javax.media.MediaLocator output)
Set the output MediaLocator. This method should only be called once; an error is thrown if the locator has already been set.

param
output MediaLocator that describes where the output goes.

	locator = output;
    
public voidsetSource(javax.media.protocol.DataSource ds)

     
	try {
	    jmfSecurity = JMFSecurityManager.getJMFSecurity();
	    securityPrivelege = true;
	} catch (SecurityException e) {
	}
    
	
	if (!(ds instanceof PushDataSource) &&
	    !(ds instanceof PullDataSource)) {

	    throw new IncompatibleSourceException("Incompatible datasource");
	}
	source = ds;
	
	if (source instanceof PushDataSource) {
	    push = true;
	    try {
		((PushDataSource) source).connect();
	    } catch (IOException ioe) {
	    }
	    streams = ((PushDataSource) source).getStreams();
	} else {
	    push = false;
	    try {
		((PullDataSource) source).connect();
	    } catch (IOException ioe) {
	    }
	    streams = ((PullDataSource) source).getStreams();
	}
	
	if (streams == null || streams.length != 1)
	    throw new IncompatibleSourceException("DataSource should have 1 stream");
	stream = streams[0];
	
	contentType = source.getContentType();
	if (push)
	    ((PushSourceStream)stream).setTransferHandler(this);
    
protected voidsetState(int state)

	synchronized(this) {
	    this.state = state;
	}
    
public voidsetSyncEnabled()

	syncEnabled = true;
    
public voidstart()

	if (state == OPENED) {
	    if (source != null)
		source.start();
	    if (writeThread == null) {
		writeThread = new Thread(this);
		writeThread.start();
	    }
	    setState(STARTED);
	}
    
public voidstop()
Stop the data-transfer. If the source has not been connected and started, stop does nothing.

	if (state == STARTED) {
	    if (source != null)
		source.stop();
	    setState(OPENED);
	}
    
public longtell()

	return nextLocation;
    
public synchronized voidtransferData(javax.media.protocol.PushSourceStream pss)

	int totalRead = 0;
	int spaceAvailable = BUFFER_LEN;
	int bytesRead = 0;
	if (errorEncountered)
	    return;

	if (buffer1Pending) {
	    synchronized (bufferLock) {
		while (buffer1Pending) {
		    if (DEBUG) System.err.println("Waiting for free buffer");
		    try {
			bufferLock.wait();
		    } catch (InterruptedException ie) {
		    }
		}
	    }
	    if (DEBUG) System.err.println("Got free buffer");
	}
	
	//	System.err.println("In transferData()");
	while (spaceAvailable > 0) {
	    try {
		bytesRead = pss.read(buffer1, totalRead, spaceAvailable);
		//System.err.println("bytesRead = " + bytesRead);
		if (bytesRead > 16 * 1024 && WRITE_CHUNK_SIZE < 32 * 1024) {
		    if (  bytesRead > 64 * 1024 &&
			  WRITE_CHUNK_SIZE < 128 * 1024  )
			WRITE_CHUNK_SIZE = 128 * 1024;
		    else if (  bytesRead > 32 * 1024 &&
			       WRITE_CHUNK_SIZE < 64 * 1024  )
			WRITE_CHUNK_SIZE = 64 * 1024;
		    else if (  WRITE_CHUNK_SIZE < 32 * 1024  )
			WRITE_CHUNK_SIZE = 32 * 1024;
		    //System.err.println("Increasing buffer to " + WRITE_CHUNK_SIZE);

		}
	    } catch (IOException ioe) {
		// What to do here?
	    }
	    if (bytesRead <= 0) {
		break;
	    }
	    totalRead += bytesRead;
	    spaceAvailable -= bytesRead;
	}

	if (totalRead > 0) {
	    synchronized (bufferLock) {
		buffer1Pending = true;
		buffer1PendingLocation = nextLocation;
		buffer1Length = totalRead;
		nextLocation = -1; // assume next write is contiguous unless seeked
		// Notify availability to write thread
		if (DEBUG) System.err.println("Notifying consumer");
		bufferLock.notifyAll();
	    }
	}
	// Send EOS if necessary
	if (bytesRead == -1) {
	    if (DEBUG) System.err.println("Got EOS");
	    receivedEOS = true;
	    // Wait until file is closed. This makes the Processor's close
	    // call to force the data sink to close the file, just in case
	    // the user doesn't remember to close the datasink before exiting.
	    while (!fileClosed && !errorEncountered && !(state == CLOSED)) {
		try {
		    Thread.sleep(50);
		} catch (InterruptedException ie) {
		}
	    }
	}
    
private voidwrite(byte[] buffer, long location, int length)


             
	int offset, toWrite;
	try {
	    if (location != -1)
		doSeek(location);
	    offset = 0;
	    while (length > 0) {
		toWrite = WRITE_CHUNK_SIZE;
		if (length < toWrite)
		    toWrite = length;
		raFile.write(buffer, offset, toWrite);
		bytesWritten += toWrite;

		// Sync/Flush after a few write so that the
		// file writing is smooth. Improves capture smoothness

		/*
		if (fileDescriptor != null) {
		    // Sync'ing the file system at every 1 sec interval.
		    if (lastSyncTime < 0)
			lastSyncTime = System.currentTimeMillis();
		    else {
			long ts = System.currentTimeMillis();
			if (ts - lastSyncTime > 1000L) {
			    fileDescriptor.sync();
			    //System.err.println("sync: byte written: " + bytesWritten);
			    bytesWritten = 0;
			    lastSyncTime = ts;
			}
		    }
		}
		*/
		
		if (  fileDescriptor != null && syncEnabled &&
		      bytesWritten >= WRITE_CHUNK_SIZE) {
		    bytesWritten -= WRITE_CHUNK_SIZE;
		    fileDescriptor.sync();
		}


		filePointer += toWrite;
		length -= toWrite;
		offset += toWrite;
		if (filePointer > fileSize)
		    fileSize = filePointer;
		Thread.yield();
	    }
	} catch (IOException ioe) {
	    errorEncountered = true;
	    errorReason = ioe.toString();
	}
    
public booleanwrite(long inOffset, int numBytes)

	try {
	    if ( (inOffset >= 0) && (numBytes > 0 ) ) {
		int remaining = numBytes;
		int bytesToRead;
		raFile.seek(inOffset);
		
		while (remaining > 0) {
		    bytesToRead = (remaining > BUFFER_LEN) ? BUFFER_LEN : remaining;
		    raFile.read(buffer1, 0, bytesToRead); //$$ CAST
		    qtStrRaFile.write(buffer1, 0, bytesToRead); //$$ CAST
		    remaining -= bytesToRead;
		}
	    } else if ( (inOffset < 0) && (numBytes > 0) ) {
		qtStrRaFile.seek(0);
		qtStrRaFile.seek(numBytes-1);
		qtStrRaFile.writeByte(0);
		qtStrRaFile.seek(0);
	    } else {
		sendEndofStreamEvent();
	    }
	} catch (Exception e) {
	    errorCreatingStreamingFile = true;
	    System.err.println("Exception when creating streamable version of media file: " +
			       e.getMessage());
	    return false;
	}
	return true;