Skip to content

Commit

Permalink
Merge pull request #186 from plank/move-to-disk
Browse files Browse the repository at this point in the history
Add Media::moveToDisk() and copyToDisk() methods
  • Loading branch information
frasmage committed Jul 28, 2020
2 parents 7362587 + 7b4729d commit 8e67113
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 27 deletions.
16 changes: 13 additions & 3 deletions src/Exceptions/MediaMoveException.php
Expand Up @@ -9,11 +9,21 @@ class MediaMoveException extends Exception
{
public static function destinationExists(string $path): self
{
return new static("Another file already exists at `{$path}`");
return new static("Another file already exists at `{$path}`.");
}

public static function failedToCopy(string $from, string $to): self
public static function destinationExistsOnDisk(string $disk, string $path): self
{
return new static("Failed to copy file from `{$from}` to `{$to}`.");
return new static("Another file already exists at `{$path}` on disk `{$disk}`.");
}

public static function fileNotFound(string $disk, string $path, Exception $previous = null): self
{
return new static("File not found at `{$path}` on disk `{$disk}`.", 0, $previous);
}

public static function failedToCopy(string $from, string $to, Exception $previous = null): self
{
return new static("Failed to copy file from `{$from}` to `{$to}`.", 0, $previous);
}
}
47 changes: 44 additions & 3 deletions src/Media.php
Expand Up @@ -10,6 +10,7 @@
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Plank\Mediable\Exceptions\MediaMoveException;
use Plank\Mediable\Exceptions\MediaUrlException;
use Plank\Mediable\Helpers\File;
use Plank\Mediable\UrlGenerators\UrlGeneratorInterface;
Expand Down Expand Up @@ -254,10 +255,11 @@ public function contents(): string
* @param string $destination directory relative to disk root
* @param string $filename filename. Do not include extension
* @return void
* @throws MediaMoveException
*/
public function move(string $destination, string $filename = null): void
{
app('mediable.mover')->move($this, $destination, $filename);
$this->getMediaMover()->move($this, $destination, $filename);
}

/**
Expand All @@ -267,10 +269,49 @@ public function move(string $destination, string $filename = null): void
* @param string $destination directory relative to disk root
* @param string $filename optional filename. Do not include extension
* @return Media
* @throws MediaMoveException
*/
public function copyTo($destination, $filename = null): self
public function copyTo(string $destination, string $filename = null): self
{
return app('mediable.mover')->copyTo($this, $destination, $filename);
return $this->getMediaMover()->copyTo($this, $destination, $filename);
}

/**
* Move the file to a new location on another disk.
*
* Will invoke the `save()` method on the model after the associated file has been moved to prevent synchronization errors
* @param string $disk the disk to move the file to
* @param string $directory directory relative to disk root
* @param string $filename filename. Do not include extension
* @return void
* @throws MediaMoveException If attempting to change the file extension or a file with the same name already exists at the destination
*/
public function moveToDisk(string $disk, string $destination, string $filename = null): void
{
$this->getMediaMover()->moveToDisk($this, $disk, $destination, $filename);
}

/**
* Copy the file from one Media object to another one on a different disk.
*
* This method creates a new Media object as well as duplicates the associated file on the disk.
*
* @param Media $media The media to copy from
* @param string $disk the disk to copy the file to
* @param string $directory directory relative to disk root
* @param string $filename optional filename. Do not include extension
*
* @return Media
* @throws MediaMoveException If a file with the same name already exists at the destination or it fails to copy the file
*/
public function copyToDisk(string $disk, string $destination, string $filename = null): self
{
return $this->getMediaMover()->copyToDisk($this, $disk, $destination, $filename);
}

protected function getMediaMover(): MediaMover
{
return app('mediable.mover');
}

