/**
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2016 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.commons.file.archive;
import com.mucommander.commons.file.FileOperation;
import com.mucommander.commons.file.FilePermissions;
import com.mucommander.commons.file.FileURL;
import com.mucommander.commons.file.PermissionBits;
import com.mucommander.commons.file.SimpleFilePermissions;
import com.mucommander.commons.file.UnsupportedFileOperationException;
import com.mucommander.commons.io.ByteCounter;
import com.mucommander.commons.io.CounterOutputStream;
import javax.swing.tree.DefaultMutableTreeNode;
import java.io.IOException;
import java.io.OutputStream;
/**
* Represents a file entry inside a read-write archive. Read-write archives are characterized by
* {@link AbstractArchiveFile#isWritable()} returning <code>false</code>.
*
* @see AbstractArchiveFile
* @see ROArchiveEntryFile
* @author Maxence Bernard
*/
public class RWArchiveEntryFile extends AbstractArchiveEntryFile {
protected RWArchiveEntryFile(FileURL url, AbstractArchiveFile archiveFile, ArchiveEntry entry) {
super(url, archiveFile, entry);
}
/**
* Updates this entry's attributes in the archive and returns <code>true</code> if the update went OK.
*
* @return <code>true</code> if the attributes were successfully updated in the archive.
*/
private boolean updateEntryAttributes() {
try {
((AbstractRWArchiveFile)archiveFile).updateEntry(entry);
return true;
}
catch(IOException e) {
return false;
}
}
/////////////////////////////////
// AbstractFile implementation //
/////////////////////////////////
/**
* @throws IOException if the entry does not exist within the archive
*/
@Override
public void changeDate(long lastModified) throws IOException, UnsupportedFileOperationException {
if(!entry.exists())
throw new IOException();
long oldDate = entry.getDate();
entry.setDate(lastModified);
boolean success = updateEntryAttributes();
if(!success) {
// restore old date if attributes could not be updated
entry.setDate(oldDate);
throw new IOException();
}
}
/**
* Always returns {@link PermissionBits#FULL_PERMISSION_BITS}.
*/
@Override
public PermissionBits getChangeablePermissions() {
// Todo: some writable archive implementations may not have full 'set' permissions support, or even no notion of permissions
return PermissionBits.FULL_PERMISSION_BITS;
}
/**
* Deletes this entry from the associated <code>AbstractArchiveFile</code>.
* <p>
* Throws a {@link UnsupportedFileOperationException} if if the underlying file does not support the required
* read and write {@link FileOperation file operations}. Throws an <code>IOException</code> in any of the following
* cases:
* <ul>
* <li>if this entry does not exist in the archive</li>
* <li>if this entry is a non-empty directory</li>
* <li>if an I/O error occurred</li>
* </ul>
* </p>
*
* @throws IOException in any of the cases listed above.
* @throws UnsupportedFileOperationException if the underlying archive file does not support the required read and
* write {@link FileOperation file operations}.
*/
@Override
public void delete() throws IOException, UnsupportedFileOperationException {
if(!entry.exists())
throw new IOException();
AbstractRWArchiveFile rwArchiveFile = (AbstractRWArchiveFile)archiveFile;
// Throw an IOException if this entry is a non-empty directory
if(isDirectory()) {
ArchiveEntryTree tree = rwArchiveFile.getArchiveEntryTree();
if(tree!=null) {
DefaultMutableTreeNode node = tree.findEntryNode(entry.getPath());
if(node!=null && node.getChildCount()>0)
throw new IOException();
}
}
// Delete the entry in the archive file
rwArchiveFile.deleteEntry(entry);
// Non-existing entries are considered as zero-length regular files
entry.setDirectory(false);
entry.setSize(0);
entry.setExists(false);
}
/**
* Creates this entry as a directory in the associated <code>AbstractArchiveFile</code>.
* <p>
* Throws a {@link UnsupportedFileOperationException} if if the underlying file does not support the required
* read and write {@link FileOperation file operations}. Throws an <code>IOException</code> if this entry
* already exists in the archive or if an I/O error occurred.
* </p>
*
* @throws IOException if this entry already exists in the archive or if an I/O error occurred.
* @throws UnsupportedFileOperationException if the underlying archive file does not support the required read and
* write {@link FileOperation file operations}.
*/
@Override
public void mkdir() throws IOException, UnsupportedFileOperationException {
if(entry.exists())
throw new IOException();
AbstractRWArchiveFile rwArchivefile = (AbstractRWArchiveFile)archiveFile;
// Update the ArchiveEntry
entry.setDirectory(true);
entry.setDate(System.currentTimeMillis());
entry.setSize(0);
// Add the entry to the archive file
rwArchivefile.addEntry(entry);
// The entry now exists
entry.setExists(true);
}
/**
* Returns an <code>OutputStream</code> that allows to write this entry's contents.
* <p>
* This method will create this entry as a regular file in the archive if it doesn't already exist, or replace
* it if it already does.
* </p>
*
* @throws IOException if an I/O error occurred
* @throws UnsupportedFileOperationException if the underlying archive file does not support the required read and
* write {@link FileOperation file operations}.
*/
@Override
public OutputStream getOutputStream() throws IOException, UnsupportedFileOperationException {
if(entry.exists()) {
try {
delete();
}
catch(IOException e) {
// Go ahead and try to add the file anyway
}
}
// Update the ArchiveEntry's size as data gets written to the OutputStream
OutputStream out = new CounterOutputStream(((AbstractRWArchiveFile)archiveFile).addEntry(entry),
new ByteCounter() {
@Override
public synchronized void add(long nbBytes) {
entry.setSize(entry.getSize()+nbBytes);
entry.setDate(System.currentTimeMillis());
}
});
entry.setExists(true);
return out;
}
@Override
public void changePermissions(int permissions) throws IOException {
if(!entry.exists())
throw new IOException();
FilePermissions oldPermissions = entry.getPermissions();
FilePermissions newPermissions = new SimpleFilePermissions(permissions, oldPermissions.getMask());
entry.setPermissions(newPermissions);
boolean success = updateEntryAttributes();
if(!success) // restore old permissions if attributes could not be updated
entry.setPermissions(oldPermissions);
if(!success)
throw new IOException();
}
}