/**
* 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;
/**
* SyncedFileAttributes is a FileAttributes implementation which allows attribute values to be automatically
* updated when accessed after a certain amount of time (the 'time to live') since their last update. The update
* is performed by the abstract {@link #updateAttributes()} method and is triggered by any of the attribute getters.
* A typical usage for this class is for remote file systems that need to keep file attributes in sync with a server,
* {@link #updateAttributes()} can retrieve a fresh copy of the attributes on the server.
*
* <p>Attributes can also be manually updated using attribute setters. The {@link #updateExpirationDate()} method allows
* to reset the expiration date and consider the attributes as 'fresh'.</p>
*
* <p>An initial value for the attributes 'time to live' is specified in the constructor and can later be changed using
* {@link #setTtl(long)}. If the 'time to live' is set to -1, attributes are no longer automatically updated, this class
* then simply acts as {@link com.mucommander.commons.file.SimpleFileAttributes}.</p>
*
* @author Maxence Bernard
*/
public abstract class SyncedFileAttributes extends SimpleFileAttributes {
/** The attributes' 'time to live', negative values disable automatic attributes updates */
private long ttl;
/** The attributes' expiration timestamp/date */
private long expirationDate;
/** True when attributes are being updated */
private boolean isUpdating;
/**
* Creates a new SyncedFileAttributes using the specifies 'time to live' value.
*
* @param ttl the attributes' 'time to live', in milliseconds
* @param updateAttributesNow if <code>true</code>, attributes are automatically updated
*/
public SyncedFileAttributes(long ttl, boolean updateAttributesNow) {
setTtl(ttl); // also sets the expiration date
if(updateAttributesNow)
checkForExpiration(true); // force attributes update
}
/**
* Returns the attributes' 'time to live', i.e. the amount of time since the last update after which attributes will
* be automatically updated when any of the getter method is called.
*
* @return the attributes' 'time to live', in milliseconds
*/
public long getTtl() {
return ttl;
}
/**
* Sets the attributes' 'time to live', , i.e. the amount of time since the last update after which attributes will
* be automatically updated when any of the getter method is called.
* Note that setting the 'time to live' causes the expiration date to be updated with {@link #updateExpirationDate()}.
*
* @param ttl the attributes' 'time to live', in milliseconds
*/
public void setTtl(long ttl) {
this.ttl = ttl;
// update the expiration date
updateExpirationDate();
}
/**
* Returns the attributes' expiration timestamp/date, the date after which attributes values will be automatically
* updated when any of the getter method is called.
*
* @return the attributes' expiration timestamp/date
*/
public long getExpirationDate() {
return expirationDate;
}
/**
* Sets the attributes' expiration timestamp/date, the date after which attributes values will be automatically
* updated when they are accessed using any of the getter methods.
*
* @param expirationDate the attributes expiration timestamp/date
*/
public void setExpirationDate(long expirationDate) {
this.expirationDate = expirationDate;
}
/**
* Updates the attributes' expiration date to 'now' + 'ttl' (as returned by {@link #getTtl()}.
* This method is called after attributes have been automatically updated. It can also be called after attribute
* values have been manually updated using the setter methods.
*/
public void updateExpirationDate() {
setExpirationDate(
ttl<0
?Long.MAX_VALUE
:System.currentTimeMillis()+getTtl());
}
/**
* Returns <code>true</code> if attributes have expired, i.e. the {@link #getExpirationDate()} expiration date} has
* passed, <code>false</code> if attributes are still 'fresh'. This method also returns <code>false</code> if
* automatic attributes' update has been disabled ('time to live' set to a negative value), or if attributes are
* currently being updated.
*
* @return <code>true</code> if attributes have expired
*/
public boolean hasExpired() {
return ttl>=0 // prevents automatic updates if ttl is set to a negative value
&& !isUpdating() // causes getters to return the current value while attributes are being updated
&& System.currentTimeMillis()>expirationDate;
}
/**
* Returns <code>true</code> if attributes are currently being updated.
*
* @return <code>true</code> if attributes are currently being updated
*/
private synchronized boolean isUpdating() {
return isUpdating;
}
/**
* Sets whether attributes are currently being updated.
*
* @param isUpdating <code>true</code> if attributes are currently being updated
*/
private synchronized void setUpdating(boolean isUpdating) {
this.isUpdating = isUpdating;
}
/**
* Checks if the attributes have expired and if they have, calls {@link #updateAttributes()} to refresh their
* values.
*
* @param forceUpdate if true, attributes will systematically be updated, without checking the expiration date
*/
protected void checkForExpiration(boolean forceUpdate) {
if(forceUpdate || hasExpired()) {
// After this method is called, hasExpired() returns false so that implementations of updateAttributes()
// can query attribute getters without entering a loop of death.
setUpdating(true);
// Updates attribute values
updateAttributes();
// Update expiration date after the attribute have actually been updated, note that it may take a while
// for remote file protocols to retrieve attributes.
updateExpirationDate();
// OK we're done
setUpdating(false);
}
}
////////////////////////
// Overridden methods //
////////////////////////
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public String getPath() {
checkForExpiration(false);
return super.getPath();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public boolean exists() {
checkForExpiration(false);
return super.exists();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public long getDate() {
checkForExpiration(false);
return super.getDate();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public long getSize() {
checkForExpiration(false);
return super.getSize();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public boolean isDirectory() {
checkForExpiration(false);
return super.isDirectory();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public FilePermissions getPermissions() {
checkForExpiration(false);
return super.getPermissions();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public String getOwner() {
checkForExpiration(false);
return super.getOwner();
}
/**
* Overridden to trigger attributes update if the expiration date has been reached.
*/
@Override
public String getGroup() {
checkForExpiration(false);
return super.getGroup();
}
//////////////////////
// Abstract methods //
//////////////////////
/**
* Updates the attribute values. This method is automatically called when attributes are expired and one of the
* attribute getters is called. The implementation may choose to update only certain attributes, or skip updates
* under certain conditions.
*/
public abstract void updateAttributes();
}