Skip to content

Commit

Permalink
Merge pull request #17 from peter-gribanov/write_to_tmp1.0
Browse files Browse the repository at this point in the history
Write sitemap.xml to temporary file
  • Loading branch information
peter-gribanov committed Jun 14, 2019
2 parents d47b875 + 656a7a8 commit 74095d0
Show file tree
Hide file tree
Showing 29 changed files with 661 additions and 488 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,15 +169,20 @@ $collection = new UrlBuilderCollection([
]);

// the file into which we will write our sitemap
$filename = __DIR__.'/sitemap.xml';
$filename_index = __DIR__.'/sitemap.xml';

// the file into which we will write sitemap part
// you must use the temporary directory if you don't want to overwrite the existing index file!!!
// the sitemap part file will be automatically moved to the directive with the sitemap index on close stream
$filename_part = sys_get_temp_dir().'/sitemap.xml';

// configure streamer
$render = new PlainTextSitemapRender();
$stream = new RenderFileStream($render, $filename)
$stream = new RenderFileStream($render, $filename_part)

// configure index streamer
$index_render = new PlainTextSitemapIndexRender();
$index_stream = new RenderFileStream($index_render, $stream, 'https://example.com/', $filename);
$index_stream = new RenderFileStream($index_render, $stream, 'https://example.com/', $filename_index);

// configure sitemap builder
$builder = new SilentSitemapBuilder($collection, $index_stream);
Expand Down
25 changes: 25 additions & 0 deletions src/Stream/Exception/FileAccessException.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,29 @@ final public static function notWritable($filename)
{
return new static(sprintf('File "%s" is not writable.', $filename));
}

/**
* @param string $filename
*
* @return static
*/
final public static function notReadable($filename)
{
return new static(sprintf('File "%s" is not readable.', $filename));
}

/**
* @param string $tmp_filename
* @param string $target_filename
*
* @return self
*/
final public static function failedOverwrite($tmp_filename, $target_filename)
{
return new self(sprintf(
'Failed to overwrite file "%s" from temporary file "%s".',
$target_filename,
$tmp_filename
));
}
}
17 changes: 16 additions & 1 deletion src/Stream/RenderBzip2FileStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class RenderBzip2FileStream implements FileStream
*/
private $filename = '';

/**
* @var string
*/
private $tmp_filename = '';

/**
* @var int
*/
Expand Down Expand Up @@ -71,8 +76,9 @@ public function open()
{
$this->state->open();

$this->tmp_filename = tempnam(sys_get_temp_dir(), 'sitemap');
if ((file_exists($this->filename) && !is_writable($this->filename)) ||
($this->handle = @bzopen($this->filename, 'w')) === false
($this->handle = @bzopen($this->tmp_filename, 'w')) === false
) {
throw FileAccessException::notWritable($this->filename);
}
Expand All @@ -87,6 +93,15 @@ public function close()
$this->state->close();
$this->write($this->end_string);
bzclose($this->handle);

if (!rename($this->tmp_filename, $this->filename)) {
unlink($this->tmp_filename);

throw FileAccessException::failedOverwrite($this->tmp_filename, $this->filename);
}

$this->handle = null;
$this->tmp_filename = '';
$this->counter = 0;
}

Expand Down
22 changes: 18 additions & 4 deletions src/Stream/RenderFileStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ class RenderFileStream implements FileStream
*/
private $filename = '';

/**
* @var string
*/
private $tmp_filename = '';

