Merge pull request #2925 from nextcloud/remove-close-wrapper

replace close:// streamwrapper with CallBackWrapper
This commit is contained in:
Roeland Jago Douma 2017-01-10 10:21:10 +01:00 committed by GitHub
commit b847dfcee9
14 changed files with 143 additions and 357 deletions

View File

@ -42,6 +42,7 @@ require_once 'aws-autoloader.php';
use Aws\S3\S3Client;
use Aws\S3\Exception\S3Exception;
use Icewind\Streams\CallbackWrapper;
use Icewind\Streams\IteratorDirectory;
use OC\Files\ObjectStore\S3ConnectionTrait;
@ -366,14 +367,15 @@ class AmazonS3 extends \OC\Files\Storage\Common {
$ext = '';
}
$tmpFile = \OCP\Files::tmpFile($ext);
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
if ($this->file_exists($path)) {
$source = $this->fopen($path, 'r');
file_put_contents($tmpFile, $source);
}
self::$tmpFiles[$tmpFile] = $path;
return fopen('close://' . $tmpFile, $mode);
$handle = fopen($tmpFile, $mode);
return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
$this->writeBack($tmpFile, $path);
});
}
return false;
}
@ -514,15 +516,11 @@ class AmazonS3 extends \OC\Files\Storage\Common {
return $this->id;
}
public function writeBack($tmpFile) {
if (!isset(self::$tmpFiles[$tmpFile])) {
return false;
}
public function writeBack($tmpFile, $path) {
try {
$this->getConnection()->putObject(array(
'Bucket' => $this->bucket,
'Key' => $this->cleanKey(self::$tmpFiles[$tmpFile]),
'Key' => $this->cleanKey($path),
'SourceFile' => $tmpFile,
'ContentType' => \OC::$server->getMimeTypeDetector()->detect($tmpFile),
'ContentLength' => filesize($tmpFile)

View File

@ -31,6 +31,7 @@
namespace OCA\Files_External\Lib\Storage;
use GuzzleHttp\Exception\RequestException;
use Icewind\Streams\CallbackWrapper;
use Icewind\Streams\IteratorDirectory;
use Icewind\Streams\RetryWrapper;
use OCP\Files\StorageNotAvailableException;
@ -45,8 +46,6 @@ class Dropbox extends \OC\Files\Storage\Common {
private $metaData = array();
private $oauth;
private static $tempFiles = array();
public function __construct($params) {
if (isset($params['configured']) && $params['configured'] == 'true'
&& isset($params['app_key'])
@ -305,27 +304,26 @@ class Dropbox extends \OC\Files\Storage\Common {
$ext = '';
}
$tmpFile = \OCP\Files::tmpFile($ext);
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
if ($this->file_exists($path)) {
$source = $this->fopen($path, 'r');
file_put_contents($tmpFile, $source);
}
self::$tempFiles[$tmpFile] = $path;
return fopen('close://'.$tmpFile, $mode);
$handle = fopen($tmpFile, $mode);
return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
$this->writeBack($tmpFile, $path);
});
}
return false;
}
public function writeBack($tmpFile) {
if (isset(self::$tempFiles[$tmpFile])) {
$handle = fopen($tmpFile, 'r');
try {
$this->dropbox->putFile(self::$tempFiles[$tmpFile], $handle);
unlink($tmpFile);
$this->deleteMetaData(self::$tempFiles[$tmpFile]);
} catch (\Exception $exception) {
\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
}
public function writeBack($tmpFile, $path) {
$handle = fopen($tmpFile, 'r');
try {
$this->dropbox->putFile($path, $handle);
unlink($tmpFile);
$this->deleteMetaData($path);
} catch (\Exception $exception) {
\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
}
}

View File

@ -33,6 +33,7 @@
namespace OCA\Files_External\Lib\Storage;
use Icewind\Streams\CallbackWrapper;
use Icewind\Streams\RetryWrapper;
class FTP extends StreamWrapper{
@ -127,21 +128,20 @@ class FTP extends StreamWrapper{
$ext='';
}
$tmpFile=\OCP\Files::tmpFile($ext);
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
if ($this->file_exists($path)) {
$this->getFile($path, $tmpFile);
}
self::$tempFiles[$tmpFile]=$path;
return fopen('close://'.$tmpFile, $mode);
$handle = fopen($tmpFile, $mode);
return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
$this->writeBack($tmpFile, $path);
});
}
return false;
}
public function writeBack($tmpFile) {
if (isset(self::$tempFiles[$tmpFile])) {
$this->uploadFile($tmpFile, self::$tempFiles[$tmpFile]);
unlink($tmpFile);
}
public function writeBack($tmpFile, $path) {
$this->uploadFile($tmpFile, $path);
unlink($tmpFile);
}
/**

View File

@ -36,6 +36,7 @@
namespace OCA\Files_External\Lib\Storage;
use GuzzleHttp\Exception\RequestException;
use Icewind\Streams\CallbackWrapper;
use Icewind\Streams\IteratorDirectory;
use Icewind\Streams\RetryWrapper;
@ -50,8 +51,6 @@ class Google extends \OC\Files\Storage\Common {
private $service;
private $driveFiles;
private static $tempFiles = array();
// Google Doc mimetypes
const FOLDER = 'application/vnd.google-apps.folder';
const DOCUMENT = 'application/vnd.google-apps.document';
@ -495,94 +494,91 @@ class Google extends \OC\Files\Storage\Common {
case 'c':
case 'c+':
$tmpFile = \OCP\Files::tmpFile($ext);
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
if ($this->file_exists($path)) {
$source = $this->fopen($path, 'rb');
file_put_contents($tmpFile, $source);
}
self::$tempFiles[$tmpFile] = $path;
return fopen('close://'.$tmpFile, $mode);
$handle = fopen($tmpFile, $mode);
return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
$this->writeBack($tmpFile, $path);
});
}
}
public function writeBack($tmpFile) {
if (isset(self::$tempFiles[$tmpFile])) {
$path = self::$tempFiles[$tmpFile];
$parentFolder = $this->getDriveFile(dirname($path));
if ($parentFolder) {
$mimetype = \OC::$server->getMimeTypeDetector()->detect($tmpFile);
$params = array(
'mimeType' => $mimetype,
'uploadType' => 'media'
);
$result = false;
public function writeBack($tmpFile, $path) {
$parentFolder = $this->getDriveFile(dirname($path));
if ($parentFolder) {
$mimetype = \OC::$server->getMimeTypeDetector()->detect($tmpFile);
$params = array(
'mimeType' => $mimetype,
'uploadType' => 'media'
);
$result = false;
$chunkSizeBytes = 10 * 1024 * 1024;
$chunkSizeBytes = 10 * 1024 * 1024;
$useChunking = false;
$size = filesize($tmpFile);
if ($size > $chunkSizeBytes) {
$useChunking = true;
} else {
$params['data'] = file_get_contents($tmpFile);
}
if ($this->file_exists($path)) {
$file = $this->getDriveFile($path);
$this->client->setDefer($useChunking);
$request = $this->service->files->update($file->getId(), $file, $params);
} else {
$file = new \Google_Service_Drive_DriveFile();
$file->setTitle(basename($path));
$file->setMimeType($mimetype);
$parent = new \Google_Service_Drive_ParentReference();
$parent->setId($parentFolder->getId());
$file->setParents(array($parent));
$this->client->setDefer($useChunking);
$request = $this->service->files->insert($file, $params);
}
if ($useChunking) {
// Create a media file upload to represent our upload process.
$media = new \Google_Http_MediaFileUpload(
$this->client,
$request,
'text/plain',
null,
true,
$chunkSizeBytes
);
$media->setFileSize($size);
// Upload the various chunks. $status will be false until the process is
// complete.
$status = false;
$handle = fopen($tmpFile, 'rb');
while (!$status && !feof($handle)) {
$chunk = fread($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
}
// The final value of $status will be the data from the API for the object
// that has been uploaded.
$result = false;
if ($status !== false) {
$result = $status;
}
fclose($handle);
} else {
$result = $request;
}
// Reset to the client to execute requests immediately in the future.
$this->client->setDefer(false);
if ($result) {
$this->setDriveFile($path, $result);
}
$useChunking = false;
$size = filesize($tmpFile);
if ($size > $chunkSizeBytes) {
$useChunking = true;
} else {
$params['data'] = file_get_contents($tmpFile);
}
if ($this->file_exists($path)) {
$file = $this->getDriveFile($path);
$this->client->setDefer($useChunking);
$request = $this->service->files->update($file->getId(), $file, $params);
} else {
$file = new \Google_Service_Drive_DriveFile();
$file->setTitle(basename($path));
$file->setMimeType($mimetype);
$parent = new \Google_Service_Drive_ParentReference();
$parent->setId($parentFolder->getId());
$file->setParents(array($parent));
$this->client->setDefer($useChunking);
$request = $this->service->files->insert($file, $params);
}
if ($useChunking) {
// Create a media file upload to represent our upload process.
$media = new \Google_Http_MediaFileUpload(
$this->client,
$request,
'text/plain',
null,
true,
$chunkSizeBytes
);
$media->setFileSize($size);
// Upload the various chunks. $status will be false until the process is
// complete.
$status = false;
$handle = fopen($tmpFile, 'rb');
while (!$status && !feof($handle)) {
$chunk = fread($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
}
// The final value of $status will be the data from the API for the object
// that has been uploaded.
$result = false;
if ($status !== false) {
$result = $status;
}
fclose($handle);
} else {
$result = $request;
}
// Reset to the client to execute requests immediately in the future.
$this->client->setDefer(false);
if ($result) {
$this->setDriveFile($path, $result);
}
unlink($tmpFile);
}
}

View File

@ -37,6 +37,7 @@ namespace OCA\Files_External\Lib\Storage;
use Guzzle\Http\Url;
use Guzzle\Http\Exception\ClientErrorResponseException;
use Icewind\Streams\CallbackWrapper;
use Icewind\Streams\IteratorDirectory;
use OpenCloud;
use OpenCloud\Common\Exceptions;
@ -410,7 +411,6 @@ class Swift extends \OC\Files\Storage\Common {
$ext = '';
}
$tmpFile = \OCP\Files::tmpFile($ext);
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
// Fetch existing file if required
if ($mode[0] !== 'w' && $this->file_exists($path)) {
if ($mode[0] === 'x') {
@ -424,9 +424,10 @@ class Swift extends \OC\Files\Storage\Common {
fseek($tmpFile, 0, SEEK_END);
}
}
self::$tmpFiles[$tmpFile] = $path;
return fopen('close://' . $tmpFile, $mode);
$handle = fopen($tmpFile, $mode);
return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
$this->writeBack($tmpFile, $path);
});
}
}
@ -615,12 +616,9 @@ class Swift extends \OC\Files\Storage\Common {
return $this->container;
}
public function writeBack($tmpFile) {
if (!isset(self::$tmpFiles[$tmpFile])) {
return false;
}
public function writeBack($tmpFile, $path) {
$fileData = fopen($tmpFile, 'r');
$this->getContainer()->uploadObject(self::$tmpFiles[$tmpFile], $fileData);
$this->getContainer()->uploadObject($path, $fileData);
// invalidate target object to force repopulation on fetch
$this->objectCache->remove(self::$tmpFiles[$tmpFile]);
unlink($tmpFile);

View File

@ -669,9 +669,6 @@ class OC {
OC\Log\ErrorHandler::register($debug);
}
// register the stream wrappers
stream_wrapper_register('close', 'OC\Files\Stream\Close');
\OC::$server->getEventLogger()->start('init_session', 'Initialize session');
OC_App::loadApps(array('session'));
if (!self::$CLI) {

View File

@ -554,7 +554,6 @@ return array(
'OC\\Files\\Storage\\Wrapper\\PermissionsMask' => $baseDir . '/lib/private/Files/Storage/Wrapper/PermissionsMask.php',
'OC\\Files\\Storage\\Wrapper\\Quota' => $baseDir . '/lib/private/Files/Storage/Wrapper/Quota.php',
'OC\\Files\\Storage\\Wrapper\\Wrapper' => $baseDir . '/lib/private/Files/Storage/Wrapper/Wrapper.php',
'OC\\Files\\Stream\\Close' => $baseDir . '/lib/private/Files/Stream/Close.php',
'OC\\Files\\Stream\\Encryption' => $baseDir . '/lib/private/Files/Stream/Encryption.php',
'OC\\Files\\Stream\\Quota' => $baseDir . '/lib/private/Files/Stream/Quota.php',
'OC\\Files\\Type\\Detection' => $baseDir . '/lib/private/Files/Type/Detection.php',

View File

@ -584,7 +584,6 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Files\\Storage\\Wrapper\\PermissionsMask' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/PermissionsMask.php',
'OC\\Files\\Storage\\Wrapper\\Quota' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Quota.php',
'OC\\Files\\Storage\\Wrapper\\Wrapper' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Wrapper.php',
'OC\\Files\\Stream\\Close' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/Close.php',
'OC\\Files\\Stream\\Encryption' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/Encryption.php',
'OC\\Files\\Stream\\Quota' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/Quota.php',
'OC\\Files\\Type\\Detection' => __DIR__ . '/../../..' . '/lib/private/Files/Type/Detection.php',

View File

@ -33,6 +33,8 @@
namespace OC\Archive;
use Icewind\Streams\CallbackWrapper;
class TAR extends Archive {
const PLAIN = 0;
const GZIP = 1;
@ -359,22 +361,19 @@ class TAR extends Archive {
if ($mode == 'r' or $mode == 'rb') {
return fopen($tmpFile, $mode);
} else {
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
self::$tempFiles[$tmpFile] = $path;
return fopen('close://' . $tmpFile, $mode);
$handle = fopen($tmpFile, $mode);
return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
$this->writeBack($tmpFile, $path);
});
}
}
private static $tempFiles = array();
/**
* write back temporary files
*/
function writeBack($tmpFile) {
if (isset(self::$tempFiles[$tmpFile])) {
$this->addFile(self::$tempFiles[$tmpFile], $tmpFile);
unlink($tmpFile);
}
function writeBack($tmpFile, $path) {
$this->addFile($path, $tmpFile);
unlink($tmpFile);
}
/**

View File

@ -31,6 +31,8 @@
namespace OC\Archive;
use Icewind\Streams\CallbackWrapper;
class ZIP extends Archive{
/**
* @var \ZipArchive zip
@ -198,24 +200,22 @@ class ZIP extends Archive{
$ext='';
}
$tmpFile=\OCP\Files::tmpFile($ext);
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
if($this->fileExists($path)) {
$this->extractFile($path, $tmpFile);
}
self::$tempFiles[$tmpFile]=$path;
return fopen('close://'.$tmpFile, $mode);
$handle = fopen($tmpFile, $mode);
return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
$this->writeBack($tmpFile, $path);
});
}
}
private static $tempFiles=array();
/**
* write back temporary files
*/
function writeBack($tmpFile) {
if(isset(self::$tempFiles[$tmpFile])) {
$this->addFile(self::$tempFiles[$tmpFile], $tmpFile);
unlink($tmpFile);
}
function writeBack($tmpFile, $path) {
$this->addFile($path, $tmpFile);
unlink($tmpFile);
}
/**

View File

@ -25,16 +25,12 @@
namespace OC\Files\ObjectStore;
use Icewind\Streams\CallbackWrapper;
use Icewind\Streams\IteratorDirectory;
use OC\Files\Cache\CacheEntry;
use OCP\Files\ObjectStore\IObjectStore;
class ObjectStoreStorage extends \OC\Files\Storage\Common {
/**
* @var array
*/
private static $tmpFiles = array();
/**
* @var \OCP\Files\ObjectStore\IObjectStore $objectStore
*/
@ -291,14 +287,14 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
$ext = '';
}
$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
if ($this->file_exists($path)) {
$source = $this->fopen($path, 'r');
file_put_contents($tmpFile, $source);
}
self::$tmpFiles[$tmpFile] = $path;
return fopen('close://' . $tmpFile, $mode);
$handle = fopen($tmpFile, $mode);
return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
$this->writeBack($tmpFile, $path);
});
}
return false;
}
@ -368,12 +364,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
return true;
}
public function writeBack($tmpFile) {
if (!isset(self::$tmpFiles[$tmpFile])) {
return;
}
$path = self::$tmpFiles[$tmpFile];
public function writeBack($tmpFile, $path) {
$stat = $this->stat($path);
if (empty($stat)) {
// create new file

View File

@ -36,6 +36,7 @@ namespace OC\Files\Storage;
use Exception;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Message\ResponseInterface;
use Icewind\Streams\CallbackWrapper;
use OC\Files\Filesystem;
use OC\Files\Stream\Close;
use Icewind\Streams\IteratorDirectory;
@ -77,8 +78,6 @@ class DAV extends Common {
private $client;
/** @var ArrayCache */
private $statCache;
/** @var array */
private static $tempFiles = [];
/** @var \OCP\Http\Client\IClientService */
private $httpClientService;
@ -409,20 +408,19 @@ class DAV extends Common {
}
$tmpFile = $tempManager->getTemporaryFile($ext);
}
Close::registerCallback($tmpFile, array($this, 'writeBack'));
self::$tempFiles[$tmpFile] = $path;
return fopen('close://' . $tmpFile, $mode);
$handle = fopen($tmpFile, $mode);
return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
$this->writeBack($tmpFile, $path);
});
}
}
/**
* @param string $tmpFile
*/
public function writeBack($tmpFile) {
if (isset(self::$tempFiles[$tmpFile])) {
$this->uploadFile($tmpFile, self::$tempFiles[$tmpFile]);
unlink($tmpFile);
}
public function writeBack($tmpFile, $path) {
$this->uploadFile($tmpFile, $path);
unlink($tmpFile);
}
/** {@inheritdoc} */

View File

@ -1,119 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Files\Stream;
/**
* stream wrapper that provides a callback on stream close
*/
class Close {
private static $callBacks = array();
private $path = '';
private $source;
private static $open = array();
public function stream_open($path, $mode, $options, &$opened_path) {
$path = substr($path, strlen('close://'));
$this->path = $path;
$this->source = fopen($path, $mode);
if (is_resource($this->source)) {
$this->meta = stream_get_meta_data($this->source);
}
self::$open[] = $path;
return is_resource($this->source);
}
public function stream_seek($offset, $whence = SEEK_SET) {
return fseek($this->source, $offset, $whence) === 0;
}
public function stream_tell() {
return ftell($this->source);
}
public function stream_read($count) {
return fread($this->source, $count);
}
public function stream_write($data) {
return fwrite($this->source, $data);
}
public function stream_set_option($option, $arg1, $arg2) {
switch ($option) {
case STREAM_OPTION_BLOCKING:
stream_set_blocking($this->source, $arg1);
break;
case STREAM_OPTION_READ_TIMEOUT:
stream_set_timeout($this->source, $arg1, $arg2);
break;
case STREAM_OPTION_WRITE_BUFFER:
stream_set_write_buffer($this->source, $arg1, $arg2);
}
}
public function stream_stat() {
return fstat($this->source);
}
public function stream_lock($mode) {
flock($this->source, $mode);
}
public function stream_flush() {
return fflush($this->source);
}
public function stream_eof() {
return feof($this->source);
}
public function url_stat($path) {
$path = substr($path, strlen('close://'));
if (file_exists($path)) {
return stat($path);
} else {
return false;
}
}
public function stream_close() {
fclose($this->source);
if (isset(self::$callBacks[$this->path])) {
call_user_func(self::$callBacks[$this->path], $this->path);
}
}
public function unlink($path) {
$path = substr($path, strlen('close://'));
return unlink($path);
}
/**
* @param string $path
*/
public static function registerCallback($path, $callback) {
self::$callBacks[$path] = $callback;
}
}

View File

@ -1,68 +0,0 @@
<?php
/**
* ownCloud
*
* @author Robin Appelman
* @copyright 2012 Robin Appelman icewind@owncloud.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Test;
/**
* Class StreamWrappersTest
*
* @group DB
*/
class StreamWrappersTest extends \Test\TestCase {
private static $trashBinStatus;
public static function setUpBeforeClass() {
self::$trashBinStatus = \OC_App::isEnabled('files_trashbin');
\OC_App::disable('files_trashbin');
}
public static function tearDownAfterClass() {
if (self::$trashBinStatus) {
(new \OC_App())->enable('files_trashbin');
}
}
public function testCloseStream() {
//ensure all basic stream stuff works
$sourceFile = \OC::$SERVERROOT . '/tests/data/lorem.txt';
$tmpFile = \OC::$server->getTempManager()->getTemporaryFile('.txt');
$file = 'close://' . $tmpFile;
$this->assertTrue(file_exists($file));
file_put_contents($file, file_get_contents($sourceFile));
$this->assertEquals(file_get_contents($sourceFile), file_get_contents($file));
unlink($file);
clearstatcache();
$this->assertFalse(file_exists($file));
//test callback
$tmpFile = \OC::$server->getTempManager()->getTemporaryFile('.txt');
$file = 'close://' . $tmpFile;
$actual = false;
$callback = function($path) use (&$actual) { $actual = $path; };
\OC\Files\Stream\Close::registerCallback($tmpFile, $callback);
$fh = fopen($file, 'w');
fwrite($fh, 'asd');
fclose($fh);
$this->assertSame($tmpFile, $actual);
}
}