001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.commons.io.res.type.ftp; 020 021 022 023import java.io.IOException; 024import java.net.SocketException; 025import java.util.HashMap; 026import java.util.Map; 027 028import lucee.commons.io.res.Resource; 029import lucee.commons.io.res.ResourceProvider; 030import lucee.commons.io.res.ResourceProviderPro; 031import lucee.commons.io.res.Resources; 032import lucee.commons.io.res.util.ResourceLockImpl; 033import lucee.commons.io.res.util.ResourceUtil; 034import lucee.commons.lang.SizeOf; 035import lucee.commons.lang.StringUtil; 036import lucee.runtime.net.proxy.Proxy; 037import lucee.runtime.op.Caster; 038import lucee.runtime.type.Sizeable; 039 040// TODO check connection timeout 041public final class FTPResourceProvider implements ResourceProviderPro,Sizeable { 042 private String scheme="ftp"; 043 private final Map clients=new HashMap(); 044 private int clientTimeout=60000; 045 private int socketTimeout=-1; 046 private int lockTimeout=20000; 047 private int cache=20000; 048 049 private FTPResourceClientCloser closer=null; 050 private final ResourceLockImpl lock=new ResourceLockImpl(lockTimeout,true); 051 private Map arguments; 052 053 public ResourceProvider init(String scheme, Map arguments) { 054 setScheme(scheme); 055 056 if(arguments!=null) { 057 this.arguments=arguments; 058 // client-timeout 059 String strTimeout=(String) arguments.get("client-timeout"); 060 if(strTimeout!=null) { 061 clientTimeout=Caster.toIntValue(strTimeout,clientTimeout); 062 } 063 // socket-timeout 064 strTimeout=(String) arguments.get("socket-timeout"); 065 if(strTimeout!=null) { 066 socketTimeout=Caster.toIntValue(strTimeout,socketTimeout); 067 } 068 // lock-timeout 069 strTimeout=(String) arguments.get("lock-timeout"); 070 if(strTimeout!=null) { 071 lockTimeout=Caster.toIntValue(strTimeout,lockTimeout); 072 } 073 // cache 074 String strCache=(String) arguments.get("cache"); 075 if(strCache!=null) { 076 cache=Caster.toIntValue(strCache,cache); 077 } 078 } 079 lock.setLockTimeout(lockTimeout); 080 081 return this; 082 } 083 084 @Override 085 public Resource getResource(String path) { 086 path=ResourceUtil.removeScheme(scheme,path); 087 FTPConnectionData data=new FTPConnectionData(); 088 path=data.load(path); 089 090 return new FTPResource(this,data,path); 091 } 092 093 094 095 096 FTPResourceClient getClient(FTPConnectionData data) throws IOException { 097 098 FTPResourceClient client=(FTPResourceClient) clients.remove(data.key()); 099 if(client==null) { 100 client = new FTPResourceClient(data,cache); 101 if(socketTimeout>0)client.setSoTimeout(socketTimeout); 102 } 103 104 if(!client.isConnected()) { 105 if(data.hasProxyData()) { 106 try { 107 Proxy.start( 108 data.getProxyserver(), 109 data.getProxyport(), 110 data.getProxyuser(), 111 data.getProxypassword() 112 ); 113 connect(client,data); 114 } 115 finally { 116 Proxy.end(); 117 } 118 } 119 else { 120 connect(client,data); 121 } 122 123 int replyCode = client.getReplyCode(); 124 if(replyCode>=400) 125 throw new FTPException(replyCode); 126 } 127 startCloser(); 128 return client; 129 } 130 131 private synchronized void startCloser() { 132 if(closer==null || !closer.isAlive()) { 133 closer=new FTPResourceClientCloser(this); 134 closer.start(); 135 } 136 137 } 138 private void connect(FTPResourceClient client, FTPConnectionData data) throws SocketException, IOException { 139 if(data.port>0)client.connect(data.host,data.port); 140 else client.connect(data.host); 141 if(!StringUtil.isEmpty(data.username))client.login(data.username,data.password); 142 } 143 144 public void returnClient(FTPResourceClient client) { 145 if(client==null)return; 146 client.touch(); 147 clients.put(client.getFtpConnectionData().key(), client); 148 } 149 150 @Override 151 public String getScheme() { 152 return scheme; 153 } 154 155 public void setScheme(String scheme) { 156 if(!StringUtil.isEmpty(scheme))this.scheme=scheme; 157 } 158 159 @Override 160 public void setResources(Resources resources) { 161 //this.resources=resources; 162 } 163 164 @Override 165 public void lock(Resource res) throws IOException { 166 lock.lock(res); 167 } 168 169 @Override 170 public void unlock(Resource res) { 171 lock.unlock(res); 172 } 173 174 @Override 175 public void read(Resource res) throws IOException { 176 lock.read(res); 177 } 178 179 public void clean() { 180 Object[] keys = clients.keySet().toArray(); 181 FTPResourceClient client; 182 for(int i=0;i<keys.length;i++) { 183 client=(FTPResourceClient) clients.get(keys[i]); 184 if(client.getLastAccess()+clientTimeout<System.currentTimeMillis()) { 185 //lucee.print.ln("disconnect:"+client.getFtpConnectionData().key()); 186 if(client.isConnected()) { 187 try { 188 client.disconnect(); 189 } 190 catch (IOException e) {} 191 } 192 clients.remove(client.getFtpConnectionData().key()); 193 } 194 } 195 } 196 197 class FTPResourceClientCloser extends Thread { 198 199 private FTPResourceProvider provider; 200 201 public FTPResourceClientCloser(FTPResourceProvider provider) { 202 this.provider=provider; 203 } 204 205 public void run() { 206 //lucee.print.ln("closer start"); 207 do { 208 sleepEL(); 209 provider.clean(); 210 } 211 while(!clients.isEmpty()); 212 //lucee.print.ln("closer stop"); 213 } 214 215 private void sleepEL() { 216 try { 217 sleep(provider.clientTimeout); 218 } catch (InterruptedException e) {} 219 } 220 } 221 222 /** 223 * @return the cache 224 */ 225 public int getCache() { 226 return cache; 227 } 228 229 @Override 230 public boolean isAttributesSupported() { 231 return false; 232 } 233 234 public boolean isCaseSensitive() { 235 return true; 236 } 237 238 @Override 239 public boolean isModeSupported() { 240 return true; 241 } 242 243 @Override 244 public long sizeOf() { 245 return SizeOf.size(lock)+SizeOf.size(clients); 246 } 247 248 @Override 249 public Map getArguments() { 250 return arguments; 251 } 252 253 @Override 254 public char getSeparator() { 255 return '/'; 256 } 257 258} 259