package railo.commons.io.res.type.ftp;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import railo.commons.io.res.Resource;
import railo.commons.io.res.ResourceProvider;
import railo.commons.io.res.Resources;
import railo.commons.io.res.util.ResourceLockImpl;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.SizeOf;
import railo.commons.lang.StringUtil;
import railo.runtime.net.proxy.Proxy;
import railo.runtime.op.Caster;
import railo.runtime.type.Sizeable;
// TODO check connection timeout
public final class FTPResourceProvider implements ResourceProvider,Sizeable {
private String scheme="ftp";
private final Map clients=new HashMap();
private int clientTimeout=60000;
private int socketTimeout=-1;
private int lockTimeout=20000;
private int cache=20000;
private FTPResourceClientCloser closer=null;
private final ResourceLockImpl lock=new ResourceLockImpl(lockTimeout,true);
private Map arguments;
public ResourceProvider init(String scheme, Map arguments) {
setScheme(scheme);
if(arguments!=null) {
this.arguments=arguments;
// client-timeout
String strTimeout=(String) arguments.get("client-timeout");
if(strTimeout!=null) {
clientTimeout=Caster.toIntValue(strTimeout,clientTimeout);
}
// socket-timeout
strTimeout=(String) arguments.get("socket-timeout");
if(strTimeout!=null) {
socketTimeout=Caster.toIntValue(strTimeout,socketTimeout);
}
// lock-timeout
strTimeout=(String) arguments.get("lock-timeout");
if(strTimeout!=null) {
lockTimeout=Caster.toIntValue(strTimeout,lockTimeout);
}
// cache
String strCache=(String) arguments.get("cache");
if(strCache!=null) {
cache=Caster.toIntValue(strCache,cache);
}
}
lock.setLockTimeout(lockTimeout);
return this;
}
@Override
public Resource getResource(String path) {
path=ResourceUtil.removeScheme(scheme,path);
FTPConnectionData data=new FTPConnectionData();
path=data.load(path);
return new FTPResource(this,data,path);
}
FTPResourceClient getClient(FTPConnectionData data) throws IOException {
FTPResourceClient client=(FTPResourceClient) clients.remove(data.key());
if(client==null) {
client = new FTPResourceClient(data,cache);
if(socketTimeout>0)client.setSoTimeout(socketTimeout);
}
if(!client.isConnected()) {
if(data.hasProxyData()) {
try {
Proxy.start(
data.getProxyserver(),
data.getProxyport(),
data.getProxyuser(),
data.getProxypassword()
);
connect(client,data);
}
finally {
Proxy.end();
}
}
else {
connect(client,data);
}
int replyCode = client.getReplyCode();
if(replyCode>=400)
throw new FTPException(replyCode);
}
startCloser();
return client;
}
private synchronized void startCloser() {
if(closer==null || !closer.isAlive()) {
closer=new FTPResourceClientCloser(this);
closer.start();
}
}
private void connect(FTPResourceClient client, FTPConnectionData data) throws SocketException, IOException {
if(data.port>0)client.connect(data.host,data.port);
else client.connect(data.host);
if(!StringUtil.isEmpty(data.username))client.login(data.username,data.password);
}
public void returnClient(FTPResourceClient client) {
if(client==null)return;
client.touch();
clients.put(client.getFtpConnectionData().key(), client);
}
@Override
public String getScheme() {
return scheme;
}
public void setScheme(String scheme) {
if(!StringUtil.isEmpty(scheme))this.scheme=scheme;
}
@Override
public void setResources(Resources resources) {
//this.resources=resources;
}
@Override
public void lock(Resource res) throws IOException {
lock.lock(res);
}
@Override
public void unlock(Resource res) {
lock.unlock(res);
}
@Override
public void read(Resource res) throws IOException {
lock.read(res);
}
public void clean() {
Object[] keys = clients.keySet().toArray();
FTPResourceClient client;
for(int i=0;i<keys.length;i++) {
client=(FTPResourceClient) clients.get(keys[i]);
if(client.getLastAccess()+clientTimeout<System.currentTimeMillis()) {
//railo.print.ln("disconnect:"+client.getFtpConnectionData().key());
if(client.isConnected()) {
try {
client.disconnect();
}
catch (IOException e) {}
}
clients.remove(client.getFtpConnectionData().key());
}
}
}
class FTPResourceClientCloser extends Thread {
private FTPResourceProvider provider;
public FTPResourceClientCloser(FTPResourceProvider provider) {
this.provider=provider;
}
public void run() {
//railo.print.ln("closer start");
do {
sleepEL();
provider.clean();
}
while(!clients.isEmpty());
//railo.print.ln("closer stop");
}
private void sleepEL() {
try {
sleep(provider.clientTimeout);
} catch (InterruptedException e) {}
}
}
/**
* @return the cache
*/
public int getCache() {
return cache;
}
@Override
public boolean isAttributesSupported() {
return false;
}
public boolean isCaseSensitive() {
return true;
}
@Override
public boolean isModeSupported() {
return true;
}
@Override
public long sizeOf() {
return SizeOf.size(lock)+SizeOf.size(clients);
}
@Override
public Map getArguments() {
return arguments;
}
}