/**
Expand Down
116 changes: 104 additions & 12 deletions src/MediaMover.php
Expand Up @@ -3,6 +3,7 @@

namespace Plank\Mediable;

use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Filesystem\FilesystemManager;
use Plank\Mediable\Exceptions\MediaMoveException;

Expand Down Expand Up @@ -39,11 +40,7 @@ public function move(Media $media, string $directory, string $filename = null):
{
$storage = $this->filesystem->disk($media->disk);

if ($filename) {
$filename = $this->removeExtensionFromFilename($filename, $media->extension);
} else {
$filename = $media->filename;
}
$filename = $this->cleanFilename($media, $filename);

$directory = trim($directory, '/');
$targetPath = $directory . '/' . $filename . '.' . $media->extension;
Expand All @@ -59,6 +56,48 @@ public function move(Media $media, string $directory, string $filename = null):
$media->save();
}

/**
* Move the file to a new location on another disk.
*
* Will invoke the `save()` method on the model after the associated file has been moved to prevent synchronization errors
* @param Media $media
* @param string $disk the disk to move the file to
* @param string $directory directory relative to disk root
* @param string $filename filename. Do not include extension
* @return void
* @throws MediaMoveException If attempting to change the file extension or a file with the same name already exists at the destination
*/
public function moveToDisk(Media $media, string $disk, string $directory, string $filename = null): void
{
if ($media->disk === $disk) {
$this->move($media, $directory, $filename);
return;
}

$currentStorage = $this->filesystem->disk($media->disk);
$targetStorage = $this->filesystem->disk($disk);

$filename = $this->cleanFilename($media, $filename);
$directory = trim($directory, '/');
$targetPath = $directory . '/' . $filename . '.' . $media->extension;

if ($targetStorage->has($targetPath)) {
throw MediaMoveException::destinationExistsOnDisk($disk, $targetPath);
}

try {
$targetStorage->put($targetPath, $currentStorage->readStream($media->getDiskPath()));
$currentStorage->delete($media->getDiskPath());
} catch (FileNotFoundException $e) {
throw MediaMoveException::fileNotFound($media->disk, $media->getDiskPath(), $e);
}

$media->disk = $disk;
$media->filename = $filename;
$media->directory = $directory;
$media->save();
}

/**
* Copy the file from one Media object to another one.
*
Expand All @@ -75,11 +114,7 @@ public function copyTo(Media $media, string $directory, string $filename = null)
{
$storage = $this->filesystem->disk($media->disk);

if ($filename) {
$filename = $this->removeExtensionFromFilename($filename, $media->extension);
} else {
$filename = $media->filename;
}
$filename = $this->cleanFilename($media, $filename);

$directory = trim($directory, '/');
$targetPath = $directory . '/' . $filename . '.' . $media->extension;
Expand All @@ -90,13 +125,61 @@ public function copyTo(Media $media, string $directory, string $filename = null)

try {
$storage->copy($media->getDiskPath(), $targetPath);
} catch (\Exception $exception) {
throw MediaMoveException::failedToCopy($media->getDiskPath(), $targetPath);
} catch (\Exception $e) {
throw MediaMoveException::failedToCopy($media->getDiskPath(), $targetPath, $e);
}

// now we copy the Media object
/** @var Media $newMedia */
$newMedia = $media->replicate();
$newMedia->filename = $filename;
$newMedia->directory = $directory;

$newMedia->save();

return $newMedia;
}

/**
* Copy the file from one Media object to another one on a different disk.
*
* This method creates a new Media object as well as duplicates the associated file on the disk.
*
* @param Media $media The media to copy from
* @param string $disk the disk to copy the file to
* @param string $directory directory relative to disk root
* @param string $filename optional filename. Do not include extension
*
* @return Media
* @throws MediaMoveException If a file with the same name already exists at the destination or it fails to copy the file
*/
public function copyToDisk(Media $media, string $disk, string $directory, string $filename = null): Media
{
if ($media->disk === $disk) {
return $this->copyTo($media, $directory, $filename);
}

$currentStorage = $this->filesystem->disk($media->disk);
$targetStorage = $this->filesystem->disk($disk);

$filename = $this->cleanFilename($media, $filename);
$directory = trim($directory, '/');
$targetPath = $directory . '/' . $filename . '.' . $media->extension;

if ($targetStorage->has($targetPath)) {
throw MediaMoveException::destinationExistsOnDisk($disk, $targetPath);
}

try {
$targetStorage->put($targetPath, $currentStorage->readStream($media->getDiskPath()));
} catch (FileNotFoundException $e) {
throw MediaMoveException::fileNotFound($media->disk, $media->getDiskPath(), $e);
}

// now we copy the Media object
/** @var Media $newMedia */
$newMedia = $media->replicate();
$newMedia->disk = $disk;
$newMedia->filename = $filename;
$newMedia->directory = $directory;

Expand All @@ -105,6 +188,15 @@ public function copyTo(Media $media, string $directory, string $filename = null)
return $newMedia;
}

protected function cleanFilename(Media $media, ?string $filename): string
{
if ($filename) {
return $this->removeExtensionFromFilename($filename, $media->extension);
}

return $media->filename;
}

