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.file;
020import java.io.BufferedInputStream;
021import java.io.BufferedOutputStream;
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.FileOutputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.OutputStream;
028import java.util.ArrayList;
029import java.util.List;
030
031import lucee.commons.cli.Command;
032import lucee.commons.io.IOUtil;
033import lucee.commons.io.ModeUtil;
034import lucee.commons.io.SystemUtil;
035import lucee.commons.io.res.ContentType;
036import lucee.commons.io.res.Resource;
037import lucee.commons.io.res.ResourceProvider;
038import lucee.commons.io.res.filter.ResourceFilter;
039import lucee.commons.io.res.filter.ResourceNameFilter;
040import lucee.commons.io.res.util.ResourceOutputStream;
041import lucee.commons.io.res.util.ResourceUtil;
042import lucee.commons.lang.ExceptionUtil;
043
044/**
045 * Implementation og Resource for the local filesystem (java.io.File)
046 */
047public final class FileResource extends File implements Resource {
048
049        private final FileResourceProvider provider;
050
051        /**
052         * Constructor for the factory
053         * @param pathname
054         */
055        FileResource(FileResourceProvider provider,String pathname) {
056                super(pathname);
057                this.provider=provider;
058        }
059
060        /**
061         * Inner Constr constructor to create parent/child
062         * @param parent
063         * @param child
064         */
065        private FileResource(FileResourceProvider provider,File parent, String child) {
066                super(parent, child);
067                this.provider=provider;
068        }
069
070
071        @Override
072        public void copyFrom(Resource res,boolean append) throws IOException {
073                IOUtil.copy(res, this.getOutputStream(append),true);
074        }
075
076        @Override
077        public void copyTo(Resource res,boolean append) throws IOException {
078                IOUtil.copy(this, res.getOutputStream(append),true);
079        }
080        
081        @Override
082        public Resource getAbsoluteResource() {
083                return new FileResource(provider,getAbsolutePath());
084        }
085
086        @Override
087        public Resource getCanonicalResource() throws IOException {
088                return new FileResource(provider,getCanonicalPath());
089        }
090
091        @Override
092        public Resource getParentResource() {
093                String p = getParent();
094                if(p==null) return null;
095                return new FileResource(provider,p);
096        }
097
098        @Override
099        public Resource[] listResources() {
100                String[] files = list();
101                if(files==null) return null;
102                
103                Resource[] resources=new Resource[files.length];
104                for(int i=0;i<files.length;i++) {
105                        resources[i]=getRealResource(files[i]);
106                }
107                return resources;
108        }
109        
110        @Override
111        public String[] list(ResourceFilter filter) {
112                String[] files = list();
113                if(files==null) return null;
114                
115                List list=new ArrayList();
116                FileResource res;
117                for(int i=0;i<files.length;i++) {
118                        res=new FileResource(provider,this,files[i]);
119                        if(filter.accept(res))list.add(files[i]);
120                }
121                return (String[]) list.toArray(new String[list.size()]);
122        }
123
124        @Override
125        public Resource[] listResources(ResourceFilter filter) {
126                String[] files = list();
127                if(files==null) return null;
128                
129                List list=new ArrayList();
130                Resource res;
131                for(int i=0;i<files.length;i++) {
132                        res=getRealResource(files[i]);
133                        if(filter.accept(res))list.add(res);
134                }
135                return (Resource[]) list.toArray(new FileResource[list.size()]);
136        }
137        
138
139        @Override
140        public String[] list(ResourceNameFilter filter) {
141                String[] files = list();
142                if(files==null) return null;
143                List list=new ArrayList();
144                for(int i=0;i<files.length;i++) {
145                        if(filter.accept(this,files[i]))list.add(files[i]);
146                }
147                return (String[]) list.toArray(new String[list.size()]);
148        }
149
150        @Override
151        public Resource[] listResources(ResourceNameFilter filter) {
152                String[] files = list();
153                if(files==null) return null;
154                
155                List list=new ArrayList();
156                for(int i=0;i<files.length;i++) {
157                        if(filter.accept(this,files[i]))list.add(getRealResource(files[i]));
158                }
159                return (Resource[]) list.toArray(new Resource[list.size()]);
160        }
161
162        @Override
163        public void moveTo(Resource dest) throws IOException {
164                if(this.equals(dest)) return;
165                boolean done=false;
166                if(dest instanceof File) {
167                        provider.lock(this);
168                        try {
169                                if(dest.exists() && !dest.delete())
170                                        throw new IOException("can't move file "+this.getAbsolutePath()+" cannot remove existing file "+dest.getAbsolutePath());
171                                
172                                done=super.renameTo((File)dest);
173                                /*if(!super.renameTo((File)dest)) {
174                                        throw new IOException("can't move file "+this.getAbsolutePath()+" to destination resource "+dest.getAbsolutePath());
175                                }*/
176                        }
177                        finally {
178                                provider.unlock(this);
179                        }
180                }
181                if(!done) {
182                        ResourceUtil.checkMoveToOK(this, dest);
183                        IOUtil.copy(getInputStream(),dest,true);
184                        if(!this.delete()) {
185                                throw new IOException("can't delete resource "+this.getAbsolutePath());
186                        }
187                }
188        }
189
190        @Override
191        public InputStream getInputStream() throws IOException {
192                //provider.lock(this);
193                provider.read(this);
194                try {
195                        //return new BufferedInputStream(new ResourceInputStream(this,new FileInputStream(this)));
196                        return new BufferedInputStream(new FileInputStream(this));
197                }
198                catch(IOException ioe) {
199                        //provider.unlock(this);
200                        throw ioe;
201                }
202        }
203
204        @Override
205        public OutputStream getOutputStream() throws IOException {
206                return getOutputStream(false);
207        }
208
209        @Override
210        public OutputStream getOutputStream(boolean append) throws IOException {
211                provider.lock(this);
212                try {
213                        if(!super.exists() &&  !super.createNewFile()) {
214                                throw new IOException("can't create file "+this);
215                        }
216                        return new BufferedOutputStream(new ResourceOutputStream(this,new FileOutputStream(this,append)));
217                }
218                catch(IOException ioe) {
219                        provider.unlock(this);
220                        throw ioe;
221                }
222        }
223        
224        @Override
225        public void createFile(boolean createParentWhenNotExists) throws IOException {
226                provider.lock(this);
227                try {
228                        if(createParentWhenNotExists) {
229                                File p = super.getParentFile();
230                                if(!p.exists()) p.mkdirs();
231                        }
232                        if(!super.createNewFile()) {
233                                if(super.isFile())      throw new IOException("can't create file "+this+", file already exists");
234                                throw new IOException("can't create file "+this);
235                        }
236                }
237                finally {
238                        provider.unlock(this);
239                }
240        }
241        
242        @Override
243        public void remove(boolean alsoRemoveChildren) throws IOException {
244                if(alsoRemoveChildren && isDirectory()) {
245                        Resource[] children = listResources();
246                        for(int i=0;i<children.length;i++) {
247                                children[i].remove(alsoRemoveChildren);
248                        }
249                }
250                provider.lock(this);
251                try {
252                        if(!super.delete()) {
253                                if(!super.exists())throw new IOException("can't delete file "+this+", file does not exist");
254                                if(!super.canWrite())throw new IOException("can't delete file "+this+", no access");
255                                throw new IOException("can't delete file "+this);
256                        }
257                }
258                finally {
259                        provider.unlock(this);
260                }
261        }
262
263        @Override
264        public String getReal(String realpath) {
265                if(realpath.length()<=2) {
266                        if(realpath.length()==0) return getPath();
267                        if(realpath.equals(".")) return getPath();
268                        if(realpath.equals("..")) return getParent();
269                }
270                return new FileResource(provider,this,realpath).getPath();
271        }
272
273        @Override
274        public Resource getRealResource(String realpath) {
275                if(realpath.length()<=2) {
276                        if(realpath.length()==0) return this;
277                        if(realpath.equals(".")) return this;
278                        if(realpath.equals("..")) return getParentResource();
279                }
280                return new FileResource(provider,this,realpath);
281        }
282
283        public ContentType getContentType() {
284                return ResourceUtil.getContentType(this);
285        }
286
287        @Override
288        public void createDirectory(boolean createParentWhenNotExists) throws IOException {
289                provider.lock(this);
290                try {
291                        if(createParentWhenNotExists?!_mkdirs():!super.mkdir()) {
292                                if(super.isDirectory()) throw new IOException("can't create directory "+this+", directory already exists");
293                                throw new IOException("can't create directory "+this);
294                        }
295                }
296                finally {
297                        provider.unlock(this);
298                }
299        }
300
301        @Override
302        public ResourceProvider getResourceProvider() {
303                return provider;
304        }
305
306        @Override
307        public boolean isReadable() {
308                return canRead();
309        }
310
311        @Override
312        public boolean isWriteable() {
313                return canWrite();
314        }
315
316        @Override
317        public boolean renameTo(Resource dest) {
318                try {
319                        moveTo(dest);
320                        return true;
321                }
322                catch (IOException e) {}
323                return false;
324        }
325
326        @Override
327        public boolean isArchive() {
328                return getAttribute(ATTRIBUTE_ARCHIVE);
329        }
330
331        @Override
332        public boolean isSystem() {
333                return getAttribute(ATTRIBUTE_SYSTEM);
334        }
335
336        @Override
337        public int getMode() {
338                if(!exists()) return 0;
339                if(SystemUtil.isUnix()) {
340                        try {
341                                // TODO geht nur fuer file
342                                String line = Command.execute("ls -ld "+getPath(),false).getOutput();
343                                
344                                line=line.trim();
345                                line=line.substring(0,line.indexOf(' '));
346                                //print.ln(getPath());
347                                return ModeUtil.toOctalMode(line);
348                                
349                        } catch (Exception e) {}
350                
351                }
352                int mode=SystemUtil.isWindows() && exists() ?0111:0;
353                if(super.canRead())mode+=0444;
354                if(super.canWrite())mode+=0222;
355                return mode;
356        }
357
358        public void setMode(int mode) throws IOException {
359                // TODO unter windows mit setReadable usw.
360                if(!SystemUtil.isUnix()) return;
361        provider.lock(this);
362        try {
363                //print.ln(ModeUtil.toStringMode(mode));
364            if (Runtime.getRuntime().exec(
365              new String[] { "chmod", ModeUtil.toStringMode(mode), getPath() } ).waitFor() != 0)
366            throw new IOException("chmod  "+ModeUtil.toStringMode(mode)+" " + toString() + " failed");
367        }
368        catch (InterruptedException e) {
369            throw new IOException("Interrupted waiting for chmod " + toString());
370        }
371        finally {
372                provider.unlock(this);
373        }
374        }
375
376        @Override
377        public void setArchive(boolean value) throws IOException {
378                setAttribute(ATTRIBUTE_ARCHIVE, value);
379        }
380
381        @Override
382        public void setHidden(boolean value) throws IOException {
383                setAttribute(ATTRIBUTE_HIDDEN, value);
384        }
385
386        @Override
387        public void setSystem(boolean value) throws IOException {
388                setAttribute(ATTRIBUTE_SYSTEM, value);
389        }
390
391        @Override
392
393        public boolean setReadable(boolean value)  {
394                if(!SystemUtil.isUnix()) return false;
395                try {
396                        setMode(ModeUtil.setReadable(getMode(), value));
397                        return true;
398                } 
399                catch (IOException e) {
400                        return false;
401                }
402        }
403
404        public boolean setWritable(boolean value) {
405                // setReadonly
406                if(!value){
407                        try {
408                                provider.lock(this);
409                                if(!super.setReadOnly()) 
410                                        throw new IOException("can't set resource read-only");
411                        }
412                        catch(IOException ioe){
413                                return false;
414                        }
415                        finally {
416                                provider.unlock(this);
417                        }
418                        return true;
419                }
420                
421                if(SystemUtil.isUnix()) {
422//                       need no lock because get/setmode has one
423                        try {
424                                setMode(ModeUtil.setWritable(getMode(), value));
425                        } 
426                        catch (IOException e) {
427                                return false;
428                        }
429                        return true;
430                }
431                
432                try {
433                        provider.lock(this);
434                        Runtime.getRuntime().exec("attrib -R " + getAbsolutePath());
435                }
436                catch(IOException ioe){
437                        return false;
438                }
439                finally {
440                        provider.unlock(this);
441                }
442                return true;
443        }
444
445        
446        
447        
448        
449        
450        @Override
451        public boolean createNewFile() {
452                try {
453                        provider.lock(this);
454                        return super.createNewFile();
455                } 
456                catch (IOException e) {
457                        return false;
458                }
459                finally {
460                        provider.unlock(this);
461                }
462        }
463        
464        @Override
465        public boolean canRead() {
466                try {
467                        provider.read(this);
468                } catch (IOException e) {
469                        return false;
470                }
471                return super.canRead();
472        }
473
474        @Override
475        public boolean canWrite() {
476                try {
477                        provider.read(this);
478                } catch (IOException e) {
479                        return false;
480                }
481                return super.canWrite();
482        }
483
484        @Override
485        public boolean delete() {
486                try {
487                        provider.lock(this);
488                        return super.delete();
489                }
490                catch (IOException e) {
491                        return false;
492                }
493                finally {
494                        provider.unlock(this);
495                }
496        }
497
498        @Override
499        public boolean exists() {
500                try {
501                        provider.read(this);
502                } catch (IOException e) {}
503                
504                return super.exists();
505        }
506
507        
508
509        @Override
510        public boolean isAbsolute() {
511                try {
512                        provider.read(this);
513                }
514                catch (IOException e) {
515                        return false;
516                }
517                return super.isAbsolute();
518        }
519
520        @Override
521        public boolean isDirectory() {
522                try {
523                        provider.read(this);
524                } catch (IOException e) {
525                        return false;
526                }
527                return super.isDirectory();
528        }
529
530        @Override
531        public boolean isFile() {
532                try {
533                        provider.read(this);
534                } catch (IOException e) {
535                        return false;
536                }
537                return super.isFile();
538        }
539
540        @Override
541        public boolean isHidden() {
542                try {
543                        provider.read(this);
544                } catch (IOException e) {
545                        return false;
546                }
547                return super.isHidden();
548        }
549
550        @Override
551        public long lastModified() {
552                try {
553                        provider.read(this);
554                } catch (IOException e) {
555                        return 0;
556                }
557                return super.lastModified();
558        }
559
560        @Override
561        public long length() {
562                try {
563                        provider.read(this);
564                } catch (IOException e) {
565                        return 0;
566                }
567                return super.length();
568        }
569
570        @Override
571        public String[] list() {
572                try {
573                        provider.read(this);
574                } catch (IOException e) {
575                        return null;
576                }
577                return super.list();
578        }
579
580        @Override
581        public boolean mkdir() {
582                try {
583                        provider.lock(this);
584                        return super.mkdir();
585                }
586                catch (IOException e) {
587                        return false;
588                }
589                finally {
590                        provider.unlock(this);
591                }
592        }
593
594        @Override
595        public boolean mkdirs() {
596                try {
597                        provider.lock(this);
598                        return _mkdirs();
599                        
600                }
601                catch (IOException e) {
602                        return false;
603                }
604                finally {
605                        provider.unlock(this);
606                }
607        }
608        
609        private boolean _mkdirs() {
610                if (super.exists())     return false;
611                if (super.mkdir())      return true;
612                
613                File parent = super.getParentFile();
614                return (parent != null) && (parent.mkdirs() && super.mkdir());
615        }
616
617        @Override
618        public boolean setLastModified(long time) {
619                try {
620                        provider.lock(this);
621                        return super.setLastModified(time);
622                }
623                catch (Throwable t) {
624                ExceptionUtil.rethrowIfNecessary(t);
625                        return false;
626                }
627                finally {
628                        provider.unlock(this);
629                }
630                
631        }
632
633        @Override
634        public boolean setReadOnly() {
635                try {
636                        provider.lock(this);
637                        return super.setReadOnly();
638                } 
639                catch (IOException e) {
640                        return false;
641                }
642                finally {
643                        provider.unlock(this);
644                }
645        }
646
647        public boolean getAttribute(short attribute) {
648                if(!SystemUtil.isWindows()) return false;
649                if(attribute==ATTRIBUTE_HIDDEN) return isHidden();
650                
651                String attr=null;
652                if(attribute==ATTRIBUTE_ARCHIVE)                attr="A";
653                else if(attribute==ATTRIBUTE_SYSTEM)    attr="S";
654                
655                try {
656                        provider.lock(this);
657                        String result = Command.execute("attrib " + getAbsolutePath(),false).getOutput();
658                        String[] arr = lucee.runtime.type.util.ListUtil.listToStringArray(result, ' ');
659                        for(int i=0;i>arr.length-1;i++) {
660                                if(attr.equals(arr[i].toUpperCase())) return true;
661                        }
662                } 
663                catch (Exception e) {}
664                finally {
665                        provider.unlock(this);
666                }
667                return false;
668        }
669
670        public void setAttribute(short attribute, boolean value) throws IOException {
671                String attr=null;
672                if(attribute==ATTRIBUTE_ARCHIVE)                attr="A";
673                else if(attribute==ATTRIBUTE_HIDDEN)    attr="H";
674                else if(attribute==ATTRIBUTE_SYSTEM)    attr="S";
675                
676                if(!SystemUtil.isWindows()) return ;
677                provider.lock(this);
678                try {
679                        Runtime.getRuntime().exec("attrib "+attr+(value?"+":"-")+" " + getAbsolutePath());
680                }
681                finally {
682                        provider.unlock(this);
683                }
684        }
685        
686        public boolean equals(Object other){
687                if(provider.isCaseSensitive()) return super.equals(other);
688                if(!(other instanceof File)) return false;
689                return getAbsolutePath().equalsIgnoreCase(((File)other).getAbsolutePath());
690        }
691}