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                                lock = channel.lock();
547                                if (lock != null) {
548                                        ByteBuffer buf = ByteBuffer.wrap(data);
549                                        channel.write(buf);
550                                        if (modifyTimeMs > 0) {
551                                                setModifyTime(new Timestamp(modifyTimeMs));
552                                        }
553                                        return true;
554                                }
555                            }catch(Exception e){
556                                LoggerFactory.getLogger(DiskFile.class).debug(e.getMessage(),e);
557                                
558                                if(retryCount >= RETRY_OPEN_LIMITED) throw new IOException(e.getMessage(),e);
559                                
560                            }
561                        }
562                        return false;
563                } catch (Exception e) {
564                        throw e;
565                } finally {
566                        close();
567                }
568        }
569
570        public boolean manualOpen(boolean append) throws IOException, FileAlreadyExistsException {
571
572                boolean allowed = beforeWrite();
573
574                if (!allowed)
575                        return false;
576
577                OpenOption[] options = null;
578                if (append) {
579                        options = new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.WRITE,
580                                        StandardOpenOption.APPEND };
581                } else {
582                        options = new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.WRITE,
583                                        StandardOpenOption.TRUNCATE_EXISTING };
584                }
585                Path path = Paths.get(origin.toURI());
586                File parent = origin.getParentFile();
587                if (parent != null && !parent.exists() && !parent.mkdirs()) {
588                        throw new IOException(String.format("Failed to create parent directory: %s", parent));
589                }
590                if (!Files.exists(path)) {
591                        try {
592                                Files.createFile(path);
593                        } catch (FileAlreadyExistsException e) {
594                                throw e;
595                        }
596                }
597                int retryCount = 0;
598                while(retryCount < RETRY_OPEN_LIMITED){
599                    retryCount++;
600                    try{
601                        channel = FileChannel.open(path, options);
602                        lock = channel.lock();
603                        return lock != null;
604                    }catch(Exception e){
605                        LoggerFactory.getLogger(DiskFile.class).debug(e.getMessage(),e);
606                        
607                        if(retryCount >= RETRY_OPEN_LIMITED) throw new IOException(e.getMessage(),e);
608                    }
609                }
610                return false;
611        }
612
613        public void manualWrite(byte[] data) throws IOException {
614                if (data == null)
615                        data = new byte[] {};
616
617                if (lock != null) {
618                        ByteBuffer buf = ByteBuffer.wrap(data);
619                        channel.write(buf);
620                        if (modifyTimeMs > 0) {
621                                setModifyTime(new Timestamp(modifyTimeMs));
622                        }
623                }
624        }
625
626        public void manualWrite(String data) throws IOException {
627                manualWrite(data.getBytes(CHARSET));
628        }
629
630        public void manualClose() throws IOException {
631                close();
632        }
633
634        @Override
635        public boolean write(byte[] data) throws IOException {
636                return write(data, false);
637        }
638
639        @Override
640        public boolean write(String data, boolean append) throws IOException {
641                return write(data.getBytes(CHARSET), append);
642        }
643
644        public boolean write(String data, String charset, boolean append) throws IOException {
645                return write(data.getBytes(charset), append);
646        }
647
648        @Override
649        public boolean write(String data) throws IOException {
650                return write(data.getBytes(CHARSET));
651        }
652
653        public boolean write(String data, String charset) throws IOException {
654                return write(data.getBytes(charset));
655        }
656
657        @Override
658        public boolean createLink(String target) throws IOException {
659                boolean allowed = beforeCreateLink(target);
660                if (allowed) {
661                        LoggerFactory.getLogger(DiskFile.class).debug("CreateLink: {}", getPath());
662                        try {
663                                Path link = Paths.get(origin.toURI());
664                                Path targetPath = Paths.get(target);
665                                Files.createSymbolicLink(link, targetPath);
666                                if (modifyTimeMs > 0) {
667                                        setModifyTime(new Timestamp(modifyTimeMs));
668                                }
669                                afterCreateLink(target);
670                                return true;
671                        } catch (IOException e) {
672                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
673                        }
674                }
675                return false;
676        }
677
678        @Override
679        public String readLink() throws IOException {
680                Path link = Paths.get(origin.toURI());
681                return Files.readSymbolicLink(link).toString();
682        }
683
684        @Override
685        public byte[] readAllBytes() throws IOException {
686                return readAllBytesFrom();
687        }
688
689        private byte[] readAllBytesFrom() throws IOException {
690                if (isLink()) {
691                        throw new IOException(String.format("The disk file '%s' is a link.", origin.getAbsolutePath()));
692                }
693                if (isDir()) {
694                        throw new IOException(String.format("The disk file '%s' is a folder.", origin.getAbsolutePath()));
695                }
696                if (!exists()) {
697                        throw new IOException(String.format("The disk file '%s' does not exist.", origin.getAbsolutePath()));
698                }
699                return Files.readAllBytes(Paths.get(getPath()));
700        }
701
702        @Override
703        public String readAllString() throws IOException {
704                return readAllString(BaseFile.CHARSET);
705        }
706
707        public String readAllString(String charset) throws IOException {
708                ByteArrayOutputStream baos = null;
709                try {
710                        byte[] bytes = readAllBytes();
711                        baos = new ByteArrayOutputStream();
712                        baos.write(bytes);
713                        baos.flush();
714                        return baos.toString(charset);
715                } finally {
716                        if (baos != null) {
717                                try {
718                                        baos.close();
719                                } catch (IOException e) {
720                                        Logger.systemError(DiskFile.class, e.getMessage(), e);
721                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
722                                }
723                        }
724                }
725        }
726
727        @Override
728        public long size() throws IOException {
729                if (isFile()) {
730                        Path path = Paths.get(origin.getAbsolutePath());
731                        return Files.size(path);
732                }
733                return 0L;
734        }
735
736        private void close() {
737                if (lock != null) {
738                        try {
739                                lock.release();
740                        } catch (IOException e) {
741                                Logger.systemError(DiskFile.class, e.getMessage(), e);
742                        }
743                        try {
744                                lock.close();
745                        } catch (IOException e) {
746                                Logger.systemError(DiskFile.class, e.getMessage(), e);
747                        } finally {
748                                lock = null;
749                        }
750                }
751
752                if (channel != null) {
753                        try {
754                                channel.close();
755                        } catch (IOException e) {
756                                Logger.systemError(DiskFile.class, e.getMessage(), e);
757                        } finally {
758                                channel = null;
759                        }
760                }
761                afterWrite();
762        }
763
764        @Override
765        public boolean complete() throws IOException {
766                close();
767                return true;
768        }
769
770        public void setModifyTimeMs(long modifyTimeMs) throws IOException {
771                this.modifyTimeMs = modifyTimeMs;
772        }
773
774        public void setModifyTimeMsFromClock(long modifyTimeMsFromClock) throws IOException {
775                String remoteTzId = new Clock().getCalendar().getTimeZone().getID();
776                String localTzId = Calendar.getInstance().getTimeZone().getID();
777                Timestamp fileLocalTimestamp = timeZoneConver(localTzId, remoteTzId, new Timestamp(modifyTimeMsFromClock));
778                this.modifyTimeMs = fileLocalTimestamp.getTime();
779        }
780
781        @Override
782        public void setModifyTime(Timestamp modifyTime) throws IOException {
783                if (exists()) {
784                        Path originPath = Paths.get(origin.toURI());
785                        Files.setLastModifiedTime(originPath, FileTime.fromMillis(modifyTime.getTime()));
786                }
787        }
788
789        public void setModifyTimeFromClock(Timestamp modifyTimeFromClock) throws IOException {
790                String remoteTzId = new Clock().getCalendar().getTimeZone().getID();
791                String localTzId = Calendar.getInstance().getTimeZone().getID();
792                Timestamp fileLocalTimestamp = timeZoneConver(localTzId, remoteTzId, modifyTimeFromClock);
793                setModifyTime(fileLocalTimestamp);
794        }
795
796        @Override
797        public Timestamp getModifyTime() throws IOException {
798                Path originPath = Paths.get(origin.toURI());
799                if (Files.exists(originPath)) {
800                        BasicFileAttributes attr = Files.readAttributes(originPath, BasicFileAttributes.class);
801                        FileTime fileTime = attr.lastModifiedTime();
802                        Timestamp fileLocalTimestamp = new Timestamp(fileTime.toMillis());
803                        return fileLocalTimestamp;
804                }
805                return null;
806        }
807
808        public Timestamp getModifyTimeForClock() throws IOException {
809                Path originPath = Paths.get(origin.toURI());
810                if (Files.exists(originPath)) {
811                        BasicFileAttributes attr = Files.readAttributes(originPath, BasicFileAttributes.class);
812                        FileTime fileTime = attr.lastModifiedTime();
813                        Timestamp fileLocalTimestamp = new Timestamp(fileTime.toMillis());
814                        String remoteTzId = new Clock().getCalendar().getTimeZone().getID();
815                        String localTzId = Calendar.getInstance().getTimeZone().getID();
816                        Timestamp fileRemoteTimestamp = timeZoneConver(remoteTzId, localTzId, fileLocalTimestamp);
817                        return fileRemoteTimestamp;
818                }
819                return null;
820        }
821
822        public void startFullAsync(long timerMs, String replaceTo) throws IOException {
823                startFullAsync(timerMs, getPath() + "/", replaceTo, null, null);
824        }
825
826        public void startFullAsync(long timerMs, String replaceTo, Runnable completedCallback) throws IOException {
827                startFullAsync(timerMs, getPath() + "/", replaceTo, completedCallback, null);
828        }
829
830        public void startFullAsync(long timerMs, String replaceTo, Runnable completedCallback,
831                        Runnable checkDeleteCompletedCallback) throws IOException {
832                startFullAsync(timerMs, getPath() + "/", replaceTo, completedCallback, checkDeleteCompletedCallback);
833        }
834
835        private void startFullAsync(long timerMs, String replaceRootPath, String replaceTo, Runnable completedCallback,
836                        Runnable checkDeleteCompletedCallback) throws IOException {
837
838                if (!RemoteFile.FIRST_LOADED) {
839                        RemoteFile firstLoaded = new RemoteFile(replaceTo);
840                }
841
842                Runnable runnable = new Runnable() {
843                        @Override
844                        public void run() {
845                                try {
846                                        startFullAsyncThread(timerMs, replaceRootPath, replaceTo, completedCallback);
847                                } catch (Exception e) {
848                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
849                                }
850                        }
851                };
852                Executors.newFixedThreadPool(1).execute(runnable);
853
854                runnable = new Runnable() {
855                        @Override
856                        public void run() {
857                                try {
858                                        startFullSyncForDelete(-1, timerMs, replaceTo, replaceRootPath, checkDeleteCompletedCallback);
859                                } catch (Exception e) {
860                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
861                                }
862                        }
863                };
864                Executors.newFixedThreadPool(1).execute(runnable);
865        }
866
867        private synchronized void startFullSyncForDelete(final Integer filePartDataTable, final long timerMs,
868                        final String replaceTo, final String replaceRootPath, final Runnable completedCallback) throws IOException {
869                DiskFile the = this;
870                RemoteFile rootDir = null;
871                if (!CommonTools.isBlank(replaceTo)) {
872                        rootDir = new RemoteFile(replaceTo);
873                }
874                if (rootDir == null) {
875                        throw new IOException("The root folder is null.");
876                }
877                RemoteFile _rootDir = rootDir;
878                Integer maxDataTable = rootDir.getMaxFilePartDataTable();
879
880                Integer _filePartDataTable = filePartDataTable;
881
882                if (filePartDataTable > maxDataTable)
883                        _filePartDataTable = -1;
884
885                final Integer finalFilePartDataTable = _filePartDataTable;
886
887                final CacheArray rows = new CacheArray();
888
889                rows.filter(getScanDeleteFilter(rootDir, finalFilePartDataTable, timerMs, replaceTo, replaceRootPath,
890                                completedCallback));
891
892                Runnable runnable = new Runnable() {
893                        @Override
894                        public void run() {
895                                try {
896                                        _rootDir.listAllForScanDelete(finalFilePartDataTable, rows);
897                                } catch (Exception e) {
898                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
899                                }
900                        }
901                };
902                Executors.newFixedThreadPool(1).execute(runnable);
903        }
904
905        private CacheArrayFilter getScanDeleteFilter(final RemoteFile rootDir, final Integer filePartDataTable,
906                        final long timerMs, final String replaceTo, final String replaceRootPath,
907                        final Runnable completedCallback) {
908                DiskFile the = this;
909                CacheArrayFilter filter = new CacheArrayFilter(CACHE_ARRAY_FILTER_TIMER) {
910                        @Override
911                        public void execute(Integer index, Object o) {
912
913                                try {
914                                        Map<String, Object> item = (Map<String, Object>) o;
915                                        Object id = item.get("id");
916                                        String parent = (String) item.get("file_parent_path");
917                                        String fileName = (String) item.get("file_name");
918                                        String pf = String.format("%s/%s", parent, fileName);
919                                        RemoteFile rf = new RemoteFile(pf);
920                                        Thread.currentThread()
921                                                        .setName(String.format("DiskFile-startFullSyncForDelete-(DataTable=%s Index=%s)-%s",
922                                                                        filePartDataTable, index, fileName));
923
924                                        if (memoryUsage() < RemoteFile.MAX_MEMORY_USAGE) {
925                                                String toDf = pf;
926                                                if (!CommonTools.isBlank(replaceRootPath) && !CommonTools.isBlank(replaceTo)) {
927                                                        toDf = pf.replaceFirst(replaceTo + "[/]*", replaceRootPath + "/");
928                                                }
929
930                                                DiskFile df = new DiskFile(toDf);
931                                                DiskFile.copyAttrs(df, the.copyStructureOnly, the.syncRoot);
932                                                boolean isDebug = isDebug(rf);
933                                                boolean isRoot = rootDir.getPath().equals(rf.getPath());
934                                                boolean isSyncedHost = rf.isSyncedOnHostname(id);
935                                                boolean rfExists = rf.exists();
936
937                                                boolean allowedForceDelete = rootDir.exists() && !isRoot && isSyncedHost && rfExists
938                                                                && !df.exists() && !df.isLogicModify();
939
940                                                if (allowedForceDelete) {
941                                                        df.logicModify();
942                                                        if (isDebug) {
943                                                                LoggerFactory.getLogger(DiskFile.class).mark("ForceRemoteDelete - {}", rf.getPath());
944                                                        }
945                                                        rf.forceDeleteAll(true);
946                                                        df.removeLogicModify();
947                                                }
948                                        } else {
949                                                LoggerFactory.getLogger(DiskFile.class).warn("Over max memory usage 'MAX_MEMORY_USAGE={}'.",
950                                                                RemoteFile.MAX_MEMORY_USAGE);
951                                        }
952                                } catch (Exception e) {
953                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
954                                }
955                        }
956
957                        @Override
958                        public void completed(Integer size) {
959                                if (isStopSync()) {
960                                        LoggerFactory.getLogger(DiskFile.class).mark("Stoped start full sync for delete.");
961                                }
962                                if (timerMs <= 0) {
963                                        LoggerFactory.getLogger(DiskFile.class).mark("Completed start full sync for delete.");
964                                }
965
966                                boolean isRootCompleted = the.getPath().equals(new DiskFile(replaceRootPath).getPath());
967
968                                if (isRootCompleted && completedCallback != null) {
969                                        completedCallback.run();
970                                }
971
972                                if (!isStopSync() && timerMs > 0) {
973                                        while (true) {
974
975                                                if (isStopSync())
976                                                        break;
977
978                                                try {
979                                                        Thread.sleep(timerMs);
980                                                } catch (InterruptedException e) {
981                                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
982                                                }
983                                                boolean allowed = checkUsage(rootDir);
984                                                if (allowed) {
985                                                        try {
986                                                                if (!isStopSync()) {
987                                                                        startFullSyncForDelete(filePartDataTable + 1, timerMs, replaceTo, replaceRootPath,
988                                                                                        completedCallback);
989                                                                }
990                                                        } catch (IOException e) {
991                                                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
992                                                        }
993                                                        break;
994                                                }
995                                        }
996                                }
997                        }
998                };
999                return filter;
1000        }
1001
1002        private void startFullAsyncThread(final long timerMs, String replaceRootPath, String replaceTo,
1003                        final Runnable completedCallback) {
1004                stopSync = false;
1005                this.setCopyStructureOnly(RemoteFile.COPY_STRUCTURE_ONLY);
1006                final DiskFile the = this;
1007                Thread.currentThread().setName(String.format("DiskFile-startFullAsyncThread-%s", the.getOrigin().getName()));
1008                replaceRootPath = replacePath(replaceRootPath);
1009                replaceTo = replacePath(replaceTo);
1010                DiskFile rootDisk = new DiskFile(replaceRootPath);
1011                boolean rootExists = false;
1012                try {
1013                        rootExists = rootDisk.exists();
1014                } catch (Exception e) {
1015                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1016                }
1017                if (!rootExists) {
1018                        Logger.systemError(DiskFile.class, "The root path '{}' does not exist.", rootDisk.getPath());
1019                }
1020                boolean isRoot = the.getPath().equals(rootDisk.getPath());
1021                LoggerFactory.getLogger(DiskFile.class).debug("IsRoot: {} - {}", isRoot, the.getPath());
1022                LoggerFactory.getLogger(DiskFile.class).debug("CacheArrayUsage: {}", CacheArray.getUsage());
1023                if (rootExists) {
1024
1025                        DirectoryStream<Path> stream = null;
1026                        try {
1027                                if (syncRoot == null) {
1028                                        File replaceRootFile = new File(replaceRootPath);
1029                                        if (replaceRootFile.getParent() == null) {
1030                                                throw new IOException("Cannot sync the root path.");
1031                                        }
1032                                        String replaceRootParent = replacePath(replaceRootFile.getParent());
1033                                        syncRoot = new File(String.format("%s/%s", replaceRootParent, replaceRootFile.getName()));
1034                                }
1035
1036                                if (!CommonTools.isBlank(replaceTo)) {
1037                                        RemoteFile rootDir = new RemoteFile(replaceTo);
1038                                        if (!rootDir.exists()) {
1039                                                rootDir.setModifyTime(this.getModifyTimeForClock());
1040                                                rootDir.mkdirs();
1041                                        }
1042                                }
1043                                Path originPath = Paths.get(origin.toURI());
1044                                CacheArray rows = new CacheArray();
1045                                CacheArrayFilter filter = getFullAsyncFilter(the, timerMs, replaceRootPath, replaceTo,
1046                                                completedCallback);
1047                                rows.filter(filter);
1048                                stream = Files.newDirectoryStream(originPath);
1049                                for (Path p : stream) {
1050                                        if (p != null) {
1051                                                File f = p.toFile();
1052                                                boolean isFile = f.isFile();
1053                                                if (isFile) {
1054                                                        boolean isWritingDf = f.getName().matches(TMP_WRITING_SWP);
1055                                                        if (isWritingDf) {
1056                                                                BasicFileAttributes attributes = Files.readAttributes(p, BasicFileAttributes.class);
1057                                                                FileTime creationTime = attributes.creationTime();
1058                                                                boolean timeouted = (Calendar.getInstance().getTimeInMillis()
1059                                                                                - creationTime.toMillis()) > (RemoteFile.BATCH_SIZE * LOGIC_TIMEOUT_MS);
1060                                                                if (timeouted) {
1061                                                                        try {
1062                                                                                boolean exists = Files.exists(p);
1063                                                                                if (exists) {
1064                                                                                        Files.deleteIfExists(p);
1065                                                                                }
1066                                                                        } catch (Exception e) {
1067                                                                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1068                                                                        }
1069                                                                }
1070                                                        }
1071                                                }
1072                                                boolean allowedPath = the.checkSyncPathAvailable(p.toString());
1073                                                if (!allowedPath) {
1074                                                        LoggerFactory.getLogger(DiskFile.class).debug("Ignored: {}", p.toString());
1075                                                }
1076                                                if (allowedPath) {
1077                                                        rows.add(p);
1078                                                }
1079                                        }
1080                                }
1081                                rows.add(null);
1082                        } catch (Exception e) {
1083                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1084                                removeLogicAccess();
1085                                if (!(e instanceof NoSuchFileException)) {
1086                                        if (!isStopSync() && timerMs > 0) {
1087                                                try {
1088                                                        Thread.sleep(timerMs);
1089                                                } catch (InterruptedException ee) {
1090                                                        LoggerFactory.getLogger(DiskFile.class).error(ee.getMessage(), ee);
1091                                                }
1092                                                if (!isStopSync()) {
1093                                                        startFullAsyncThread(timerMs, replaceRootPath, replaceTo, completedCallback);
1094                                                }
1095                                        }
1096                                }
1097                        } finally {
1098                                if (stream != null) {
1099                                        try {
1100                                                stream.close();
1101                                        } catch (IOException e) {
1102                                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1103                                        }
1104                                }
1105                        }
1106                }
1107        }
1108
1109        private CacheArrayFilter getFullAsyncFilter(DiskFile the, long timerMs, String replaceRootPath, String replaceTo,
1110                        Runnable completedCallback) {
1111
1112                return new CacheArrayFilter(CACHE_ARRAY_FILTER_TIMER) {
1113                        RemoteFile rf = null;
1114                        final CacheArrayFilter theFilter = this;
1115
1116                        @Override
1117                        public void completed(Integer size) {
1118                                boolean isRootCompleted = the.getPath().equals(new DiskFile(replaceRootPath).getPath());
1119
1120                                if (isRootCompleted) {
1121                                        try {
1122                                                if (rf != null && the.isStopSync() && isDebug(rf)) {
1123                                                        LoggerFactory.getLogger(DiskFile.class).mark("StopedRootScan - {}", the.getPath());
1124                                                }
1125                                                if (!the.isStopSync() && timerMs > 0) {
1126                                                        Thread.sleep(timerMs);
1127                                                        if (!the.isStopSync()) {
1128                                                                if (rf != null && isDebug(rf)) {
1129                                                                        LoggerFactory.getLogger(DiskFile.class).mark("RootScaning - {}", the.getPath());
1130                                                                }
1131                                                                the.startFullAsyncThread(timerMs, replaceRootPath, replaceTo, completedCallback);
1132                                                        }
1133                                                }
1134                                                if (completedCallback != null) {
1135                                                        completedCallback.run();
1136                                                }
1137                                        } catch (Exception e) {
1138                                                LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1139                                        }
1140                                }
1141                        }
1142
1143                        @Override
1144                        public void execute(Integer index, Object o) {
1145                                DiskFile df = null;
1146                                try {
1147                                        Path p = (Path) o;
1148                                        String pf = replacePath(p.toFile().getAbsolutePath());
1149                                        Thread.currentThread().setName(
1150                                                        String.format("DiskFile-getFullAsyncFilter-%s", p.toFile().getParentFile().getName()));
1151                                        String toRf = pf;
1152                                        if (!CommonTools.isBlank(replaceRootPath) && !CommonTools.isBlank(replaceTo)) {
1153                                                toRf = pf.replaceFirst(replaceRootPath + "[/]*", replaceTo + "/");
1154                                        }
1155
1156                                        df = new DiskFile(pf);
1157                                        DiskFile.copyAttrs(df, the.copyStructureOnly, the.syncRoot);
1158                                        rf = new RemoteFile(toRf);
1159
1160                                        boolean isRoot = the.getPath().equals(new DiskFile(replaceRootPath).getPath());
1161
1162                                        boolean allowedConn = checkUsage(rf);
1163                                        String fileName = p.toFile().getName();
1164                                        boolean isDebug = isDebug(rf);
1165
1166                                        boolean timeouted = rf.isTimeout();
1167                                        if (timeouted && RemoteFile.addQueuePathMapping(rf.getPath()) && allowedConn) {
1168                                                if (isDebug) {
1169                                                        LoggerFactory.getLogger(DiskFile.class).mark("ForceDelete(Timeouted) - {}", rf.getPath());
1170                                                }
1171                                                rf.forceDeleteFile(false);
1172                                                RemoteFile.removeQueuePathMapping(rf.getPath());
1173                                        }
1174                                        Timestamp modifyTime = df.getModifyTimeForClock();
1175                                        boolean allowedModifyTime = modifyTime != null;
1176                                        boolean allowed = DiskFile.checkQueuePathMapping() && !isStopSync() && allowedModifyTime
1177                                                        && allowedConn;
1178
1179                                        if (isDebug) {
1180                                                debugLog("CheckUsage", rf, this);
1181                                        }
1182
1183                                        if (!isRoot && allowed) {
1184                                                allowed = !df.isLogicCheck();
1185                                        }
1186
1187                                        if (allowed) {
1188
1189                                                if (!isRoot)
1190                                                        df.logicCheck();
1191
1192                                                rf.setModifyTime(modifyTime);
1193                                                long dfModifyTimeMs = modifyTime.getTime();
1194                                                boolean blocked = false;
1195                                                boolean isRealDelete = rf.isLastOperateRealDelete();
1196
1197                                                if (isRealDelete) {
1198                                                        String clientHostname = CommonTools.getHostname();
1199                                                        String delHostname = rf.getLastSourceHostname();
1200                                                        boolean isOwnerDel = clientHostname.equals(delHostname);
1201                                                        if (isOwnerDel) {
1202                                                                Timestamp deletedTime = rf.getLastOperateTime();
1203                                                                if (deletedTime != null) {
1204                                                                        boolean waiting = new Clock().getTime() - deletedTime.getTime() <= LOGIC_TIMEOUT_MS;
1205                                                                        if (waiting) {
1206                                                                                blocked = true;
1207                                                                        }
1208                                                                }
1209                                                        } else {
1210                                                                Timestamp deletedTime = rf.getLastOperateTime();
1211                                                                if (deletedTime != null) {
1212                                                                        boolean olded = modifyTime.getTime() < deletedTime.getTime();
1213                                                                        if (olded) {
1214                                                                                blocked = true;
1215                                                                        }
1216                                                                }
1217                                                        }
1218                                                }
1219
1220                                                boolean haveParent = rf.getParentFile().exists();
1221                                                if (!blocked && haveParent) {
1222                                                        if (df.isLink()) {
1223                                                                if (!df.isLogicAccess()) {
1224                                                                        df.logicAccess();
1225                                                                        if (rf.exists()) {
1226                                                                                if (rf.isLink()) {
1227                                                                                        long rfModifyTimeMs = rf.getModifyTime().getTime();
1228                                                                                        boolean changedTime = dfModifyTimeMs > rfModifyTimeMs;
1229                                                                                        boolean changedValue = !rf.readLink().equals(df.readLink());
1230                                                                                        boolean changed = changedTime && changedValue;
1231                                                                                        if (changed) {
1232                                                                                                RemoteFile prf = rf.getParentFile();
1233                                                                                                if (prf != null && prf.exists()) {
1234                                                                                                        if (isDebug) {
1235                                                                                                                LoggerFactory.getLogger(DiskFile.class).mark("CreateLink - {}",
1236                                                                                                                                rf.getPath());
1237                                                                                                        }
1238                                                                                                        rf.forceDeleteLink(false);
1239                                                                                                        rf.createLink(df.readLink());
1240                                                                                                }
1241                                                                                        }
1242                                                                                } else {
1243                                                                                        RemoteFile prf = rf.getParentFile();
1244                                                                                        if (prf != null && prf.exists()) {
1245                                                                                                if (isDebug) {
1246                                                                                                        LoggerFactory.getLogger(DiskFile.class).mark("CreateLink - {}",
1247                                                                                                                        rf.getPath());
1248                                                                                                }
1249                                                                                                rf.forceDeleteLink(false);
1250                                                                                                rf.createLink(df.readLink());
1251                                                                                        }
1252                                                                                }
1253                                                                        } else {
1254                                                                                RemoteFile prf = rf.getParentFile();
1255                                                                                if (prf != null && prf.exists()) {
1256                                                                                        if (isDebug) {
1257                                                                                                LoggerFactory.getLogger(DiskFile.class).mark("CreateLink - {}",
1258                                                                                                                rf.getPath());
1259                                                                                        }
1260                                                                                        rf.createLink(df.readLink());
1261                                                                                }
1262                                                                        }
1263                                                                }
1264                                                        } else if (df.isFile()) {
1265                                                                if (!df.isLogicModify() && !df.isLogicAccess()) {
1266                                                                        if (rf.exists()) {
1267                                                                                if (rf.isFile()) {
1268                                                                                        long rfModifyTimeMs = rf.getModifyTime().getTime();
1269                                                                                        if (dfModifyTimeMs > rfModifyTimeMs) {
1270                                                                                                RemoteFile prf = rf.getParentFile();
1271                                                                                                if (prf != null && prf.exists()) {
1272                                                                                                        if (DiskFile.addQueuePathMapping(df.getPath())) {
1273                                                                                                                if (isDebug) {
1274                                                                                                                        LoggerFactory.getLogger(DiskFile.class)
1275                                                                                                                                        .mark("RemoteQueuing - {}", df.getPath());
1276                                                                                                                }
1277                                                                                                                df.logicAccess();
1278                                                                                                                rf.forceDeleteFile(false);
1279                                                                                                                df.writeToRemote(rf);
1280                                                                                                        } else {
1281                                                                                                                df.removeLogicAccess();
1282                                                                                                                df.removeLogicCheck();
1283                                                                                                        }
1284                                                                                                }
1285                                                                                        }
1286                                                                                } else {
1287                                                                                        rf.forceDeleteFile(false);
1288                                                                                }
1289                                                                        } else {
1290                                                                                RemoteFile prf = rf.getParentFile();
1291                                                                                if (prf != null && prf.exists()) {
1292                                                                                        if (DiskFile.addQueuePathMapping(df.getPath())) {
1293                                                                                                if (isDebug) {
1294                                                                                                        LoggerFactory.getLogger(DiskFile.class).mark("RemoteQueuing - {}",
1295                                                                                                                        df.getPath());
1296                                                                                                }
1297                                                                                                df.logicAccess();
1298                                                                                                df.writeToRemote(rf);
1299                                                                                        } else {
1300                                                                                                df.removeLogicAccess();
1301                                                                                                df.removeLogicCheck();
1302                                                                                        }
1303                                                                                }
1304                                                                        }
1305                                                                }
1306                                                        } else if (df.isDir()) {
1307                                                                if (isStopSync() && isDebug) {
1308                                                                        LoggerFactory.getLogger(DiskFile.class).mark("Stopping queue");
1309                                                                }
1310                                                                if (!isStopSync()) {
1311                                                                        boolean allowedPoolSize = theFilter.getCacheArray().getUsingPoolSize() < theFilter
1312                                                                                        .getCacheArray().getMaxPoolSize() && checkUsage(rf);
1313                                                                        if (isDebug && allowedPoolSize) {
1314                                                                                LoggerFactory.getLogger(DiskFile.class).debug("Scaning - {}", df.getPath());
1315                                                                        }
1316                                                                        if (isDebug && !allowedPoolSize) {
1317                                                                                LoggerFactory.getLogger(DiskFile.class).debug("Skiped - {}", df.getPath());
1318                                                                        }
1319                                                                        if (rf.exists()) {
1320                                                                                if (rf.isDir()) {
1321                                                                                        if (isRoot || allowedPoolSize) {
1322                                                                                                df.startFullAsyncThread(timerMs, replaceRootPath, replaceTo,
1323                                                                                                                completedCallback);
1324                                                                                        }
1325                                                                                } else {
1326                                                                                        if (rf.isDir())
1327                                                                                                rf.forceDeleteDir(false);
1328
1329                                                                                        if (rf.isFile())
1330                                                                                                rf.forceDeleteFile(false);
1331
1332                                                                                        if (rf.isLink())
1333                                                                                                rf.forceDeleteLink(false);
1334
1335                                                                                        RemoteFile prf = rf.getParentFile();
1336                                                                                        if (isRoot || (prf != null && prf.exists())) {
1337                                                                                                if (isDebug) {
1338                                                                                                        LoggerFactory.getLogger(DiskFile.class)
1339                                                                                                                        .mark("ReCreateRemoteDir - {}", rf.getPath());
1340                                                                                                }
1341                                                                                                rf.mkdirs();
1342                                                                                                if (isRoot || allowedPoolSize) {
1343                                                                                                        df.startFullAsyncThread(timerMs, replaceRootPath, replaceTo,
1344                                                                                                                        completedCallback);
1345                                                                                                }
1346                                                                                        }
1347                                                                                }
1348                                                                        } else {
1349                                                                                RemoteFile prf = rf.getParentFile();
1350                                                                                if (isRoot || (prf != null && prf.exists())) {
1351                                                                                        if (isDebug) {
1352                                                                                                LoggerFactory.getLogger(DiskFile.class).mark("CreateRemoteDir - {}",
1353                                                                                                                rf.getPath());
1354                                                                                        }
1355                                                                                        rf.mkdirs();
1356                                                                                        if (isRoot || allowedPoolSize) {
1357                                                                                                df.startFullAsyncThread(timerMs, replaceRootPath, replaceTo,
1358                                                                                                                completedCallback);
1359                                                                                        }
1360                                                                                }
1361                                                                        }
1362                                                                        if (!isRoot && !allowedPoolSize) {
1363                                                                                df.removeLogicCheck();
1364                                                                        }
1365                                                                }
1366                                                        }
1367                                                }
1368                                        }
1369                                } catch (Exception e) {
1370                                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1371                                        if (df != null) {
1372                                                df.removeLogicAccess();
1373                                        }
1374                                }
1375                        }
1376
1377                };
1378        }
1379
1380        public void setCopyStructureOnly(boolean copyStructureOnly) {
1381                this.copyStructureOnly = copyStructureOnly;
1382        }
1383
1384        public boolean isCopyStructureOnly() {
1385                return this.copyStructureOnly;
1386        }
1387
1388        private boolean isViEditSwp(String fileName) {
1389                return fileName.matches(TMP_MATCHES_EDIT) || fileName.matches(TMP_MATCHES_VI_SWP);
1390        }
1391
1392        protected static DiskFile copyAttrs(DiskFile source, boolean _copyStructureOnly, File _syncRoot) {
1393                source.setCopyStructureOnly(_copyStructureOnly);
1394                source.syncRoot = _syncRoot;
1395                return source;
1396        }
1397
1398        public File getParentFile() {
1399                return new File(getParent());
1400        }
1401
1402        protected boolean checkSyncPathAvailable(String path) {
1403                try {
1404                        return checkSyncPathAvailable(path, RemoteFile.SHOW_HIDDEN, RemoteFile.SHOW_LINK, RemoteFile.MAX_FILE_SZIE,
1405                                        SYNC_PATH_ALLOWED, SYNC_PATH_IGNORED);
1406                } catch (Exception e) {
1407                        LoggerFactory.getLogger(DiskFile.class).error(e.getMessage(), e);
1408                        return false;
1409                }
1410        }
1411
1412        protected static synchronized boolean addQueuePathMapping(String path) {
1413                Long addTimeMs = null;
1414                boolean allowed = checkQueuePathMapping();
1415
1416                if (!allowed)
1417                        return false;
1418
1419                addTimeMs = QUEUE_PATH_MAPPING.get(path);
1420
1421                if (addTimeMs == null) {
1422                        QUEUE_PATH_MAPPING.put(path, System.currentTimeMillis());
1423                        return true;
1424                }
1425
1426                return false;
1427        }
1428
1429        public static synchronized boolean checkQueuePathMapping() {
1430                Long addTimeMs = null;
1431                Map<String, Long> copyQueueMap = getQueuePathMapping();
1432                List<String> keyList = new ArrayList<String>(copyQueueMap.keySet());
1433                for (String key : keyList) {
1434                        addTimeMs = copyQueueMap.get(key);
1435                        if (addTimeMs != null) {
1436                                boolean timeout = (System.currentTimeMillis() - addTimeMs) >= LOGIC_TIMEOUT_MS;
1437
1438                                if (timeout) {
1439                                        QUEUE_PATH_MAPPING.remove(key);
1440                                }
1441                        }
1442                }
1443
1444                if (QUEUE_PATH_MAPPING.keySet().size() >= RemoteFile.MAX_QUEUE_SIZE)
1445                        return false;
1446
1447                return true;
1448        }
1449
1450        protected static synchronized void removeQueuePathMapping(String path) {
1451                QUEUE_PATH_MAPPING.remove(path);
1452        }
1453
1454        public static synchronized Map<String, Long> getQueuePathMapping() {
1455                return Collections.synchronizedMap(QUEUE_PATH_MAPPING);
1456        }
1457
1458        private static void debugLog(String point, RemoteFile rf, CacheArrayFilter filter) {
1459                debugLog(DiskFile.class, point, rf, filter);
1460        }
1461
1462}