001/*******************************************************************************
002The MIT License (MIT)
003
004Copyright (c) 2024 KILLCODING.COM
005
006Permission is hereby granted, free of charge, to any person obtaining a copy
007of this software and associated documentation files (the "Software"), to deal
008in the Software without restriction, including without limitation the rights
009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010copies of the Software, and to permit persons to whom the Software is
011furnished to do so, subject to the following conditions:
012
013The above copyright notice and this permission notice shall be included in
014all copies or substantial portions of the Software.
015
016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
022THE SOFTWARE.
023*****************************************************************************/
024package com.killcoding.file;
025
026import java.io.File;
027import java.nio.file.Paths;
028import java.nio.file.Files;
029import java.io.IOException;
030import java.nio.file.OpenOption;
031import java.nio.file.StandardOpenOption;
032import java.nio.channels.FileChannel;
033import java.nio.channels.FileLock;
034import java.nio.file.Path;
035import java.nio.ByteBuffer;
036import com.killcoding.log.Logger;
037import java.net.URI;
038import java.util.Comparator;
039import java.util.List;
040import java.util.ArrayList;
041import java.nio.channels.SeekableByteChannel;
042import java.io.ByteArrayOutputStream;
043import java.nio.file.attribute.BasicFileAttributes;
044import java.nio.file.attribute.FileTime;
045import java.time.Instant;
046import java.util.Arrays;
047import java.io.ByteArrayInputStream;
048import java.sql.Timestamp;
049import java.util.stream.Stream;
050import java.util.stream.Collectors;
051import java.util.HashSet;
052import java.util.Set;
053import java.nio.file.DirectoryStream;
054import java.nio.file.CopyOption;
055import java.nio.file.LinkOption;
056import com.killcoding.tool.CommonTools;
057import com.killcoding.file.RemoteFile;
058import com.killcoding.log.LoggerFactory;
059import java.util.concurrent.Future;
060import com.killcoding.tool.ConfigProperties;
061import com.killcoding.datasource.Clock;
062import java.util.concurrent.Callable;
063import com.killcoding.file.DiskFile;
064import java.nio.file.attribute.FileAttribute;
065import java.text.SimpleDateFormat;
066import com.killcoding.cache.CacheArray;
067import com.killcoding.cache.CacheArrayFilter;
068import java.util.Map;
069import java.util.concurrent.ExecutorService;
070import java.util.concurrent.Executors;
071import java.nio.file.NoSuchFileException;
072import java.util.concurrent.ConcurrentHashMap;
073import java.util.Iterator;
074import java.util.Date;
075import java.time.ZonedDateTime;
076import java.time.LocalDateTime;
077import java.time.ZoneId;
078import java.time.format.DateTimeFormatter;
079import java.util.Calendar;
080import java.util.TimeZone;
081import java.text.ParseException;
082import java.util.Collections;
083import java.util.HashMap;
084import java.nio.file.FileAlreadyExistsException;
085import java.util.LinkedList;
086
087public class DiskFile extends BaseFile {
088    
089    public static final int RETRY_OPEN_LIMITED = 100;
090
091        protected static Integer MAX_POOL_SIZE = 100;
092        private static final Map<String, Long> QUEUE_PATH_MAPPING = new ConcurrentHashMap<String, Long>();
093
094        private static ExecutorService splitPool = null;
095
096        protected boolean copyStructureOnly = false;
097        private FileChannel channel = null;
098        private FileLock lock = null;
099        private long modifyTimeMs = 0L;
100
101        public DiskFile(String path) {
102                super(path);
103        }
104        
105        public DiskFile(File origin) {
106                super(origin);
107        }       
108
109        public static synchronized void initPool(int poolSize) {
110                if (splitPool == null) {
111                        MAX_POOL_SIZE = poolSize;
112                        LoggerFactory.getLogger(DiskFile.class).mark("MAX_POOL_SIZE={}", MAX_POOL_SIZE);
113                        splitPool = Executors.newFixedThreadPool(MAX_POOL_SIZE);
114                }
115        }
116
117        private static synchronized void initDefaultPool() {
118                if (splitPool == null) {
119                        LoggerFactory.getLogger(DiskFile.class).mark("MAX_POOL_SIZE={}", MAX_POOL_SIZE);
120                        splitPool = Executors.newFixedThreadPool(MAX_POOL_SIZE);
121                }
122        }
123
124        public void split(int partSize, FilePart filePart) throws IOException {
125                if (isLink()) {
126                        throw new IOException(String.format("The disk file '%s' is a link.", origin.getAbsolutePath()));
127                }
128                if (isDir()) {
129                        throw new IOException(String.format("The disk file '%s' is a folder.", origin.getAbsolutePath()));
130                }
131                if (!exists()) {
132                        throw new IOException(String.format("The disk file '%s' does not exist.", origin.getAbsolutePath()));
133                }
134                initDefaultPool();
135                splitPool.execute(new Runnable() {
136                        @Override
137                        public void run() {
138                                SeekableByteChannel ch = null;
139                                int partIndex = -1;
140                                long fileSize = 0L;
141                                try {
142                                        Path originPath = Paths.get(origin.toURI());
143                                        Thread.currentThread().setName(String.format("DiskFile-split-%s", origin.getName()));
144                                        ch = Files.newByteChannel(originPath, StandardOpenOption.READ);
145                                        ByteBuffer bf = ByteBuffer.allocate(partSize);
146                                        while (ch.read(bf) != -1) {
147                                                bf.flip();
148                                                partIndex += 1;
149                                                List<Byte> parts = new ArrayList<Byte>();
150                                                while (bf.hasRemaining()) {
151                                                        parts.add(bf.get());
152                                                }
153                                                int partsSize = parts.size();
154                                                fileSize += partsSize;
155                                                byte[] part = new byte[partsSize];
156                                                for (int i = 0; i < partsSize; i++) {
157                                                        part[i] = parts.get(i);
158                                                }
159                                                filePart.process(partIndex, part);
160                                                bf.clear();
161                                        }
162                                        filePart.completed(partIndex, getModifyTimeForClock(), fileSize);
163                                } catch (Exception e) {
164                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
165                                } finally {
166                                        if (ch != null) {
167                                                try {
168                                                        ch.close();
169                                                } catch (IOException e) {
170                                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
171                                                }
172                                        }
173                                        filePart.ended(partIndex, fileSize);
174                                }
175                        }
176                });
177        }
178
179        @Override
180        public void copyTo(String toPath) throws IOException {
181                Path originPath = Paths.get(origin.toURI());
182                Path destPath = Paths.get(toPath);
183                LoggerFactory.getLogger(DiskFile.class).debug("CopyTo: {} -> {}", originPath, destPath);
184                Files.copy(originPath, destPath);
185        }
186
187        @Override
188        public void moveTo(String toPath) throws IOException {
189                Path originPath = Paths.get(origin.toURI());
190                Path destPath = Paths.get(toPath);
191                LoggerFactory.getLogger(DiskFile.class).debug("MoveTo: {} -> {}", originPath, destPath);
192                if (Files.exists(destPath)) {
193                        Files.delete(destPath);
194                }
195                Files.move(originPath, destPath);
196        }
197
198        public RemoteFile writeToRemote(RemoteFile remoteFile) throws IOException {
199                return writeToRemote(remoteFile, new Runnable() {
200                        @Override
201                        public void run() {
202
203                        }
204                });
205        }
206
207        public RemoteFile writeToRemote(RemoteFile remoteFile, Runnable completedCallback) throws IOException {
208                if (remoteFile.exists(true)) {
209                        throw new IOException(String.format("The remote file '%s' already exists.", remoteFile.getPath()));
210                } else {
211                        remoteFile.copyFrom(copyStructureOnly, this);
212                        if (isDir()) {
213                                startFullAsync(0, this.getPath() + "/", remoteFile.getPath() + "/", completedCallback, null);
214                        }
215                }
216                return remoteFile;
217        }
218
219        protected boolean isLogicModify() throws Exception {
220                boolean b = false;
221                String tmpPath = toModifyTmpFilePath();
222
223                DiskFile df = null;
224                Path logicPath = Paths.get(tmpPath);
225                if (Files.exists(logicPath)) {
226                        df = new DiskFile(tmpPath);
227                        DiskFile.copyAttrs(df, this.copyStructureOnly, this.syncRoot);
228                        Timestamp dfModifyTime = df.getModifyTimeForClock();
229
230                        long diffMs = Calendar.getInstance().getTimeInMillis() - dfModifyTime.getTime();
231
232                        b = diffMs < LOGIC_TIMEOUT_MS;
233                }
234                return b;
235        }
236
237        protected DiskFile logicModify() throws Exception {
238                return logicModify(null);
239        }
240
241        protected DiskFile logicModify(String msg) throws Exception {
242                if (getParentFile().exists()) {
243                        String tmpLogicPath = toModifyTmpFilePath();
244
245                        if (tmpLogicPath == null)
246                                return null;
247
248                        Path logicPath = Paths.get(tmpLogicPath);
249                        DiskFile df = new DiskFile(tmpLogicPath);
250                        DiskFile.copyAttrs(df, this.copyStructureOnly, this.syncRoot);
251
252                        if (msg == null) {
253                                df.write(String.format("%s-%s", Thread.currentThread().getName(), Thread.currentThread().getId()),
254                                                false);
255                        } else {
256                                df.write(msg, false);
257                        }
258
259                        Files.setLastModifiedTime(logicPath, FileTime.fromMillis(Calendar.getInstance().getTimeInMillis()));
260                        return df;
261                }
262                return null;
263        }
264
265        protected void removeLogicModify() {
266                try {
267                        String tmpPath = toModifyTmpFilePath();
268                        if (tmpPath != null) {
269                                DiskFile df = new DiskFile(tmpPath);
270                                DiskFile.copyAttrs(df, this.copyStructureOnly, this.syncRoot);
271                                if (df.exists()) {
272                                        df.delete();
273                                }
274                        }
275                } catch (Exception e) {
276                        LoggerFactory.getLogger(DiskFile.class).warn(e.getMessage(), e);
277                }
278        }
279
280        protected boolean isLogicAccess() throws Exception {
281            boolean b = false;
282                String tmpPath = toAccessTmpFilePath();
283                DiskFile df = null;
284                Path logicPath = Paths.get(tmpPath);
285                if (Files.exists(logicPath)) {
286                        df = new DiskFile(tmpPath);
287                        DiskFile.copyAttrs(df, this.copyStructureOnly, this.syncRoot);
288                        Timestamp dfModifyTime = df.getModifyTimeForClock();
289
290                        long diffMs = Calendar.getInstance().getTimeInMillis() - dfModifyTime.getTime();
291
292                        b = diffMs < LOGIC_ACCESS_TIMEOUT_MS;
293                }
294                return b;
295        }
296
297        protected DiskFile logicAccess() throws Exception {
298                return logicAccess(null);
299        }
300
301        protected DiskFile logicAccess(String msg) throws Exception {
302                if (getParentFile().exists()) {
303                        String tmpLogicPath = toAccessTmpFilePath();
304                        Path logicPath = Paths.get(tmpLogicPath);
305                        DiskFile df = new DiskFile(tmpLogicPath);
306                        DiskFile.copyAttrs(df, this.copyStructureOnly, this.syncRoot);
307                        if (msg == null) {
308                                df.write(String.format("%s-%s", Thread.currentThread().getName(), Thread.currentThread().getId()),
309                                                false);
310                        } else {
311                                df.write(msg, false);
312                        }
313
314                        Files.setLastModifiedTime(logicPath, FileTime.fromMillis(Calendar.getInstance().getTimeInMillis()));
315                        return df;
316                }
317                return null;
318        }
319
320        protected void removeLogicAccess() {
321                try {
322                        String tmpPath = toAccessTmpFilePath();
323                        if (tmpPath != null) {
324                                DiskFile df = new DiskFile(tmpPath);
325                                DiskFile.copyAttrs(df, this.copyStructureOnly, this.syncRoot);
326                                if (df.exists()) {
327                                        df.delete();
328                                }
329                        }
330                } catch (Exception e) {
331                        LoggerFactory.getLogger(DiskFile.class).warn(e.getMessage(), e);
332                }
333        }
334
335        protected boolean isLogicCheck() throws Exception {
336                boolean b = false;
337                String tmpPath = toCheckTmpFilePath();
338                
339                DiskFile df = null;
340                Path logicPath = Paths.get(tmpPath);
341                if (Files.exists(logicPath)) {
342                        df = new DiskFile(tmpPath);
343                        DiskFile.copyAttrs(df, this.copyStructureOnly, this.syncRoot);
344                        Timestamp dfModifyTime = df.getModifyTimeForClock();
345                                
346                        long diffMs = Calendar.getInstance().getTimeInMillis() - dfModifyTime.getTime();
347
348                        b = diffMs < LOGIC_CHECK_TIMEOUT_MS;
349                }
350                return b;
351        }
352
353        protected DiskFile logicCheck() throws Exception {
354                return logicCheck(null);
355        }
356
357        public DiskFile logicCheck(String msg) throws Exception {
358        if (getParentFile().exists()) {
359                String tmpLogicPath = toCheckTmpFilePath();
360                if (tmpLogicPath == null)
361                        return null;
362    
363                Path logicPath = Paths.get(tmpLogicPath);
364                DiskFile df = new DiskFile(tmpLogicPath);
365                DiskFile.copyAttrs(df, this.copyStructureOnly, this.syncRoot);
366                if (msg == null) {
367                        df.write(String.format("%s-%s", Thread.currentThread().getName(), Thread.currentThread().getId()),
368                                        false);
369                } else {
370                        df.write(msg, false);
371                }
372    
373                Files.setLastModifiedTime(logicPath, FileTime.fromMillis(Calendar.getInstance().getTimeInMillis()));
374                return df;
375        }
376                return null;
377        }
378
379        protected void removeLogicCheck() {
380            try{
381                String tmpPath = toCheckTmpFilePath();
382                if (tmpPath != null) {
383                        DiskFile df = new DiskFile(tmpPath);
384                        DiskFile.copyAttrs(df, this.copyStructureOnly, this.syncRoot);
385                        if (df.exists()) {
386                                df.delete();
387                                DiskFile pdf = new DiskFile(this.getOrigin().getParent());
388                                DiskFile.copyAttrs(pdf, this.copyStructureOnly, this.syncRoot);
389                                if(pdf.isLogicCheck()){
390                                    pdf.removeLogicCheck();
391                                }
392                        }
393                }
394            }catch(Exception e){
395                LoggerFactory.getLogger(DiskFile.class).warn(e.getMessage(), e);
396            }
397        }
398
399        @Override
400        public boolean beforeDelete(boolean realDeleted) {
401                return true;
402        }
403
404        @Override
405        public void afterDelete(boolean realDeleted) {
406
407        }
408
409        @Override
410        public boolean beforeMkdirs() {
411                return true;
412        }
413
414        @Override
415        public void afterMkdirs() {
416
417        }
418
419        @Override
420        public boolean beforeCreateLink(String target) {
421                return true;
422        }
423
424        @Override
425        public void afterCreateLink(String target) {
426
427        }
428
429        @Override
430        public boolean beforeWrite() {
431                return true;
432        }
433
434        @Override
435        public void afterWrite() {
436
437        }
438
439        @Override
440        public boolean delete() throws IOException {
441                return deleteFrom();
442        }
443
444        private boolean deleteFrom() throws IOException {
445                boolean allowed = beforeDelete(true);
446                if (allowed) {
447                        LoggerFactory.getLogger(DiskFile.class).debug("Delete: {}", getPath());
448                        if (isFile()) {
449                                Path originPath = Paths.get(origin.toURI());
450                                boolean exists = Files.exists(originPath);
451                                if (exists) {
452                                        Files.deleteIfExists(Paths.get(origin.toURI()));
453                                        afterDelete(true);
454                                        return true;
455                                }
456                        }
457                        if (isDir()) {
458                                Path originPath = Paths.get(origin.toURI());
459                                boolean exists = Files.exists(originPath);
460                                if (exists) {
461                                        Files.walk(originPath).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
462                                        afterDelete(true);
463                                        return true;
464                                }
465                        }
466                        if (isLink()) {
467                                Path originPath = Paths.get(origin.toURI());
468                                boolean exists = Files.exists(originPath);
469                                if (exists) {
470                                        Files.deleteIfExists(Paths.get(origin.toURI()));
471                                        afterDelete(true);
472                                        return true;
473                                }
474                        }
475                }
476                return false;
477        }
478
479        @Override
480        public boolean isFile() throws IOException {
481                return this.origin.isFile() && !isLink();
482        }
483
484        @Override
485        public boolean isDirectory() throws IOException {
486                return this.origin.isDirectory() && !isLink();
487        }
488
489        @Override
490        public boolean isLink() throws IOException {
491                return Files.isSymbolicLink(Paths.get(this.origin.toURI()));
492        }
493
494        @Override
495        public boolean exists() throws IOException {
496                return this.origin.exists();
497        }
498
499        @Override
500        public boolean mkdirs() throws IOException {
501                boolean allowed = beforeMkdirs();
502                if (allowed) {
503                        LoggerFactory.getLogger(DiskFile.class).debug("Mkidrs: {}", getPath());
504                        boolean b = this.origin.mkdirs();
505                        if (b && modifyTimeMs > 0) {
506                                setModifyTime(new Timestamp(modifyTimeMs));
507                        }
508                        afterMkdirs();
509                        return b;
510                }
511                return false;
512        }
513
514        @Override
515        public boolean write(byte[] data, boolean append) throws IOException {
516
517                boolean allowed = beforeWrite();
518
519                if (!allowed)
520                        return false;
521
522                if (data == null)
523                        data = new byte[] {};
524
525                try {
526
527                        OpenOption[] options = null;
528                        if (append) {
529                                options = new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.WRITE,
530                                                StandardOpenOption.APPEND };
531                        } else {
532                                options = new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.WRITE,
533                                                StandardOpenOption.TRUNCATE_EXISTING };
534                        }
535
536                        File parent = origin.getParentFile();
537                        if (!parent.exists()) {
538                                parent.mkdirs();
539                        }
540                        int retryCount = 0;
541                        Path path = Paths.get(origin.toURI());
542                        while(retryCount < RETRY_OPEN_LIMITED){
543                            retryCount++;
544                            try{
545                            channel = FileChannel.open(path, options);
546                            if(channel != null){
547                                lock = channel.lock();
548                                if (lock != null) {
549                                        ByteBuffer buf = ByteBuffer.wrap(data);
550                                        channel.write(buf);
551                                        if (modifyTimeMs > 0) {
552                                                setModifyTime(new Timestamp(modifyTimeMs));
553                                        }
554                                        return true;
555                                }
556                            }
557                            }catch(Exception e){
558                                LoggerFactory.getLogger(DiskFile.class).debug(e.getMessage(),e);
559                                
560                                if(retryCount >= RETRY_OPEN_LIMITED) throw new IOException(e.getMessage(),e);
561                                
562                            }
563                        }
564                        return false;
565                } catch (Exception e) {
566                        throw e;
567                } finally {
568                        close();
569                }
570        }
571
572        public boolean manualOpen(boolean append) throws IOException, FileAlreadyExistsException {
573
574                boolean allowed = beforeWrite();
575
576                if (!allowed)
577                        return false;
578
579                OpenOption[] options = null;
580                if (append) {
581                        options = new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.WRITE,
582                                        StandardOpenOption.APPEND };
583                } else {
584                        options = new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.WRITE,
585                                        StandardOpenOption.TRUNCATE_EXISTING };
586                }
587                Path path = Paths.get(origin.toURI());
588                File parent = origin.getParentFile();
589                if (parent != null && !parent.exists() && !parent.mkdirs()) {
590                        throw new IOException(String.format("Failed to create parent directory: %s", parent));
591                }
592                if (!Files.exists(path)) {
593                        try {
594                                Files.createFile(path);
595                        } catch (FileAlreadyExistsException e) {
596                                throw e;
597                        }
598                }
599                int retryCount = 0;
600                while(retryCount < RETRY_OPEN_LIMITED){
601                    retryCount++;
602                    try{
603                        channel = FileChannel.open(path, options);
604                        lock = channel.lock();
605                        return lock != null;
606                    }catch(Exception e){
607                        LoggerFactory.getLogger(DiskFile.class).debug(e.getMessage(),e);
608                        
609                        if(retryCount >= RETRY_OPEN_LIMITED) throw new IOException(e.getMessage(),e);
610                    }
611                }
612                return false;
613        }
614
615        public void manualWrite(byte[] data) throws IOException {
616                if (data == null)
617                        data = new byte[] {};
618
619                if (lock != null) {
620                        ByteBuffer buf = ByteBuffer.wrap(data);
621                        channel.write(buf);
622                        if (modifyTimeMs > 0) {
623                                setModifyTime(new Timestamp(modifyTimeMs));
624                        }
625                }
626        }
627
628        public void manualWrite(String data) throws IOException {
629                manualWrite(data.getBytes(CHARSET));
630        }
631
632        public void manualClose() throws IOException {
633                close();
634        }
635
636        @Override
637        public boolean write(byte[] data) throws IOException {
638                return write(data, false);
639        }
640
641        @Override
642        public boolean write(String data, boolean append) throws IOException {
643                return write(data.getBytes(CHARSET), append);
644        }
645
646        public boolean write(String data, String charset, boolean append) throws IOException {
647                return write(data.getBytes(charset), append);
648        }
649
650        @Override
651        public boolean write(String data) throws IOException {
652                return write(data.getBytes(CHARSET));
653        }
654
655        public boolean write(String data, String charset) throws IOException {
656                return write(data.getBytes(charset));
657        }
658
659        @Override
660        public boolean createLink(String target) throws IOException {
661                boolean allowed = beforeCreateLink(target);
662                if (allowed) {
663                        LoggerFactory.getLogger(DiskFile.class).debug("CreateLink: {}", getPath());
664                        try {
665                                Path link = Paths.get(origin.toURI());
666                                Path targetPath = Paths.get(target);
667                                Files.createSymbolicLink(link, targetPath);
668                                if (modifyTimeMs > 0) {
669                                        setModifyTime(new Timestamp(modifyTimeMs));
670                                }
671                                afterCreateLink(target);
672                                return true;
673                        } catch (IOException e) {
674                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
675                        }
676                }
677                return false;
678        }
679
680        @Override
681        public String readLink() throws IOException {
682                Path link = Paths.get(origin.toURI());
683                return Files.readSymbolicLink(link).toString();
684        }
685
686        @Override
687        public byte[] readAllBytes() throws IOException {
688                return readAllBytesFrom();
689        }
690
691        private byte[] readAllBytesFrom() throws IOException {
692                if (isLink()) {
693                        throw new IOException(String.format("The disk file '%s' is a link.", origin.getAbsolutePath()));
694                }
695                if (isDir()) {
696                        throw new IOException(String.format("The disk file '%s' is a folder.", origin.getAbsolutePath()));
697                }
698                if (!exists()) {
699                        throw new IOException(String.format("The disk file '%s' does not exist.", origin.getAbsolutePath()));
700                }
701                return Files.readAllBytes(Paths.get(getPath()));
702        }
703
704        @Override
705        public String readAllString() throws IOException {
706                return readAllString(BaseFile.CHARSET);
707        }
708
709        public String readAllString(String charset) throws IOException {
710                ByteArrayOutputStream baos = null;
711                try {
712                        byte[] bytes = readAllBytes();
713                        baos = new ByteArrayOutputStream();
714                        baos.write(bytes);
715                        baos.flush();
716                        return baos.toString(charset);
717                } finally {
718                        if (baos != null) {
719                                try {
720                                        baos.close();
721                                } catch (IOException e) {
722                                        Logger.systemError(DiskFile.class, e.getMessage(), e);
723                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
724                                }
725                        }
726                }
727        }
728
729        @Override
730        public long size() throws IOException {
731                if (isFile()) {
732                        Path path = Paths.get(origin.getAbsolutePath());
733                        return Files.size(path);
734                }
735                return 0L;
736        }
737
738        private void close() {
739                if (lock != null) {
740                        try {
741                                lock.release();
742                        } catch (IOException e) {
743                                Logger.systemError(DiskFile.class, e.getMessage(), e);
744                        }
745                        try {
746                                lock.close();
747                        } catch (IOException e) {
748                                Logger.systemError(DiskFile.class, e.getMessage(), e);
749                        } finally {
750                                lock = null;
751                        }
752                }
753
754                if (channel != null) {
755                        try {
756                                channel.close();
757                        } catch (IOException e) {
758                                Logger.systemError(DiskFile.class, e.getMessage(), e);
759                        } finally {
760                                channel = null;
761                        }
762                }
763                afterWrite();
764        }
765
766        @Override
767        public boolean complete() throws IOException {
768                close();
769                return true;
770        }
771
772        public void setModifyTimeMs(long modifyTimeMs) throws IOException {
773                this.modifyTimeMs = modifyTimeMs;
774        }
775
776        public void setModifyTimeMsFromClock(long modifyTimeMsFromClock) throws IOException {
777                String remoteTzId = new Clock().getCalendar().getTimeZone().getID();
778                String localTzId = Calendar.getInstance().getTimeZone().getID();
779                Timestamp fileLocalTimestamp = timeZoneConver(localTzId, remoteTzId, new Timestamp(modifyTimeMsFromClock));
780                this.modifyTimeMs = fileLocalTimestamp.getTime();
781        }
782
783        @Override
784        public void setModifyTime(Timestamp modifyTime) throws IOException {
785                if (exists()) {
786                        Path originPath = Paths.get(origin.toURI());
787                        Files.setLastModifiedTime(originPath, FileTime.fromMillis(modifyTime.getTime()));
788                }
789        }
790
791        public void setModifyTimeFromClock(Timestamp modifyTimeFromClock) throws IOException {
792                String remoteTzId = new Clock().getCalendar().getTimeZone().getID();
793                String localTzId = Calendar.getInstance().getTimeZone().getID();
794                Timestamp fileLocalTimestamp = timeZoneConver(localTzId, remoteTzId, modifyTimeFromClock);
795                setModifyTime(fileLocalTimestamp);
796        }
797
798        @Override
799        public Timestamp getModifyTime() throws IOException {
800                Path originPath = Paths.get(origin.toURI());
801                if (Files.exists(originPath)) {
802                        BasicFileAttributes attr = Files.readAttributes(originPath, BasicFileAttributes.class);
803                        FileTime fileTime = attr.lastModifiedTime();
804                        Timestamp fileLocalTimestamp = new Timestamp(fileTime.toMillis());
805                        return fileLocalTimestamp;
806                }
807                return null;
808        }
809
810        public Timestamp getModifyTimeForClock() throws IOException {
811                Path originPath = Paths.get(origin.toURI());
812                if (Files.exists(originPath)) {
813                        BasicFileAttributes attr = Files.readAttributes(originPath, BasicFileAttributes.class);
814                        FileTime fileTime = attr.lastModifiedTime();
815                        Timestamp fileLocalTimestamp = new Timestamp(fileTime.toMillis());
816                        String remoteTzId = new Clock().getCalendar().getTimeZone().getID();
817                        String localTzId = Calendar.getInstance().getTimeZone().getID();
818                        Timestamp fileRemoteTimestamp = timeZoneConver(remoteTzId, localTzId, fileLocalTimestamp);
819                        return fileRemoteTimestamp;
820                }
821                return null;
822        }
823
824        public void startFullAsync(long timerMs, String replaceTo) throws IOException {
825                startFullAsync(timerMs, getPath() + "/", replaceTo, null, null);
826        }
827
828        public void startFullAsync(long timerMs, String replaceTo, Runnable completedCallback) throws IOException {
829                startFullAsync(timerMs, getPath() + "/", replaceTo, completedCallback, null);
830        }
831
832        public void startFullAsync(long timerMs, String replaceTo, Runnable completedCallback,
833                        Runnable checkDeleteCompletedCallback) throws IOException {
834                startFullAsync(timerMs, getPath() + "/", replaceTo, completedCallback, checkDeleteCompletedCallback);
835        }
836
837        private void startFullAsync(long timerMs, String replaceRootPath, String replaceTo, Runnable completedCallback,
838                        Runnable checkDeleteCompletedCallback) throws IOException {
839
840                if (!RemoteFile.FIRST_LOADED) {
841                        RemoteFile firstLoaded = new RemoteFile(replaceTo);
842                }
843
844                Runnable runnable = new Runnable() {
845                        @Override
846                        public void run() {
847                                try {
848                                        startFullAsyncThread(timerMs, replaceRootPath, replaceTo, completedCallback);
849                                } catch (Exception e) {
850                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
851                                }
852                        }
853                };
854                Executors.newFixedThreadPool(1).execute(runnable);
855
856                runnable = new Runnable() {
857                        @Override
858                        public void run() {
859                                try {
860                                        startFullSyncForDelete(-1, timerMs, replaceTo, replaceRootPath, checkDeleteCompletedCallback);
861                                } catch (Exception e) {
862                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
863                                }
864                        }
865                };
866                Executors.newFixedThreadPool(1).execute(runnable);
867        }
868
869        private synchronized void startFullSyncForDelete(final Integer filePartDataTable, final long timerMs,
870                        final String replaceTo, final String replaceRootPath, final Runnable completedCallback) throws IOException {
871                DiskFile the = this;
872                RemoteFile rootDir = null;
873                if (!CommonTools.isBlank(replaceTo)) {
874                        rootDir = new RemoteFile(replaceTo);
875                }
876                if (rootDir == null) {
877                        throw new IOException("The root folder is null.");
878                }
879                RemoteFile _rootDir = rootDir;
880                Integer maxDataTable = rootDir.getMaxFilePartDataTable();
881
882                Integer _filePartDataTable = filePartDataTable;
883
884                if (filePartDataTable > maxDataTable)
885                        _filePartDataTable = -1;
886
887                final Integer finalFilePartDataTable = _filePartDataTable;
888
889                final CacheArray rows = new CacheArray();
890
891                rows.filter(getScanDeleteFilter(rootDir, finalFilePartDataTable, timerMs, replaceTo, replaceRootPath,
892                                completedCallback));
893
894                Runnable runnable = new Runnable() {
895                        @Override
896                        public void run() {
897                                try {
898                                        _rootDir.listAllForScanDelete(finalFilePartDataTable, rows);
899                                } catch (Exception e) {
900                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
901                                }
902                        }
903                };
904                Executors.newFixedThreadPool(1).execute(runnable);
905        }
906
907        private CacheArrayFilter getScanDeleteFilter(final RemoteFile rootDir, final Integer filePartDataTable,
908                        final long timerMs, final String replaceTo, final String replaceRootPath,
909                        final Runnable completedCallback) {
910                DiskFile the = this;
911                CacheArrayFilter filter = new CacheArrayFilter(CACHE_ARRAY_FILTER_TIMER) {
912                        @Override
913                        public void execute(Integer index, Object o) {
914
915                                try {
916                                        Map<String, Object> item = (Map<String, Object>) o;
917                                        Object id = item.get("id");
918                                        String parent = (String) item.get("file_parent_path");
919                                        String fileName = (String) item.get("file_name");
920                                        String pf = String.format("%s/%s", parent, fileName);
921                                        RemoteFile rf = new RemoteFile(pf);
922                                        Thread.currentThread()
923                                                        .setName(String.format("DiskFile-startFullSyncForDelete-(DataTable=%s Index=%s)-%s",
924                                                                        filePartDataTable, index, fileName));
925
926                                        if (memoryUsage() < RemoteFile.MAX_MEMORY_USAGE) {
927                                                String toDf = pf;
928                                                if (!CommonTools.isBlank(replaceRootPath) && !CommonTools.isBlank(replaceTo)) {
929                                                        toDf = pf.replaceFirst(replaceTo + "[/]*", replaceRootPath + "/");
930                                                }
931
932                                                DiskFile df = new DiskFile(toDf);
933                                                DiskFile.copyAttrs(df, the.copyStructureOnly, the.syncRoot);
934                                                boolean isDebug = isDebug(rf);
935                                                boolean isRoot = rootDir.getPath().equals(rf.getPath());
936                                                boolean isSyncedHost = rf.isSyncedOnHostname(id);
937                                                boolean rfExists = rf.exists();
938
939                                                boolean allowedForceDelete = rootDir.exists() && !isRoot && isSyncedHost && rfExists
940                                                                && !df.exists() && !df.isLogicModify();
941
942                                                if (allowedForceDelete) {
943                                                        df.logicModify();
944                                                        if (isDebug) {
945                                                                LoggerFactory.getLogger(DiskFile.class).mark("ForceRemoteDelete - {}", rf.getPath());
946                                                        }
947                                                        rf.forceDeleteAll(true);
948                                                        df.removeLogicModify();
949                                                }
950                                        } else {
951                                                LoggerFactory.getLogger(DiskFile.class).warn("Over max memory usage 'MAX_MEMORY_USAGE={}'.",
952                                                                RemoteFile.MAX_MEMORY_USAGE);
953                                        }
954                                } catch (Exception e) {
955                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
956                                }
957                        }
958
959                        @Override
960                        public void completed(Integer size) {
961                                if (isStopSync()) {
962                                        LoggerFactory.getLogger(DiskFile.class).mark("Stoped start full sync for delete.");
963                                }
964                                if (timerMs <= 0) {
965                                        LoggerFactory.getLogger(DiskFile.class).mark("Completed start full sync for delete.");
966                                }
967
968                                boolean isRootCompleted = the.getPath().equals(new DiskFile(replaceRootPath).getPath());
969
970                                if (isRootCompleted && completedCallback != null) {
971                                        completedCallback.run();
972                                }
973
974                                if (!isStopSync() && timerMs > 0) {
975                                        while (true) {
976
977                                                if (isStopSync())
978                                                        break;
979
980                                                try {
981                                                        Thread.sleep(timerMs);
982                                                } catch (InterruptedException e) {
983                                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
984                                                }
985                                                boolean allowed = checkUsage(rootDir);
986                                                if (allowed) {
987                                                        try {
988                                                                if (!isStopSync()) {
989                                                                        startFullSyncForDelete(filePartDataTable + 1, timerMs, replaceTo, replaceRootPath,
990                                                                                        completedCallback);
991                                                                }
992                                                        } catch (IOException e) {
993                                                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
994                                                        }
995                                                        break;
996                                                }
997                                        }
998                                }
999                        }
1000                };
1001                return filter;
1002        }
1003
1004        private void startFullAsyncThread(final long timerMs, String replaceRootPath, String replaceTo,
1005                        final Runnable completedCallback) {
1006                stopSync = false;
1007                this.setCopyStructureOnly(RemoteFile.COPY_STRUCTURE_ONLY);
1008                final DiskFile the = this;
1009                Thread.currentThread().setName(String.format("DiskFile-startFullAsyncThread-%s", the.getOrigin().getName()));
1010                replaceRootPath = replacePath(replaceRootPath);
1011                replaceTo = replacePath(replaceTo);
1012                DiskFile rootDisk = new DiskFile(replaceRootPath);
1013                boolean rootExists = false;
1014                try {
1015                        rootExists = rootDisk.exists();
1016                } catch (Exception e) {
1017                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1018                }
1019                if (!rootExists) {
1020                        Logger.systemError(DiskFile.class, "The root path '{}' does not exist.", rootDisk.getPath());
1021                }
1022                boolean isRoot = the.getPath().equals(rootDisk.getPath());
1023                LoggerFactory.getLogger(DiskFile.class).debug("IsRoot: {} - {}", isRoot, the.getPath());
1024                LoggerFactory.getLogger(DiskFile.class).debug("CacheArrayUsage: {}", CacheArray.getUsage());
1025                if (rootExists) {
1026
1027                        DirectoryStream<Path> stream = null;
1028                        try {
1029                                if (syncRoot == null) {
1030                                        File replaceRootFile = new File(replaceRootPath);
1031                                        if (replaceRootFile.getParent() == null) {
1032                                                throw new IOException("Cannot sync the root path.");
1033                                        }
1034                                        String replaceRootParent = replacePath(replaceRootFile.getParent());
1035                                        syncRoot = new File(String.format("%s/%s", replaceRootParent, replaceRootFile.getName()));
1036                                }
1037
1038                                if (!CommonTools.isBlank(replaceTo)) {
1039                                        RemoteFile rootDir = new RemoteFile(replaceTo);
1040                                        if (!rootDir.exists()) {
1041                                                rootDir.setModifyTime(this.getModifyTimeForClock());
1042                                                rootDir.mkdirs();
1043                                        }
1044                                }
1045                                Path originPath = Paths.get(origin.toURI());
1046                                CacheArray rows = new CacheArray();
1047                                CacheArrayFilter filter = getFullAsyncFilter(the, timerMs, replaceRootPath, replaceTo,
1048                                                completedCallback);
1049                                rows.filter(filter);
1050                                stream = Files.newDirectoryStream(originPath);
1051                                for (Path p : stream) {
1052                                        if (p != null) {
1053                                                File f = p.toFile();
1054                                                boolean isFile = f.isFile();
1055                                                if (isFile) {
1056                                                        boolean isWritingDf = f.getName().matches(TMP_WRITING_SWP);
1057                                                        if (isWritingDf) {
1058                                                                BasicFileAttributes attributes = Files.readAttributes(p, BasicFileAttributes.class);
1059                                                                FileTime creationTime = attributes.creationTime();
1060                                                                boolean timeouted = (Calendar.getInstance().getTimeInMillis()
1061                                                                                - creationTime.toMillis()) > (RemoteFile.BATCH_SIZE * LOGIC_TIMEOUT_MS);
1062                                                                if (timeouted) {
1063                                                                        try {
1064                                                                                boolean exists = Files.exists(p);
1065                                                                                if (exists) {
1066                                                                                        Files.deleteIfExists(p);
1067                                                                                }
1068                                                                        } catch (Exception e) {
1069                                                                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1070                                                                        }
1071                                                                }
1072                                                        }
1073                                                }
1074                                                boolean allowedPath = the.checkSyncPathAvailable(p.toString());
1075                                                if (!allowedPath) {
1076                                                        LoggerFactory.getLogger(DiskFile.class).debug("Ignored: {}", p.toString());
1077                                                }
1078                                                if (allowedPath) {
1079                                                        rows.add(p);
1080                                                }
1081                                        }
1082                                }
1083                                rows.add(null);
1084                        } catch (Exception e) {
1085                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1086                                removeLogicAccess();
1087                                if (!(e instanceof NoSuchFileException)) {
1088                                        if (!isStopSync() && timerMs > 0) {
1089                                                try {
1090                                                        Thread.sleep(timerMs);
1091                                                } catch (InterruptedException ee) {
1092                                                        LoggerFactory.getLogger(DiskFile.class).error(ee.getMessage(), ee);
1093                                                }
1094                                                if (!isStopSync()) {
1095                                                        startFullAsyncThread(timerMs, replaceRootPath, replaceTo, completedCallback);
1096                                                }
1097                                        }
1098                                }
1099                        } finally {
1100                                if (stream != null) {
1101                                        try {
1102                                                stream.close();
1103                                        } catch (IOException e) {
1104                                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1105                                        }
1106                                }
1107                        }
1108                }
1109        }
1110
1111        private CacheArrayFilter getFullAsyncFilter(DiskFile the, long timerMs, String replaceRootPath, String replaceTo,
1112                        Runnable completedCallback) {
1113
1114                return new CacheArrayFilter(CACHE_ARRAY_FILTER_TIMER) {
1115                        RemoteFile rf = null;
1116                        final CacheArrayFilter theFilter = this;
1117
1118                        @Override
1119                        public void completed(Integer size) {
1120                                boolean isRootCompleted = the.getPath().equals(new DiskFile(replaceRootPath).getPath());
1121
1122                                if (isRootCompleted) {
1123                                        try {
1124                                                if (rf != null && the.isStopSync() && isDebug(rf)) {
1125                                                        LoggerFactory.getLogger(DiskFile.class).mark("StopedRootScan - {}", the.getPath());
1126                                                }
1127                                                if (!the.isStopSync() && timerMs > 0) {
1128                                                        Thread.sleep(timerMs);
1129                                                        if (!the.isStopSync()) {
1130                                                                if (rf != null && isDebug(rf)) {
1131                                                                        LoggerFactory.getLogger(DiskFile.class).mark("RootScaning - {}", the.getPath());
1132                                                                }
1133                                                                the.startFullAsyncThread(timerMs, replaceRootPath, replaceTo, completedCallback);
1134                                                        }
1135                                                }
1136                                                if (completedCallback != null) {
1137                                                        completedCallback.run();
1138                                                }
1139                                        } catch (Exception e) {
1140                                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1141                                        }
1142                                }
1143                        }
1144
1145                        @Override
1146                        public void execute(Integer index, Object o) {
1147                                DiskFile df = null;
1148                                try {
1149                                        Path p = (Path) o;
1150                                        String pf = replacePath(p.toFile().getAbsolutePath());
1151                                        Thread.currentThread().setName(
1152                                                        String.format("DiskFile-getFullAsyncFilter-%s", p.toFile().getParentFile().getName()));
1153                                        String toRf = pf;
1154                                        if (!CommonTools.isBlank(replaceRootPath) && !CommonTools.isBlank(replaceTo)) {
1155                                                toRf = pf.replaceFirst(replaceRootPath + "[/]*", replaceTo + "/");
1156                                        }
1157
1158                                        df = new DiskFile(pf);
1159                                        DiskFile.copyAttrs(df, the.copyStructureOnly, the.syncRoot);
1160                                        rf = new RemoteFile(toRf);
1161
1162                                        boolean isRoot = the.getPath().equals(new DiskFile(replaceRootPath).getPath());
1163
1164                                        boolean allowedConn = checkUsage(rf);
1165                                        String fileName = p.toFile().getName();
1166                                        boolean isDebug = isDebug(rf);
1167
1168                                        boolean timeouted = rf.isTimeout();
1169                                        if (timeouted && RemoteFile.addQueuePathMapping(rf.getPath()) && allowedConn) {
1170                                                if (isDebug) {
1171                                                        LoggerFactory.getLogger(DiskFile.class).mark("ForceDelete(Timeouted) - {}", rf.getPath());
1172                                                }
1173                                                rf.forceDeleteFile(false);
1174                                                RemoteFile.removeQueuePathMapping(rf.getPath());
1175                                        }
1176                                        Timestamp modifyTime = df.getModifyTimeForClock();
1177                                        boolean allowedModifyTime = modifyTime != null;
1178                                        boolean allowed = DiskFile.checkQueuePathMapping() && !isStopSync() && allowedModifyTime
1179                                                        && allowedConn;
1180
1181                                        if (isDebug) {
1182                                                debugLog("CheckUsage", rf, this);
1183                                        }
1184
1185                                        if (!isRoot && allowed) {
1186                                                allowed = !df.isLogicCheck();
1187                                        }
1188
1189                                        if (allowed) {
1190
1191                                                if (!isRoot)
1192                                                        df.logicCheck();
1193
1194                                                rf.setModifyTime(modifyTime);
1195                                                long dfModifyTimeMs = modifyTime.getTime();
1196                                                boolean blocked = false;
1197                                                boolean isRealDelete = rf.isLastOperateRealDelete();
1198
1199                                                if (isRealDelete) {
1200                                                        String clientHostname = CommonTools.getHostname();
1201                                                        String delHostname = rf.getLastSourceHostname();
1202                                                        boolean isOwnerDel = clientHostname.equals(delHostname);
1203                                                        if (isOwnerDel) {
1204                                                                Timestamp deletedTime = rf.getLastOperateTime();
1205                                                                if (deletedTime != null) {
1206                                                                        boolean waiting = new Clock().getTime() - deletedTime.getTime() <= LOGIC_TIMEOUT_MS;
1207                                                                        if (waiting) {
1208                                                                                blocked = true;
1209                                                                        }
1210                                                                }
1211                                                        } else {
1212                                                                Timestamp deletedTime = rf.getLastOperateTime();
1213                                                                if (deletedTime != null) {
1214                                                                        boolean olded = modifyTime.getTime() < deletedTime.getTime();
1215                                                                        if (olded) {
1216                                                                                blocked = true;
1217                                                                        }
1218                                                                }
1219                                                        }
1220                                                }
1221
1222                                                boolean haveParent = rf.getParentFile().exists();
1223                                                if (!blocked && haveParent) {
1224                                                        if (df.isLink()) {
1225                                                                if (!df.isLogicAccess()) {
1226                                                                        df.logicAccess();
1227                                                                        if (rf.exists()) {
1228                                                                                if (rf.isLink()) {
1229                                                                                        long rfModifyTimeMs = rf.getModifyTime().getTime();
1230                                                                                        boolean changedTime = dfModifyTimeMs > rfModifyTimeMs;
1231                                                                                        boolean changedValue = !rf.readLink().equals(df.readLink());
1232                                                                                        boolean changed = changedTime && changedValue;
1233                                                                                        if (changed) {
1234                                                                                                RemoteFile prf = rf.getParentFile();
1235                                                                                                if (prf != null && prf.exists()) {
1236                                                                                                        if (isDebug) {
1237                                                                                                                LoggerFactory.getLogger(DiskFile.class).mark("CreateLink - {}",
1238                                                                                                                                rf.getPath());
1239                                                                                                        }
1240                                                                                                        rf.forceDeleteLink(false);
1241                                                                                                        rf.createLink(df.readLink());
1242                                                                                                }
1243                                                                                        }
1244                                                                                } else {
1245                                                                                        RemoteFile prf = rf.getParentFile();
1246                                                                                        if (prf != null && prf.exists()) {
1247                                                                                                if (isDebug) {
1248                                                                                                        LoggerFactory.getLogger(DiskFile.class).mark("CreateLink - {}",
1249                                                                                                                        rf.getPath());
1250                                                                                                }
1251                                                                                                rf.forceDeleteLink(false);
1252                                                                                                rf.createLink(df.readLink());
1253                                                                                        }
1254                                                                                }
1255                                                                        } else {
1256                                                                                RemoteFile prf = rf.getParentFile();
1257                                                                                if (prf != null && prf.exists()) {
1258                                                                                        if (isDebug) {
1259                                                                                                LoggerFactory.getLogger(DiskFile.class).mark("CreateLink - {}",
1260                                                                                                                rf.getPath());
1261                                                                                        }
1262                                                                                        rf.createLink(df.readLink());
1263                                                                                }
1264                                                                        }
1265                                                                }
1266                                                        } else if (df.isFile()) {
1267                                                                if (!df.isLogicModify() && !df.isLogicAccess()) {
1268                                                                        if (rf.exists()) {
1269                                                                                if (rf.isFile()) {
1270                                                                                        long rfModifyTimeMs = rf.getModifyTime().getTime();
1271                                                                                        if (dfModifyTimeMs > rfModifyTimeMs) {
1272                                                                                                RemoteFile prf = rf.getParentFile();
1273                                                                                                if (prf != null && prf.exists()) {
1274                                                                                                        if (DiskFile.addQueuePathMapping(df.getPath())) {
1275                                                                                                                if (isDebug) {
1276                                                                                                                        LoggerFactory.getLogger(DiskFile.class)
1277                                                                                                                                        .mark("RemoteQueuing - {}", df.getPath());
1278                                                                                                                }
1279                                                                                                                df.logicAccess();
1280                                                                                                                rf.forceDeleteFile(false);
1281                                                                                                                df.writeToRemote(rf);
1282                                                                                                        } else {
1283                                                                                                                df.removeLogicAccess();
1284                                                                                                                df.removeLogicCheck();
1285                                                                                                        }
1286                                                                                                }
1287                                                                                        }
1288                                                                                } else {
1289                                                                                        rf.forceDeleteFile(false);
1290                                                                                }
1291                                                                        } else {
1292                                                                                RemoteFile prf = rf.getParentFile();
1293                                                                                if (prf != null && prf.exists()) {
1294                                                                                        if (DiskFile.addQueuePathMapping(df.getPath())) {
1295                                                                                                if (isDebug) {
1296                                                                                                        LoggerFactory.getLogger(DiskFile.class).mark("RemoteQueuing - {}",
1297                                                                                                                        df.getPath());
1298                                                                                                }
1299                                                                                                df.logicAccess();
1300                                                                                                df.writeToRemote(rf);
1301                                                                                        } else {
1302                                                                                                df.removeLogicAccess();
1303                                                                                                df.removeLogicCheck();
1304                                                                                        }
1305                                                                                }
1306                                                                        }
1307                                                                }
1308                                                        } else if (df.isDir()) {
1309                                                                if (isStopSync() && isDebug) {
1310                                                                        LoggerFactory.getLogger(DiskFile.class).mark("Stopping queue");
1311                                                                }
1312                                                                if (!isStopSync()) {
1313                                                                        boolean allowedPoolSize = theFilter.getCacheArray().getUsingPoolSize() < theFilter
1314                                                                                        .getCacheArray().getMaxPoolSize() && checkUsage(rf);
1315                                                                        if (isDebug && allowedPoolSize) {
1316                                                                                LoggerFactory.getLogger(DiskFile.class).debug("Scaning - {}", df.getPath());
1317                                                                        }
1318                                                                        if (isDebug && !allowedPoolSize) {
1319                                                                                LoggerFactory.getLogger(DiskFile.class).debug("Skiped - {}", df.getPath());
1320                                                                        }
1321                                                                        if (rf.exists()) {
1322                                                                                if (rf.isDir()) {
1323                                                                                        if (isRoot || allowedPoolSize) {
1324                                                                                                df.startFullAsyncThread(timerMs, replaceRootPath, replaceTo,
1325                                                                                                                completedCallback);
1326                                                                                        }
1327                                                                                } else {
1328                                                                                        if (rf.isDir())
1329                                                                                                rf.forceDeleteDir(false);
1330
1331                                                                                        if (rf.isFile())
1332                                                                                                rf.forceDeleteFile(false);
1333
1334                                                                                        if (rf.isLink())
1335                                                                                                rf.forceDeleteLink(false);
1336
1337                                                                                        RemoteFile prf = rf.getParentFile();
1338                                                                                        if (isRoot || (prf != null && prf.exists())) {
1339                                                                                                if (isDebug) {
1340                                                                                                        LoggerFactory.getLogger(DiskFile.class)
1341                                                                                                                        .mark("ReCreateRemoteDir - {}", rf.getPath());
1342                                                                                                }
1343                                                                                                rf.mkdirs();
1344                                                                                                if (isRoot || allowedPoolSize) {
1345                                                                                                        df.startFullAsyncThread(timerMs, replaceRootPath, replaceTo,
1346                                                                                                                        completedCallback);
1347                                                                                                }
1348                                                                                        }
1349                                                                                }
1350                                                                        } else {
1351                                                                                RemoteFile prf = rf.getParentFile();
1352                                                                                if (isRoot || (prf != null && prf.exists())) {
1353                                                                                        if (isDebug) {
1354                                                                                                LoggerFactory.getLogger(DiskFile.class).mark("CreateRemoteDir - {}",
1355                                                                                                                rf.getPath());
1356                                                                                        }
1357                                                                                        rf.mkdirs();
1358                                                                                        if (isRoot || allowedPoolSize) {
1359                                                                                                df.startFullAsyncThread(timerMs, replaceRootPath, replaceTo,
1360                                                                                                                completedCallback);
1361                                                                                        }
1362                                                                                }
1363                                                                        }
1364                                                                        if (!isRoot && !allowedPoolSize) {
1365                                                                                df.removeLogicCheck();
1366                                                                        }
1367                                                                }
1368                                                        }
1369                                                }
1370                                        }
1371                                } catch (Exception e) {
1372                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1373                                        if (df != null) {
1374                                                df.removeLogicAccess();
1375                                        }
1376                                }
1377                        }
1378
1379                };
1380        }
1381
1382        public void setCopyStructureOnly(boolean copyStructureOnly) {
1383                this.copyStructureOnly = copyStructureOnly;
1384        }
1385
1386        public boolean isCopyStructureOnly() {
1387                return this.copyStructureOnly;
1388        }
1389
1390        private boolean isViEditSwp(String fileName) {
1391                return fileName.matches(TMP_MATCHES_EDIT) || fileName.matches(TMP_MATCHES_VI_SWP);
1392        }
1393
1394        protected static DiskFile copyAttrs(DiskFile source, boolean _copyStructureOnly, File _syncRoot) {
1395                source.setCopyStructureOnly(_copyStructureOnly);
1396                source.syncRoot = _syncRoot;
1397                return source;
1398        }
1399
1400        public File getParentFile() {
1401                return new File(getParent());
1402        }
1403
1404        protected boolean checkSyncPathAvailable(String path) {
1405                try {
1406                        return checkSyncPathAvailable(path, RemoteFile.SHOW_HIDDEN, RemoteFile.SHOW_LINK, RemoteFile.MAX_FILE_SZIE,
1407                                        SYNC_PATH_ALLOWED, SYNC_PATH_IGNORED);
1408                } catch (Exception e) {
1409                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1410                        return false;
1411                }
1412        }
1413
1414        protected static synchronized boolean addQueuePathMapping(String path) {
1415                Long addTimeMs = null;
1416                boolean allowed = checkQueuePathMapping();
1417
1418                if (!allowed)
1419                        return false;
1420
1421                addTimeMs = QUEUE_PATH_MAPPING.get(path);
1422
1423                if (addTimeMs == null) {
1424                        QUEUE_PATH_MAPPING.put(path, System.currentTimeMillis());
1425                        return true;
1426                }
1427
1428                return false;
1429        }
1430
1431        public static synchronized boolean checkQueuePathMapping() {
1432                Long addTimeMs = null;
1433                Map<String, Long> copyQueueMap = getQueuePathMapping();
1434                List<String> keyList = new ArrayList<String>(copyQueueMap.keySet());
1435                for (String key : keyList) {
1436                        addTimeMs = copyQueueMap.get(key);
1437                        if (addTimeMs != null) {
1438                                boolean timeout = (System.currentTimeMillis() - addTimeMs) >= LOGIC_TIMEOUT_MS;
1439
1440                                if (timeout) {
1441                                        QUEUE_PATH_MAPPING.remove(key);
1442                                }
1443                        }
1444                }
1445
1446                if (QUEUE_PATH_MAPPING.keySet().size() >= RemoteFile.MAX_QUEUE_SIZE)
1447                        return false;
1448
1449                return true;
1450        }
1451
1452        protected static synchronized void removeQueuePathMapping(String path) {
1453                QUEUE_PATH_MAPPING.remove(path);
1454        }
1455
1456        public static synchronized Map<String, Long> getQueuePathMapping() {
1457                return Collections.synchronizedMap(QUEUE_PATH_MAPPING);
1458        }
1459
1460        private static void debugLog(String point, RemoteFile rf, CacheArrayFilter filter) {
1461                debugLog(DiskFile.class, point, rf, filter);
1462        }
1463
1464}