Skip to content

Added ability to import a file into the repository #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 6, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# alpha2 / dev-master

- Added `--pretty` option to `session:export:view` command to output formatted XML
- Ask confirmation before overwriting file in `session:export:view`
## Features

- New command: `file:import`: - import files into the repository.

## Improvements

- `session:export:view`: Added `--pretty` option to `session:export:view` command to output formatted XML.
- `session:export:view`: Ask confirmation before overwriting file.
9 changes: 9 additions & 0 deletions features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,15 @@ public function thereExistsAPropertyAt($arg1)
$session->getProperty($arg1);
}

/**
* @Given /^there should exist a property at "([^"]*)"$/
*/
public function thereShouldExistAPropertyAt($arg1)
{
$session = $this->getSession();
$session->getProperty($arg1);
}

/**
* @Given /^there should not exist a property at "([^"]*)"$/
*/
Expand Down
Binary file added features/fixtures/files/phpcrlogos.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions features/phpcr_file_import.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
Feature: Import an external file as to a node
In order to import an external file into the system
As a user that is logged into the shell
I should be able to run a command which does that

Background:
Given that I am logged in as "testuser"
And the "cms.xml" fixtures are loaded
And the file "phpcr.png" contains the contents of "files/phpcrlogos.png"
And the current node is "/"

Scenario: Import a file
Given I execute the "file:import . phpcr.png" command
Then the command should not fail
And I save the session
Then the command should not fail
And there should exist a node at "/phpcr.png"
And the node at "/phpcr.png/jcr:content" should have the property "jcr:mimeType" with value "image/png"

Scenario: Import a file onto existing file, no overwrite specified
Given I execute the "file:import . phpcr.png" command
And I execute the "file:import . phpcr.png" command
Then the command should fail

Scenario: Import a file onto existing file, force not specified
Given I execute the "file:import . phpcr.png --no-interaction" command
And I execute the "file:import . phpcr.png" command
Then the command should fail

Scenario: Import a file onto existing file, force specified
Given I execute the "file:import . phpcr.png" command
And I execute the "file:import . phpcr.png --force" command
Then the command should not fail
And I save the session
Then the command should not fail
And there should exist a node at "/phpcr.png"
And the node at "/phpcr.png/jcr:content" should have the property "jcr:mimeType" with value "image/png"

Scenario: Import a file, override mime type
Given I execute the "file:import . phpcr.png --mime-type=application/data" command
Then the command should not fail
And I save the session
Then the command should not fail
And there should exist a node at "/phpcr.png"
And the node at "/phpcr.png/jcr:content" should have the property "jcr:mimeType" with value "application/data"

Scenario: Import a file, specify a name
Given I execute the "file:import ./foobar.png phpcr.png --mime-type=application/data" command
Then the command should not fail
And I save the session
Then the command should not fail
And there should exist a node at "/foobar.png"

Scenario: Import a file to a specified property
Given I execute the "file:import ./ phpcr.png --no-container" command
Then the command should not fail
And I save the session
Then the command should not fail
And there should exist a property at "/phpcr.png"

Scenario: Import overwrite a specified property
Given I execute the "file:import ./ phpcr.png --no-container" command
And I save the session
And I execute the "file:import ./ phpcr.png --no-container" command
Then the command should not fail
And I save the session
Then the command should not fail
And there should exist a property at "/phpcr.png"
1 change: 1 addition & 0 deletions src/PHPCR/Shell/Console/Application/ShellApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ private function registerCommands()
$this->add(new CommandPhpcr\NodeCreateCommand());
$this->add(new CommandPhpcr\NodeCorrespondingCommand());
$this->add(new CommandPhpcr\NodeDefinitionCommand());
$this->add(new CommandPhpcr\NodeFileImportCommand());
$this->add(new CommandPhpcr\NodePropertySetCommand());
$this->add(new CommandPhpcr\NodeSetPrimaryTypeCommand());
$this->add(new CommandPhpcr\NodeRenameCommand());
Expand Down
126 changes: 126 additions & 0 deletions src/PHPCR/Shell/Console/Command/Phpcr/NodeFileImportCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

namespace PHPCR\Shell\Console\Command\Phpcr;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use PHPCR\PropertyType;
use Symfony\Component\Console\Input\InputOption;
use PHPCR\PathNotFoundException;

