PluginProbe
404 Solution / 4.3.3
404 Solution v4.3.3
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / php / FileSync.php

FileSync.php in 404 Solution 4.3.3, at includes/php/FileSync.php

103 lines 2.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 if (!defined('ABSPATH')) {
5 exit;
6 }
7
8 class ABJ_404_Solution_FileSync {
9
10 /** @var self|null */
11 private static $instance = null;
12 /**
13 * Test seam: install or clear the cached singleton instance without
14 * private-field reflection. Pass null to reset between tests; pass a
15 * configured instance (or double) to install it. Mirrors the setInstance()
16 * contract on DataAccess / PluginLogic (M105 singleton-reset seam).
17 *
18 * @param self|null $instance
19 * @return void
20 */
21 public static function setInstance($instance) {
22 self::$instance = $instance;
23 }
24
25
26 /** @return self */
27 public static function getInstance(): self {
28 if (self::$instance == null) {
29 self::$instance = new ABJ_404_Solution_FileSync();
30 }
31
32 return self::$instance;
33 }
34
35 /**
36 * @param string $key
37 * @return string
38 */
39 function getSyncFilePath(string $key): string {
40 $filePath = abj404_getUploadsDir() . 'SYNC_FILE_' . $key . '.txt';
41 return $filePath;
42 }
43
44 /**
45 * @param string $key
46 * @return string
47 */
48 function getOwnerFromFile(string $key): string {
49 $filePath = $this->getSyncFilePath($key);
50
51 // Read and catch rather than check-then-read, so there is no TOCTOU
52 // window between "does it exist" and "read it".
53 try {
54 $contents = ABJ_404_Solution_FileSystemService::readFileContents($filePath, false);
55 return $contents;
56 } catch (Exception $e) {
57 // Empty means "no lock owner" to every caller, and for a missing
58 // file that is exactly right. For a file that EXISTS and could not
59 // be read it is dangerous: a permissions problem or a full disk
60 // then presents as an unlocked resource, two workers proceed at
61 // once, and nothing downstream can tell the difference.
62 //
63 // So report rather than decide. This class does file I/O; whether
64 // an unreadable lock file should stop the caller is lock policy,
65 // and lock policy lives in LockOwnerStore. Deciding here also meant
66 // writing a log line from inside a getter, which is its own problem
67 // (lint-hidden-write-getters) and a fair one: a get* that emits
68 // telemetry surprises every caller.
69 if (is_file($filePath)) {
70 throw $e;
71 }
72 return "";
73 }
74 }
75
76 /**
77 * @param string $key
78 * @param string $uniqueID
79 * @return void
80 */
81 function writeOwnerToFile(string $key, string $uniqueID): void {
82 $filePath = $this->getSyncFilePath($key);
83
84 // Fixed: Check return value to handle write failures (disk full, permissions, etc.)
85 $result = @file_put_contents($filePath, $uniqueID, LOCK_EX);
86
87 if ($result === false) {
88 throw new Exception("Failed to write lock file: " . $filePath);
89 }
90 }
91
92 /**
93 * @param string $uniqueID
94 * @param string $key
95 * @return void
96 */
97 function releaseLock(string $uniqueID, string $key): void {
98 $filePath = $this->getSyncFilePath($key);
99 ABJ_404_Solution_FileSystemService::safeUnlink($filePath);
100 }
101
102 }
103