Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace OpenSpout\Reader\XLSX\Manager\SharedStringsCaching;
6
 
7
use OpenSpout\Common\Helper\FileSystemHelper;
8
use OpenSpout\Reader\Exception\SharedStringNotFoundException;
9
 
10
/**
11
 * This class implements the file-based caching strategy for shared strings.
12
 * Shared strings are stored in small files (with a max number of strings per file).
13
 * This strategy is slower than an in-memory strategy but is used to avoid out of memory crashes.
14
 *
15
 * @internal
16
 */
17
final class FileBasedStrategy implements CachingStrategyInterface
18
{
19
    /**
20
     * Value to use to escape the line feed character ("\n").
21
     */
22
    public const ESCAPED_LINE_FEED_CHARACTER = '_x000A_';
23
 
24
    /** @var FileSystemHelper Helper to perform file system operations */
25
    private readonly FileSystemHelper $fileSystemHelper;
26
 
27
    /** @var string Temporary folder where the temporary files will be created */
28
    private readonly string $tempFolder;
29
 
30
    /**
31
     * @var int Maximum number of strings that can be stored in one temp file
32
     *
33
     * @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
34
     */
35
    private readonly int $maxNumStringsPerTempFile;
36
 
37
    /** @var null|resource Pointer to the last temp file a shared string was written to */
38
    private $tempFilePointer;
39
 
40
    /**
41
     * @var string Path of the temporary file whose contents is currently stored in memory
42
     *
43
     * @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
44
     */
1441 ariadna 45
    private string $readMemoryTempFilePath = '';
1 efrain 46
 
1441 ariadna 47
    /** @var string Path of the temporary file whose contents is currently being written to */
48
    private string $writeMemoryTempFilePath = '';
49
 
1 efrain 50
    /**
51
     * @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
52
     *
53
     * @var string[] Contents of the temporary file that was last read
54
     */
55
    private array $inMemoryTempFileContents;
56
 
57
    /**
58
     * @param string $tempFolder               Temporary folder where the temporary files to store shared strings will be stored
59
     * @param int    $maxNumStringsPerTempFile Maximum number of strings that can be stored in one temp file
60
     */
61
    public function __construct(string $tempFolder, int $maxNumStringsPerTempFile)
62
    {
63
        $this->fileSystemHelper = new FileSystemHelper($tempFolder);
64
        $this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, uniqid('sharedstrings'));
65
 
66
        $this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile;
67
    }
68
 
69
    /**
70
     * Adds the given string to the cache.
71
     *
72
     * @param string $sharedString      The string to be added to the cache
73
     * @param int    $sharedStringIndex Index of the shared string in the sharedStrings.xml file
74
     */
75
    public function addStringForIndex(string $sharedString, int $sharedStringIndex): void
76
    {
77
        $tempFilePath = $this->getSharedStringTempFilePath($sharedStringIndex);
78
 
1441 ariadna 79
        if ($this->writeMemoryTempFilePath !== $tempFilePath) {
1 efrain 80
            if (null !== $this->tempFilePointer) {
81
                fclose($this->tempFilePointer);
82
            }
83
            $resource = fopen($tempFilePath, 'w');
84
            \assert(false !== $resource);
85
            $this->tempFilePointer = $resource;
1441 ariadna 86
            $this->writeMemoryTempFilePath = $tempFilePath;
1 efrain 87
        }
88
 
89
        // The shared string retrieval logic expects each cell data to be on one line only
90
        // Encoding the line feed character allows to preserve this assumption
91
        $lineFeedEncodedSharedString = $this->escapeLineFeed($sharedString);
92
 
93
        fwrite($this->tempFilePointer, $lineFeedEncodedSharedString.PHP_EOL);
94
    }
95
 
96
    /**
97
     * Closes the cache after the last shared string was added.
98
     * This prevents any additional string from being added to the cache.
99
     */
100
    public function closeCache(): void
101
    {
102
        // close pointer to the last temp file that was written
103
        if (null !== $this->tempFilePointer) {
1441 ariadna 104
            $this->writeMemoryTempFilePath = '';
1 efrain 105
            fclose($this->tempFilePointer);
106
        }
107
    }
108
 
109
    /**
110
     * Returns the string located at the given index from the cache.
111
     *
112
     * @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
113
     *
114
     * @return string The shared string at the given index
115
     *
116
     * @throws SharedStringNotFoundException If no shared string found for the given index
117
     */
118
    public function getStringAtIndex(int $sharedStringIndex): string
119
    {
120
        $tempFilePath = $this->getSharedStringTempFilePath($sharedStringIndex);
121
        $indexInFile = $sharedStringIndex % $this->maxNumStringsPerTempFile;
122
 
1441 ariadna 123
        if ($this->readMemoryTempFilePath !== $tempFilePath) {
124
            $contents = @file_get_contents($tempFilePath);
125
            if (false === $contents) {
126
                throw new SharedStringNotFoundException("Shared string temp file could not be read: {$tempFilePath} ; for index: {$sharedStringIndex}");
127
            }
1 efrain 128
            $this->inMemoryTempFileContents = explode(PHP_EOL, $contents);
1441 ariadna 129
            $this->readMemoryTempFilePath = $tempFilePath;
1 efrain 130
        }
131
 
132
        $sharedString = null;
133
 
134
        // Using isset here because it is way faster than array_key_exists...
135
        if (isset($this->inMemoryTempFileContents[$indexInFile])) {
136
            $escapedSharedString = $this->inMemoryTempFileContents[$indexInFile];
137
            $sharedString = $this->unescapeLineFeed($escapedSharedString);
138
        }
139
 
140
        if (null === $sharedString) {
141
            throw new SharedStringNotFoundException("Shared string not found for index: {$sharedStringIndex}");
142
        }
143
 
144
        return rtrim($sharedString, PHP_EOL);
145
    }
146
 
147
    /**
148
     * Destroys the cache, freeing memory and removing any created artifacts.
149
     */
150
    public function clearCache(): void
151
    {
152
        $this->fileSystemHelper->deleteFolderRecursively($this->tempFolder);
153
    }
154
 
155
    /**
156
     * Returns the path for the temp file that should contain the string for the given index.
157
     *
158
     * @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
159
     *
160
     * @return string The temp file path for the given index
161
     */
162
    private function getSharedStringTempFilePath(int $sharedStringIndex): string
163
    {
164
        $numTempFile = (int) ($sharedStringIndex / $this->maxNumStringsPerTempFile);
165
 
166
        return $this->tempFolder.\DIRECTORY_SEPARATOR.'sharedstrings'.$numTempFile;
167
    }
168
 
169
    /**
170
     * Escapes the line feed characters (\n).
171
     */
172
    private function escapeLineFeed(string $unescapedString): string
173
    {
174
        return str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString);
175
    }
176
 
177
    /**
178
     * Unescapes the line feed characters (\n).
179
     */
180
    private function unescapeLineFeed(string $escapedString): string
181
    {
182
        return str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString);
183
    }
184
}