class NodeFileImportCommand extends Command
{
/**
* @var PHPCR\SessionInterface
*/
protected $session;

protected function configure()
{
$this->setName('file:import');
$this->setDescription('Import a file at the given path');
$this->addArgument('path', InputArgument::REQUIRED, 'Path to import file to');
$this->addArgument('file', InputArgument::REQUIRED, 'Path to file to import');
$this->addOption('mime-type', null, InputOption::VALUE_REQUIRED, 'Mime type (optional, auto-detected)');
$this->addOption('force', null, InputOption::VALUE_NONE, 'Force overwriting any existing node');
$this->addOption('no-container', null, InputOption::VALUE_NONE, 'Do not wrap in a JCR nt:file, but write directly to the specified property');
$this->setHelp(<<<HERE
Import an external file into the repository.

The file will be imported as a node of built-in type <comment>nt:file</comment>.

If a Node is specified as <info>path</info> then the filename of the imported file will be used
as the new node, otherwise, if the target <info>path</info> does not exist, then it is assumed
that the path is the target path for the new file, including the filename.

PHPCRSH> file:import ./ foobar.png
PHPCRSH> file:import ./barfoo.png foobar.png

In the first example above will create <info>/foobar.png</info>, whereas the second will create
<info>./barfoo.png</info>.

By default the file will be imported in a container, i.e. a node with type <info>nt:file</info>. In
addition to the file data, the node will contain metadata.

Alternatively you can specify the <info>--no-container</info> option to import directly to a single property.

The mime-type of the file (in the case where a container is used) will be automatically determined unless
specified with <info>--mime-type</info>.
HERE
);
}

public function execute(InputInterface $input, OutputInterface $output)
{
$this->session = $this->getHelper('phpcr')->getSession();

$filePath = $input->getArgument('file');
$force = $input->getOption('force');
$noContainer = $input->getOption('no-container');

$path = $this->session->getAbsPath($input->getArgument('path'));
$mimeType = $input->getOption('mime-type');
$filename = basename($filePath);

if (!file_exists($filePath)) {
throw new \InvalidArgumentException(sprintf(
'File "%s" does not exist.',
$filePath
));
}

try {
// first assume the user specified the path to the parent node
$parentNode = $this->session->getNode($path);
} catch (PathNotFoundException $e) {
// if the given path does not exist, assume that the basename is the target
// filename and the dirname the path to the parent node
$parentPath = dirname($path);
$parentNode = $this->session->getNode($parentPath);
$filename = basename($path);
}

$fhandle = fopen($filePath, 'r');

if ($noContainer) {
$this->importToProperty($fhandle, $filePath, $filename, $parentNode, $force);
} else {
$this->importToContainer($fhandle, $mimeType, $filePath, $filename, $parentNode, $force);
}
}

private function importToProperty($fhandle, $filePath, $filename, $parentNode, $force)
{
$parentNode->setProperty($filename, $fhandle, PropertyType::BINARY);
}

private function importToContainer($fhandle, $mimeType, $file, $filename, $parentNode, $force)
{
// if no mime-type specified, guess it.
if (!$mimeType) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file);
}

// handle existing node
if ($parentNode->hasNode($filename)) {
if (true === $force) {
$fileNode = $parentNode->getNode($filename);
$this->session->removeItem($fileNode->getPath());
} else {
throw new \InvalidArgumentException(sprintf(
'Node "%s" already has child "%s". Use --force to overwrite.',
$parentNode->getPath(),
$filename
));
}
}

$fileNode = $parentNode->addNode($filename, 'nt:file');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would add an optional nodename in case i don't want the basename of the file i import.

$contentNode = $fileNode->addNode('jcr:content', 'nt:unstructured');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could also allow to push a stream into any property of an existing node as an alternative. in some of our projects, we just put a binary stream into a property instead of having the nt:file node.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm. not sure how to elegantly handle that - it would be quite a different behavior (e.g. no node type detection, no wrapping node, etc). Maybe file:import /path/to/file --to-property=/foo/bar or another command file:get-contents /path/to/file /path/to/property

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the to-property option. a separate command clutters up the list of commands and can be counter-inuitive which to use. --to-property would require the containing node to already exist and create/update the property.

$contentNode->setProperty('jcr:data', $fhandle, PropertyType::BINARY);
$contentNode->setProperty('jcr:mimeType', $mimeType);
}
}