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.compress;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.HashMap;
024import java.util.Map;
025import java.util.zip.GZIPOutputStream;
026import java.util.zip.ZipOutputStream;
027
028import lucee.commons.digest.MD5;
029import lucee.commons.io.CompressUtil;
030import lucee.commons.io.IOUtil;
031import lucee.commons.io.SystemUtil;
032import lucee.commons.io.res.Resource;
033import lucee.commons.io.res.ResourceProvider;
034import lucee.commons.io.res.type.ram.RamResourceProviderOld;
035import lucee.commons.io.res.util.ResourceUtil;
036import lucee.commons.lang.ExceptionUtil;
037import lucee.runtime.config.Config;
038import lucee.runtime.config.ConfigImpl;
039import lucee.runtime.engine.ThreadLocalPageContext;
040import lucee.runtime.op.Caster;
041
042import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
043
044public final class Compress {
045        
046        public static final int FORMAT_ZIP = CompressUtil.FORMAT_ZIP;
047        public static final int FORMAT_TAR = CompressUtil.FORMAT_TAR;
048        public static final int FORMAT_TGZ = CompressUtil.FORMAT_TGZ;
049        public static final int FORMAT_TBZ2 = CompressUtil.FORMAT_TBZ2;
050        
051        
052        //private final static Map files=new WeakHashMap();
053        
054        private final Resource ffile;
055        //private ResourceProvider ramProvider;
056        private long syn=-1;
057        private Resource root;
058        private Synchronizer synchronizer;
059        private long lastMod=-1;
060        private long lastCheck=-1;
061
062        private int format;
063        private int mode;
064        private boolean caseSensitive;
065        private Resource temp;
066        
067        /**
068         * private Constructor of the class, will be invoked be getInstance
069         * @param file
070         * @param format 
071         * @param caseSensitive 
072         */
073        public Compress(Resource file, int format, boolean caseSensitive) {
074                this.ffile=file;
075                this.format=format;
076                this.mode=ffile.getMode();
077                if(mode==0) mode=0777;
078                load(this.caseSensitive=caseSensitive);
079        }
080
081        /**
082         * return zip instance matching the zipfile, singelton isnatce only 1 zip for one file
083         * @param zipFile
084         * @param format 
085         * @param caseSensitive 
086         * @return
087         */
088        /*public static Compress getInstance(Resource zipFile, int format, boolean caseSensitive) {
089                Compress compress=(Compress) files.get(zipFile.getPath());
090                if(compress==null) {
091                        compress=new Compress(zipFile,format,caseSensitive);
092                        files.put(zipFile.getPath(), compress);
093                }
094                return compress;ConfigImpl
095        }*/
096        public static Compress getInstance(Resource zipFile, int format, boolean caseSensitive) {
097                ConfigImpl config=(ConfigImpl) ThreadLocalPageContext.getConfig();
098                return config.getCompressInstance(zipFile, format, caseSensitive);
099        }
100        
101        
102        private synchronized void load(boolean caseSensitive) {
103                long actLastMod = ffile.lastModified();
104                lastMod=actLastMod;
105                lastCheck=System.currentTimeMillis();
106                Map args = new HashMap();
107                args.put("case-sensitive", Caster.toBoolean(caseSensitive));
108                //ramProvider = new RamResourceProviderOld().init("ram",args);
109                //root=ramProvider.getResource("/");
110                
111                if(temp==null){
112                        String cid="";
113                        try {
114                                Config config = ThreadLocalPageContext.getConfig();
115                                if(config!=null){
116                                        cid=config.getId();
117                                        temp = config.getTempDirectory();
118                                }
119                                if(temp==null)temp=SystemUtil.getTempDirectory();
120
121                                temp=temp.getRealResource("compress");
122                                temp=temp.getRealResource(MD5.getDigestAsString(cid+"-"+ffile.getAbsolutePath()));
123                                if(!temp.exists())temp.createDirectory(true);
124                                
125                        }
126                        catch(Throwable t){
127                ExceptionUtil.rethrowIfNecessary(t);
128            }
129                }
130                
131                
132                if(temp!=null) {
133                        String name=Caster.toString(actLastMod)+":"+Caster.toString(ffile.length());
134                        name=MD5.getDigestAsString(name,name);
135                        root=temp.getRealResource(name);
136                        if(actLastMod>0 && root.exists()) return;
137                        
138                        
139                        ResourceUtil.removeChildrenEL(temp);
140                        //if(root!=null)ResourceUtil.removeChildrenEL(root);
141                        //String name=CreateUUID.invoke();
142                        //root=temp.getRealResource(name);
143                        root.mkdirs();
144                }
145                else {
146                        ResourceProvider ramProvider = new RamResourceProviderOld().init("ram",args);
147                        root=ramProvider.getResource("/");
148                }
149                _load();
150        }
151        
152        
153
154        private void _load() {
155                if(ffile.exists()) {
156                        try {
157                                CompressUtil.extract(format, ffile, root);
158                        } catch (IOException e) {}
159                }
160                else {
161                        try {
162                                ffile.createFile(false);
163                        } 
164                        catch (IOException e) {}
165                        lastMod=ffile.lastModified();
166                }
167        }
168
169        public Resource getRamProviderResource(String path) {
170                long t=System.currentTimeMillis();
171                if(t>lastCheck+2000){
172                        
173                        lastCheck=t;
174                        t=ffile.lastModified();
175                        if((lastMod-t)>10 || (t-lastMod)>10 || root==null || !root.exists()){
176                                lastMod=t;
177                                load(caseSensitive);
178                        }
179                }
180                return root.getRealResource(path);//ramProvider.getResource(path);
181        }
182
183        /**
184         * @return the zipFile
185         */
186        public Resource getCompressFile() {
187                return ffile;
188        }
189
190        public synchronized void synchronize(boolean async) {
191                if(!async) {
192                        doSynchronize();
193                        return;
194                }
195                syn=System.currentTimeMillis();
196                if(synchronizer==null || !synchronizer.isRunning()) {
197                        synchronizer=new Synchronizer(this,100);
198                        synchronizer.start();
199                }
200        }
201
202        private void doSynchronize() {
203                try {
204                        CompressUtil.compress(format, root.listResources(), ffile, 777);
205                        //ramProvider=null;
206                } 
207                catch (IOException e) {}
208        }
209        
210        class Synchronizer extends Thread {
211                private Compress zip;
212                private int interval;
213                private boolean running=true;
214
215                public Synchronizer(Compress zip, int interval) {
216                        this.zip=zip;
217                        this.interval=interval; 
218                }
219                
220                public void run() {
221                        if(FORMAT_TAR==format) runTar(ffile);
222                        if(FORMAT_TGZ==format) runTGZ(ffile);
223                        else runZip(ffile);
224                        
225                }
226
227                private void runTGZ(Resource res) {
228                        GZIPOutputStream gos=null;
229                        InputStream tmpis=null;
230                        Resource tmp = SystemUtil.getTempDirectory().getRealResource(System.currentTimeMillis()+"_.tgz");
231                try {
232                                gos=new GZIPOutputStream(res.getOutputStream());
233                                // wait for sync                
234                                while(true) {
235                                        sleepEL();
236                                        if(zip.syn+interval<=System.currentTimeMillis()) break;
237                                }
238                                // sync
239                                tmpis = tmp.getInputStream();
240                                CompressUtil.compressTar(root.listResources(), tmp, -1);
241                                CompressUtil.compressGZip(tmpis, gos);
242                        }
243                        catch (IOException e) {}
244                        finally {
245                                IOUtil.closeEL(gos);
246                                IOUtil.closeEL(tmpis);
247                                tmp.delete();
248                                running=false;
249                        }
250                }
251                private void runTar(Resource res) {
252                        TarArchiveOutputStream tos=null;
253                        try {
254                                tos=new TarArchiveOutputStream(res.getOutputStream());
255                                tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
256                        // wait for sync                
257                                while(true) {
258                                        sleepEL();
259                                        if(zip.syn+interval<=System.currentTimeMillis()) break;
260                                }
261                                // sync
262                                CompressUtil.compressTar(root.listResources(), tos, -1);
263                        }
264                        catch (IOException e) {}
265                        finally {
266                                IOUtil.closeEL(tos);
267                                running=false;
268                        }
269                }
270
271                private void runZip(Resource res) {
272                        ZipOutputStream zos=null;
273                        try {
274                                zos=new ZipOutputStream(res.getOutputStream());
275                                // wait for sync                
276                                while(true) {
277                                        sleepEL();
278                                        if(zip.syn+interval<=System.currentTimeMillis()) break;
279                                }
280                                // sync
281                                CompressUtil.compressZip(root.listResources(), zos, null);
282                        }
283                        catch (IOException e) {}
284                        finally {
285                                IOUtil.closeEL(zos);
286                                running=false;
287                        }
288                }
289
290                private void sleepEL() {
291                        try {
292                                sleep(interval);
293                        } 
294                        catch (InterruptedException e) {}
295                }
296
297                public boolean isRunning() {
298                        return running;
299                }
300        }
301}