/**
* Remove the media's extension from a filename.
* @param string $filename
Expand Down
3 changes: 2 additions & 1 deletion src/MediableServiceProvider.php
Expand Up @@ -45,7 +45,8 @@ public function boot(): void
public function register(): void
{
$this->mergeConfigFrom(
__DIR__ . '/../config/mediable.php', 'mediable'
__DIR__ . '/../config/mediable.php',
'mediable'
);

$this->registerSourceAdapterFactory();
Expand Down
2 changes: 1 addition & 1 deletion tests/TestCase.php
Expand Up @@ -147,7 +147,7 @@ protected function alternateFilePath()

protected function remoteFilePath()
{
return 'https://www.plankdesign.com/externaluse/plank.png';
return 'https://raw.githubusercontent.com/plank/laravel-mediable/master/tests/_data/plank.png';
}

protected function sampleFile()
Expand Down
50 changes: 50 additions & 0 deletions tests/integration/MediaTest.php
Expand Up @@ -282,6 +282,56 @@ public function test_it_throws_an_exception_if_moving_to_existing_file()
$media1->move('', 'bar.baz');
}

public function test_it_can_be_moved_to_another_disk()
{
$this->useFilesystem('tmp');
$this->useFilesystem('uploads');

$this->useDatabase();

$media = $this->makeMedia([
'disk' => 'tmp',
'directory' => 'foo',
'filename' => 'bar',
'extension' => 'baz'
]);
$original_path = $media->getAbsolutePath();
$this->seedFileForMedia($media);

$media->moveToDisk('uploads', 'alpha/beta', 'gamma');
$this->assertEquals('uploads', $media->disk);
$this->assertEquals('alpha/beta/gamma.baz', $media->getDiskPath());
$this->assertTrue($media->fileExists());
$this->assertFalse(file_exists($original_path));
}

public function test_it_can_be_copied_to_another_disk()
{
$this->useFilesystem('tmp');
$this->useFilesystem('uploads');

$this->useDatabase();

$media = $this->makeMedia([
'disk' => 'tmp',
'directory' => 'foo',
'filename' => 'bar',
'extension' => 'baz'
]);
$original_path = $media->getAbsolutePath();
$this->seedFileForMedia($media);

$newMedia = $media->copyToDisk('uploads', 'alpha/beta', 'gamma');
$this->assertEquals('uploads', $newMedia->disk);
$this->assertEquals('alpha/beta/gamma.baz', $newMedia->getDiskPath());
$this->assertTrue($newMedia->fileExists());

//original should be unchanged
$this->assertEquals('tmp', $media->disk);
$this->assertEquals('foo/bar.baz', $media->getDiskPath());
$this->assertTrue($media->fileExists());
}

public function test_it_can_access_file_contents()
{
$this->useFilesystem('tmp');
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/MediaUploaderTest.php
Expand Up @@ -378,7 +378,7 @@ public function test_it_imports_http_stream_contents()
$this->useDatabase();
$this->useFilesystem('tmp');

$resource = fopen('https://www.plankdesign.com/externaluse/plank.png', 'r');
$resource = fopen($this->remoteFilePath(), 'r');

$media = Facade::fromSource($resource)
->toDestination('tmp', 'foo')
Expand Down
12 changes: 8 additions & 4 deletions tests/integration/MediableTest.php
Expand Up @@ -396,8 +396,10 @@ public function test_it_increments_order()
$mediable->attachMedia($media1, 'bar');
$mediable->attachMedia($media1, 'foo');

$this->assertEquals([2 => 1, 3 => 2, 1 => 3],
$mediable->getMedia('foo')->pluck('pivot.order', 'id')->toArray());
$this->assertEquals(
[2 => 1, 3 => 2, 1 => 3],
$mediable->getMedia('foo')->pluck('pivot.order', 'id')->toArray()
);
$this->assertEquals([1 => 1], $mediable->getMedia('bar')->pluck('pivot.order', 'id')->toArray());
}

Expand All @@ -410,8 +412,10 @@ public function test_it_increments_order_when_attaching_multiple()

$mediable->attachMedia([2, 3, 1], 'foo');

$this->assertEquals([2 => 1, 3 => 2, 1 => 3],
$mediable->getMedia('foo')->pluck('pivot.order', 'id')->toArray());
$this->assertEquals(
[2 => 1, 3 => 2, 1 => 3],
$mediable->getMedia('foo')->pluck('pivot.order', 'id')->toArray()
);
}

public function test_it_can_unset_order()
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/SourceAdapters/SourceAdapterTest.php
Expand Up @@ -47,7 +47,7 @@ public function adapterProvider()
{
$file = $this->sampleFilePath();
$string = file_get_contents($file);
$url = 'https://www.plankdesign.com/externaluse/plank.png?foo=bar.baz';
$url = $this->remoteFilePath() . '?foo=bar.baz';

$uploadedFile = new UploadedFile($file, 'plank.png', 'image/png', 7173, UPLOAD_ERR_OK, true);

Expand Down Expand Up @@ -83,7 +83,7 @@ public function adapterProvider()
public function invalidAdapterProvider()
{
$file = __DIR__ . '/../../_data/invalid.png';
$url = 'https://www.plankdesign.com/externaluse/invalid.png';
$url = 'https://raw.githubusercontent.com/plank/laravel-mediable/master/tests/_data/invalid.png';

$uploadedFile = new UploadedFile(
$file,
Expand Down

0 comments on commit 8e67113

Please sign in to comment.