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