/**
* @var int
*/
Expand Down Expand Up @@ -77,10 +82,10 @@ public function open()
{
$this->state->open();

if ((file_exists($this->filename) && !is_writable($this->filename)) ||
($this->handle = @fopen($this->filename, 'wb')) === false
) {
throw FileAccessException::notWritable($this->filename);
$this->tmp_filename = tempnam(sys_get_temp_dir(), 'sitemap');

if (($this->handle = @fopen($this->tmp_filename, 'wb')) === false) {
throw FileAccessException::notWritable($this->tmp_filename);
}

$this->write($this->render->start());
Expand All @@ -93,6 +98,15 @@ public function close()
$this->state->close();
$this->write($this->end_string);
fclose($this->handle);

if (!rename($this->tmp_filename, $this->filename)) {
unlink($this->tmp_filename);

throw FileAccessException::failedOverwrite($this->tmp_filename, $this->filename);
}

$this->handle = null;
$this->tmp_filename = '';
$this->counter = 0;
$this->used_bytes = 0;
}
Expand Down
21 changes: 17 additions & 4 deletions src/Stream/RenderGzipFileStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ class RenderGzipFileStream implements FileStream
*/
private $filename = '';

/**
* @var string
*/
private $tmp_filename = '';

/**
* @var int
*/
Expand Down Expand Up @@ -84,10 +89,9 @@ public function open()
$this->state->open();

$mode = 'wb'.$this->compression_level;
if ((file_exists($this->filename) && !is_writable($this->filename)) ||
($this->handle = @gzopen($this->filename, $mode)) === false
) {
throw FileAccessException::notWritable($this->filename);
$this->tmp_filename = tempnam(sys_get_temp_dir(), 'sitemap');
if (($this->handle = @gzopen($this->tmp_filename, $mode)) === false) {
throw FileAccessException::notWritable($this->tmp_filename);
}

$this->write($this->render->start());
Expand All @@ -100,6 +104,15 @@ public function close()
$this->state->close();
$this->write($this->end_string);
gzclose($this->handle);

if (!rename($this->tmp_filename, $this->filename)) {
unlink($this->tmp_filename);

throw FileAccessException::failedOverwrite($this->tmp_filename, $this->filename);
}

$this->handle = null;
$this->tmp_filename = '';
$this->counter = 0;
}

Expand Down
110 changes: 94 additions & 16 deletions src/Stream/RenderIndexFileStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace GpsLab\Component\Sitemap\Stream;

use GpsLab\Component\Sitemap\Render\SitemapIndexRender;
use GpsLab\Component\Sitemap\Stream\Exception\FileAccessException;
use GpsLab\Component\Sitemap\Stream\Exception\OverflowException;
use GpsLab\Component\Sitemap\Stream\Exception\StreamStateException;
use GpsLab\Component\Sitemap\Stream\State\StreamState;
Expand All @@ -32,6 +33,11 @@ class RenderIndexFileStream implements FileStream
*/
private $state;

/**
* @var resource|null
*/
private $handle;

/**
* @var string
*/
Expand All @@ -42,6 +48,11 @@ class RenderIndexFileStream implements FileStream
*/
private $filename = '';

/**
* @var string
*/
private $tmp_filename = '';

/**
* @var int
*/
Expand All @@ -53,9 +64,9 @@ class RenderIndexFileStream implements FileStream
private $counter = 0;

/**
* @var string
* @var bool
*/
private $buffer = '';
private $empty_index = true;

/**
* @param SitemapIndexRender $render
Expand Down Expand Up @@ -84,16 +95,41 @@ public function open()
{
$this->state->open();
$this->substream->open();
$this->buffer = $this->render->start();

$this->tmp_filename = tempnam(sys_get_temp_dir(), 'sitemap_index');
if (($this->handle = @fopen($this->tmp_filename, 'wb')) === false) {
throw FileAccessException::notWritable($this->tmp_filename);
}

fwrite($this->handle, $this->render->start());
}

public function close()
{
$this->state->close();
$this->addSubStreamFileToIndex();
$this->substream->close();

// not add empty sitemap part to index
if (!$this->empty_index) {
$this->addSubStreamFileToIndex();
}

fwrite($this->handle, $this->render->end());
fclose($this->handle);

$this->moveParts();

// move the sitemap index file from the temporary directory to the target
if (!rename($this->tmp_filename, $this->filename)) {
unlink($this->tmp_filename);

throw FileAccessException::failedOverwrite($this->tmp_filename, $this->filename);
}

$this->removeOldParts();

file_put_contents($this->filename, $this->buffer.$this->render->end());
$this->buffer = '';
$this->handle = null;
$this->tmp_filename = '';
$this->counter = 0;
}

Expand All @@ -109,42 +145,51 @@ public function push(Url $url)
try {
$this->substream->push($url);
} catch (OverflowException $e) {
$this->substream->close();
$this->addSubStreamFileToIndex();
$this->substream->open();
$this->substream->push($url);
}

$this->empty_index = false;
++$this->counter;
}

private function addSubStreamFileToIndex()
{
$this->substream->close();

$filename = $this->substream->getFilename();
$indexed_filename = $this->getIndexPartFilename($filename, ++$this->index);
$last_mod = (new \DateTimeImmutable())->setTimestamp(filemtime($filename));

// rename sitemap file to the index part file
rename($filename, dirname($filename).'/'.$indexed_filename);
if (!file_exists($filename) || ($time = filemtime($filename)) === false) {
throw FileAccessException::notReadable($filename);
}

$last_mod = (new \DateTimeImmutable())->setTimestamp($time);

$this->buffer .= $this->render->sitemap($this->host.$indexed_filename, $last_mod);
// rename sitemap file to sitemap part
$new_filename = sys_get_temp_dir().'/'.$indexed_filename;
if (!rename($filename, $new_filename)) {
throw FileAccessException::failedOverwrite($filename, $new_filename);
}

fwrite($this->handle, $this->render->sitemap($indexed_filename, $last_mod));
}

/**
* @param string $filename
* @param string $path
* @param int $index
*
* @return string
*/
private function getIndexPartFilename($filename, $index)
private function getIndexPartFilename($path, $index)
{
// use explode() for correct add index
// sitemap.xml -> sitemap1.xml
// sitemap.xml.gz -> sitemap1.xml.gz

list($filename, $extension) = explode('.', basename($filename), 2);
list($path, $extension) = explode('.', basename($path), 2) + ['', ''];

return sprintf('%s%s.%s', $filename, $index, $extension);
return sprintf('%s%s.%s', $path, $index, $extension);
}

/**
Expand All @@ -154,4 +199,37 @@ public function count()
{
return $this->counter;
}

/**
* Move parts of the sitemap from the temporary directory to the target.
*/
private function moveParts()
{
$filename = $this->substream->getFilename();
for ($i = 1; $i <= $this->index; ++$i) {
$indexed_filename = $this->getIndexPartFilename($filename, $i);
$source = sys_get_temp_dir().'/'.$indexed_filename;
$target = dirname($this->filename).'/'.$indexed_filename;
if (!rename($source, $target)) {
throw FileAccessException::failedOverwrite($source, $target);
}
}
}

/**
* Remove old parts of the sitemap from the target directory.
*/
private function removeOldParts()
{
$filename = $this->substream->getFilename();
for ($i = $this->index + 1; true; ++$i) {
$indexed_filename = $this->getIndexPartFilename($filename, $i);
$target = dirname($this->filename).'/'.$indexed_filename;
if (file_exists($target)) {
unlink($target);
} else {
break;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @license http://opensource.org/licenses/MIT
*/

namespace GpsLab\Component\Sitemap\Tests\Unit\Builder\Sitemap;
namespace GpsLab\Component\Sitemap\Tests\Builder\Sitemap;

use GpsLab\Component\Sitemap\Builder\Sitemap\SilentSitemapBuilder;
use GpsLab\Component\Sitemap\Builder\Url\UrlBuilder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @license http://opensource.org/licenses/MIT
*/

namespace GpsLab\Component\Sitemap\Tests\Unit\Builder\Sitemap;
namespace GpsLab\Component\Sitemap\Tests\Builder\Sitemap;

use GpsLab\Component\Sitemap\Builder\Sitemap\SymfonySitemapBuilder;
use GpsLab\Component\Sitemap\Builder\Url\UrlBuilder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @license http://opensource.org/licenses/MIT
*/

namespace GpsLab\Component\Sitemap\Tests\Unit\Builder\Url;
namespace GpsLab\Component\Sitemap\Tests\Builder\Url;

use GpsLab\Component\Sitemap\Builder\Url\UrlBuilder;
use GpsLab\Component\Sitemap\Builder\Url\UrlBuilderCollection;
Expand Down
Loading

0 comments on commit 74095d0

Please sign in to comment.