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 com.killcoding.log.LoggerFactory; 028import com.killcoding.log.Logger; 029import java.io.IOException; 030import java.util.List; 031import java.util.ArrayList; 032import java.nio.ByteBuffer; 033import java.io.ByteArrayInputStream; 034import java.sql.Timestamp; 035import com.killcoding.tool.CommonTools; 036import java.nio.file.Path; 037import java.nio.file.Files; 038import java.nio.file.Paths; 039import java.text.SimpleDateFormat; 040import java.time.ZonedDateTime; 041import java.time.ZoneId; 042import java.time.LocalDateTime; 043import java.time.format.DateTimeFormatter; 044import com.killcoding.cache.CacheArrayFilter; 045import java.nio.file.attribute.BasicFileAttributes; 046import java.nio.file.attribute.FileTime; 047import java.nio.file.attribute.FileAttribute; 048import java.nio.file.LinkOption; 049 050public abstract class BaseFile { 051 052 protected static final String TMP_MATCHES_EDIT = "^.*\\..*~$"; 053 protected static final String TMP_MATCHES_VI_SWP = "^.*\\..*\\.sw[xp]$"; 054 055 protected static final String TMP_EXT_MODIFY_FILE = ".modify.tmp~"; 056 protected static final String TMP_EXT_ACCESS_FILE = ".access.tmp~"; 057 protected static final String TMP_EXT_CHECK_FILE = ".check.tmp~"; 058 059 protected static final String TMP_WRITING_SWP = "^.*\\.writing.tmp~$"; 060 protected static final String TMP_WRITING_FOR_REPLACE = "\\.writing\\.tmp~"; 061 protected static final String TMP_WRITING_FILE = ".writing.tmp~"; 062 063 public static String CHARSET = "UTF-8"; 064 065 public abstract boolean isFile() throws IOException; 066 067 public abstract boolean isDirectory() throws IOException; 068 069 public abstract boolean isLink() throws IOException; 070 071 public abstract boolean delete() throws IOException; 072 073 public abstract boolean exists() throws IOException; 074 075 public abstract boolean mkdirs() throws IOException; 076 077 public abstract boolean createLink(String target) throws IOException; 078 079 public abstract String readLink() throws IOException; 080 081 public abstract boolean write(byte[] data, boolean append) throws IOException; 082 083 public abstract boolean write(byte[] data) throws IOException; 084 085 public abstract boolean write(String data, boolean append) throws IOException; 086 087 public abstract boolean write(String data) throws IOException; 088 089 public abstract byte[] readAllBytes() throws IOException; 090 091 public abstract String readAllString() throws IOException; 092 093 public abstract long size() throws IOException; 094 095 public abstract boolean complete() throws IOException; 096 097 public abstract Timestamp getModifyTime() throws IOException; 098 099 public abstract void setModifyTime(Timestamp modifyTime) throws IOException; 100 101 public abstract void copyTo(String toPath) throws IOException; 102 103 public abstract void moveTo(String toPath) throws IOException; 104 105 public abstract boolean beforeDelete(boolean realDeleted); 106 107 public abstract void afterDelete(boolean realDeleted); 108 109 public abstract boolean beforeMkdirs(); 110 111 public abstract void afterMkdirs(); 112 113 public abstract boolean beforeCreateLink(String target); 114 115 public abstract void afterCreateLink(String target); 116 117 public abstract boolean beforeWrite(); 118 119 public abstract void afterWrite(); 120 121 public static long CACHE_ARRAY_FILTER_TIMER = 10L; 122 123 public static long LOGIC_TIMEOUT_MS = 300000L; 124 125 public static long LOGIC_ACCESS_TIMEOUT_MS = 60000L; 126 127 public static long LOGIC_CHECK_TIMEOUT_MS = 60000L; 128 129 protected static boolean stopSync = true; 130 131 protected File syncRoot = null; 132 133 protected File origin; 134 135 public static Path TEMP_DIR = null; 136 137 private static long debugLogTime = 0L; 138 139 public static final List<String> SYNC_PATH_ALLOWED = new ArrayList<String>(); 140 public static final List<String> SYNC_PATH_IGNORED = new ArrayList<String>(); 141 142 static { 143 try{ 144 SYNC_PATH_IGNORED.add(TMP_MATCHES_EDIT); 145 SYNC_PATH_IGNORED.add(TMP_MATCHES_VI_SWP); 146 147 String tmpdir = System.getProperty("java.io.tmpdir"); 148 Path tmpBfDir = Paths.get(String.format("%s/BaseFile",tmpdir)); 149 if(!Files.exists(tmpBfDir)){ 150 Files.createDirectories(tmpBfDir); 151 } 152 Path pidDir = Paths.get(String.format("%s/%s/",tmpBfDir.toString(),ProcessHandle.current().pid())); 153 if(TEMP_DIR == null) TEMP_DIR = Files.createDirectories(pidDir); 154 155 }catch(IOException e){ 156 Logger.systemError(BaseFile.class,e.getMessage(),e); 157 } 158 } 159 160 public BaseFile(String path) { 161 super(); 162 origin = new File(path); 163 } 164 165 public BaseFile(File origin) { 166 super(); 167 this.origin = origin; 168 } 169 170 public static void setTmpDir(String tmpDir) throws IOException { 171 TEMP_DIR = Files.createDirectories(Paths.get(tmpDir)); 172 } 173 174 public String getPath() { 175 return parsePath(String.format("%s/%s",getParent(),origin.getName())); 176 } 177 178 public File getOrigin(){ 179 return origin; 180 } 181 182 public String getParent() { 183 String parent = CommonTools.isBlank(origin.getParent()) ? "/" : origin.getParent(); 184 return replacePath(parent); 185 } 186 187 public List<String> getParents() { 188 List<String> parents = new ArrayList<String>(); 189 String path = getPath(); 190 do{ 191 File file = new File(path); 192 path = file.getParent(); 193 194 if(path == null) break; 195 196 parents.add(path); 197 }while(path != null); 198 199 return parents; 200 } 201 202 public void write(int partSize,byte[] allBytes) throws IOException { 203 try{ 204 ByteBuffer bf = ByteBuffer.allocate(partSize); 205 ByteArrayInputStream bais = new ByteArrayInputStream(allBytes); 206 int partIndex = -1; 207 while(bais.read() != -1){ 208 bf.flip(); 209 partIndex += 1; 210 List<Byte> parts = new ArrayList<Byte>(); 211 while (bf.hasRemaining()) { 212 parts.add(bf.get()); 213 } 214 bf.clear(); 215 int partsSize = parts.size(); 216 byte[] part = new byte[partsSize]; 217 for (int i = 0; i < partsSize; i++) { 218 part[i] = parts.get(i); 219 } 220 write(part,true); 221 } 222 }finally{ 223 complete(); 224 } 225 } 226 227 protected String toModifyTmpFilePath() throws IOException { 228 if(syncRoot == null) throw new IOException("The 'BaseFile.syncRoot' is null."); 229 230 File syncFile = new File(getPath()); 231 if(syncRoot != null){ 232 String syncRootParent = replacePath(syncRoot.getParent()) + "/"; 233 String replacedFilePath = TEMP_DIR.toAbsolutePath() + "/." + replacePath(syncFile.getAbsolutePath()).replaceFirst(syncRootParent,"") + TMP_EXT_MODIFY_FILE; 234 return replacedFilePath; 235 } 236 return null; 237 } 238 239 protected String toAccessTmpFilePath() throws IOException { 240 if(syncRoot == null) throw new IOException("The 'BaseFile.syncRoot' is null."); 241 242 File syncFile = new File(getPath()); 243 if(syncRoot != null){ 244 String syncRootParent = replacePath(syncRoot.getParent()) + "/"; 245 String replacedFilePath = TEMP_DIR.toAbsolutePath() + "/." + replacePath(syncFile.getAbsolutePath()).replaceFirst(syncRootParent,"") + TMP_EXT_ACCESS_FILE; 246 return replacedFilePath; 247 } 248 return null; 249 } 250 251 protected String toCheckTmpFilePath() throws IOException { 252 if(syncRoot == null) throw new IOException("The 'BaseFile.syncRoot' is null."); 253 254 File syncFile = new File(getPath()); 255 if(syncRoot != null){ 256 String syncRootParent = replacePath(syncRoot.getParent()) + "/"; 257 String replacedFilePath = TEMP_DIR.toAbsolutePath() + "/." + replacePath(syncFile.getAbsolutePath()).replaceFirst(syncRootParent,"") + TMP_EXT_CHECK_FILE; 258 return replacedFilePath; 259 } 260 return null; 261 } 262 263 public synchronized static void stopSync() { 264 LoggerFactory.getLogger(DiskFile.class).mark("Sending stop command."); 265 stopSync = true; 266 for (int i = 0; i < 29; i++) { 267 try{ 268 if(!isStopSync()) { 269 stopSync(); 270 break; 271 } 272 Thread.sleep(1000L); 273 }catch(InterruptedException e){ 274 LoggerFactory.getLogger(BaseFile.class).error(e.getMessage(),e); 275 } 276 } 277 LoggerFactory.getLogger(DiskFile.class).mark("Sent stop command"); 278 } 279 280 public static boolean isStopSync(){ 281 DiskFile.checkQueuePathMapping(); 282 RemoteFile.checkQueuePathMapping(); 283 return stopSync && DiskFile.getQueuePathMapping().isEmpty() && RemoteFile.getQueuePathMapping().isEmpty(); 284 } 285 286 protected static boolean isDebug(RemoteFile remoteFile){ 287 boolean debug = false; 288 if(remoteFile != null){ 289 boolean fileDebug = remoteFile.getOrigin().getName().matches("^(\\.{0,1}DEBUG[\\.\\d\\w]*)$"); 290 boolean logDeubg = remoteFile.getConfigProperties().getBoolean("LogCompletedStatus", false); 291 debug = fileDebug || logDeubg; 292 } 293 return debug || LoggerFactory.getLogger(BaseFile.class).isDebugEnabled(); 294 } 295 296 public static boolean isHidden(String path) throws Exception{ 297 Path checkPath = Paths.get(path); 298 299 if(!Files.exists(checkPath)) return false; 300 301 while(checkPath != null){ 302 303 boolean hide = Files.isHidden(checkPath); 304 305 if(hide) return true; 306 307 checkPath = checkPath.getParent(); 308 309 310 } 311 return false; 312 } 313 314 public static boolean checkSyncPathAvailable(String path, boolean showHidden,boolean showLink,long maxFileSize, List<String> syncPathAllowed, 315 List<String> syncPathIgnored) { 316 317 if(maxFileSize <= 0) return false; 318 319 try { 320 boolean matchedAllowed = true; 321 boolean matchedIgnored = false; 322 Path fullPath = Paths.get(path); 323 324 if(!showHidden){ 325 matchedAllowed = !isHidden(path); 326 } 327 328 if(matchedAllowed && !showLink && Files.exists(fullPath)){ 329 boolean isLink = Files.isSymbolicLink(fullPath); 330 matchedAllowed = !isLink; 331 } 332 333 if (matchedAllowed && syncPathAllowed != null) { 334 for (String regex : syncPathAllowed) { 335 if (!CommonTools.isBlank(regex)) { 336 matchedAllowed = path.matches(regex); 337 338 if (matchedAllowed) 339 break; 340 } 341 } 342 } 343 if (matchedAllowed && syncPathIgnored != null) { 344 for (String regex : syncPathIgnored) { 345 if (!CommonTools.isBlank(regex)) { 346 matchedIgnored = path.matches(regex); 347 348 if (matchedIgnored) 349 break; 350 } 351 } 352 } 353 354 boolean b = matchedAllowed && !matchedIgnored; 355 if (b) { 356 Path checkPath = Paths.get(path); 357 if (Files.exists(checkPath)) { 358 359 int parentLength = checkPath.getParent().toString().length(); 360 int fileNamelength = checkPath.getFileName().toString().length(); 361 362 b = parentLength <= RemoteFile.MAX_FILE_PARENT_PATH_LENGTH && fileNamelength <= RemoteFile.MAX_FILE_NAME_LENGTH; 363 364 if (b && Files.isRegularFile(checkPath)) { 365 long fileSize = Files.size(checkPath); 366 b = fileSize <= maxFileSize; 367 } 368 }else{ 369 b = false; 370 } 371 } 372 373 if(b) { 374 Path checkPath = Paths.get(path); 375 if (Files.exists(checkPath)) { 376 boolean isLink = Files.isSymbolicLink(checkPath); 377 boolean isFile = checkPath.toFile().isFile(); 378 if(isFile && !isLink){ 379 if(RemoteFile.SYNC_CUTOFF_TIME == null){ 380 b = false; 381 } else{ 382 long syncCutoffTimeMs = 0L; 383 syncCutoffTimeMs = RemoteFile.SYNC_CUTOFF_TIME.getTime(); 384 BasicFileAttributes attr = Files.readAttributes(checkPath, BasicFileAttributes.class); 385 FileTime fileTime = attr.lastModifiedTime(); 386 387 b = fileTime.toMillis() >= syncCutoffTimeMs; 388 } 389 } 390 }else{ 391 b = false; 392 } 393 } 394 395 if (!b) { 396 LoggerFactory.getLogger(BaseFile.class).debug("Unavailable: {}", path); 397 } 398 return b; 399 } catch (Exception e) { 400 LoggerFactory.getLogger(BaseFile.class).error(e.getMessage(), e); 401 return false; 402 } 403 } 404 405 public boolean isDir() throws IOException { 406 return isDirectory(); 407 } 408 409 public Timestamp timeZoneConver(String destTimeZone, String srcTimeZone, Timestamp srcDate) throws IOException { 410 try{ 411 String pattern = "yyyy-MM-dd HH:mm:ss.SSS"; 412 SimpleDateFormat fullFormat = new SimpleDateFormat(pattern); 413 DateTimeFormatter format = DateTimeFormatter.ofPattern(pattern); 414 String srcDateStr = fullFormat.format(srcDate); 415 LocalDateTime ldt = LocalDateTime.parse(srcDateStr, DateTimeFormatter.ofPattern(pattern)); 416 ZoneId srcZoneId = ZoneId.of(srcTimeZone); 417 ZonedDateTime srcDateTime = ldt.atZone(srcZoneId); 418 ZoneId destZoneId = ZoneId.of(destTimeZone); 419 ZonedDateTime destDateTime = srcDateTime.withZoneSameInstant(destZoneId); 420 return new Timestamp(fullFormat.parse(format.format(destDateTime)).getTime()); 421 }catch(Exception e){ 422 throw new IOException(e.getMessage(),e); 423 } 424 } 425 426 protected static String replacePath(String source){ 427 return source.replaceAll("\\\\", "/"); 428 } 429 430 protected static String parsePath(String source){ 431 return source.replaceAll("\\\\", "/").replaceAll("//","/"); 432 } 433 434 public static boolean checkUsage(RemoteFile rf){ 435 return memoryUsage() < RemoteFile.MAX_MEMORY_USAGE && rf.getDataSourceWriter().getUsage() < RemoteFile.MAX_CONNECT_USAGE && rf.getDataSourceReader().getUsage() < RemoteFile.MAX_CONNECT_USAGE; 436 } 437 438 439 public static long memoryUsing(){ 440 int mb = 1024 * 1024; 441 Runtime instance = Runtime.getRuntime(); 442 long totalMemory = instance.totalMemory() / mb; 443 long freeMemory = instance.freeMemory() / mb; 444 return (instance.totalMemory() - instance.freeMemory()) / mb; 445 } 446 447 public static double memoryUsage(long using){ 448 int mb = 1024 * 1024; 449 Runtime instance = Runtime.getRuntime(); 450 long maxMemory = instance.maxMemory() / mb; 451 return (using * 1.0D / maxMemory * 1.0D); 452 } 453 454 public static double memoryUsage(){ 455 return memoryUsage(memoryUsing()); 456 } 457 458 protected synchronized static void debugLog(Class clazz,String point,RemoteFile rf,CacheArrayFilter filter){ 459 long ctm = System.currentTimeMillis(); 460 boolean canWrite = (ctm - debugLogTime) >= 60000L; 461 if(canWrite){ 462 DiskFile.checkQueuePathMapping(); 463 RemoteFile.checkQueuePathMapping(); 464 debugLogTime = ctm; 465 long memoryUsing = memoryUsing(); 466 String readerUsage = String.format("%.2f%%",rf.getDataSourceReader().getUsage()*100); 467 String writerUsage = String.format("%.2f%%",rf.getDataSourceWriter().getUsage()*100); 468 String memoryUsage = String.format("%.2f%%",memoryUsage(memoryUsing)*100); 469 LoggerFactory.getLogger(clazz).mark("************{}************",point); 470 LoggerFactory.getLogger(clazz).mark("[{}] DiskQueuePathMapping - {}",point,DiskFile.getQueuePathMapping().keySet().size()); 471 LoggerFactory.getLogger(clazz).mark("[{}] RemoteQueuePathMapping - {}",point,RemoteFile.getQueuePathMapping().keySet().size()); 472 LoggerFactory.getLogger(clazz).mark("[{}] CacheArrayPoolUsage - {}",point,filter.getCacheArray().getUsingPoolSize()); 473 LoggerFactory.getLogger(clazz).mark("[{}] DataSourceReaderUsage - {}",point,readerUsage); 474 LoggerFactory.getLogger(clazz).mark("[{}] DataSourceWriterUsage - {}",point,writerUsage); 475 LoggerFactory.getLogger(clazz).mark("[{}] MemoryUsage - {}mb ({})",point,memoryUsing,memoryUsage); 476 } 477 } 478 479}