diff --git a/README.md b/README.md index 1c79d03..2561fa5 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,11 @@ composer require aerex/baikal-storage-plugin ``` +## Configuration +Copy sample configuration to your baikal installation. Make sure that the folder is writable + ## Usage -- Add the plugin to `Core/Frameworks/Baikal/Core/Server.php` +- Add the plugin to `Core/Frameworks/Baikal/Core/Server.php` ``` -$this->server->addPlugin(new \Aerex\BaikalStorage\Plugin()) +$this->server->addPlugin(new \Aerex\BaikalStorage\Plugin()) ``` diff --git a/composer.json b/composer.json index 99ac68f..ec44c04 100644 --- a/composer.json +++ b/composer.json @@ -19,13 +19,13 @@ "php": ">=5.5", "sabre/dav" : "~4.0.2", "sabre/vobject": "^4.0", - "easycorp/easy-log-handler": "^1.0", "nesbot/carbon": "^2.0.0", "laminas/laminas-validator": "^2.13", "laminas/laminas-stdlib": "^3.2", "psr/container": "^1.0", "symfony/config": "3.4", - "symfony/process": "^3.4" + "symfony/process": "^3.4", + "monolog/monolog": "^2.0" }, "require-dev": { "phpunit/phpunit": "^7.4" diff --git a/lib/Configs/ConfigBuilder.php b/lib/Configs/ConfigBuilder.php index 92a9638..0ea0040 100644 --- a/lib/Configs/ConfigBuilder.php +++ b/lib/Configs/ConfigBuilder.php @@ -11,32 +11,18 @@ class ConfigBuilder implements ConfigurationInterface { private $configs = []; private $configDir; - public function __construct($configDir = null) { - if (!isset($configDir)) { - $this->configDir = $this->getHomeDir() . '~/.config/baikal'; - } else { - $this->configDir = $configDir; - } + public function __construct($configDir) { + $this->configDir = $configDir; $this->processor = new Processor(); } - private function getHomeDir() { - if (stristr(PHP_OS, 'WIN')) { - return rtrim($_SERVER['HOMEDRIVE'] . $_SERVER['HOMEPATH'], '\\/'); - } else { - return rtrim($_SERVER['HOME'], '/'); - } - - } - - public function add($config) { $this->configs[] = $config; } public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder('configs'); - $rootNode = $treeBuilder->getRootNode(); + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('configs'); $ref = $rootNode->children(); foreach ($this->configs as $config) { $ref = $ref->append($config->get()); @@ -46,10 +32,7 @@ class ConfigBuilder implements ConfigurationInterface { } public function readContent() { - if (!is_dir($this->configDir)) { - mkdir($this->configDir, 0755, true); - } - $contents = sprintf('%s/storage.yml', $this->configDir); + $contents = sprintf('%s/storage.yaml', $this->configDir); return file_get_contents($contents); } diff --git a/lib/Configs/TaskwarriorConfig.php b/lib/Configs/TaskwarriorConfig.php index 4fe47f3..fb9e972 100644 --- a/lib/Configs/TaskwarriorConfig.php +++ b/lib/Configs/TaskwarriorConfig.php @@ -6,12 +6,17 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder; class TaskwarriorConfig { public function get() { - $treeBuilder = new TreeBuilder('taskwarrior'); - $node = $treeBuilder->getRootNode(); + $treeBuilder = new TreeBuilder(); + $node = $treeBuilder->root('taskwarrior'); $node->children() - ->scalarNode('data_dir') - ->defaultValue('~/.task') - ->end(); + ->scalarNode('taskdata') + ->defaultValue('~/.task') + ->end() + ->scalarNode('taskrc') + ->defaultValue('~/.taskrc') + ->end() + ->end(); + return $node; } } diff --git a/lib/Console.php b/lib/Console.php index 02c485c..435e4e0 100644 --- a/lib/Console.php +++ b/lib/Console.php @@ -18,14 +18,15 @@ class Console extends AbstractConsole { } } - public function execute($cmd, $args, $input = null) { + public function execute($cmd, $args, $input = null, $envs = []) { $stdin[] = $cmd; - $stdin[] = array_merge($stdin, $this->defaultArgs, $args); + $stdin = array_merge($stdin, $this->defaultArgs, $args); if (isset($input)) { $stdin[] = $this->convertToString($input); - $process = new Process($stdin); } + $process = new Process(implode(' ', $stdin), $input, $envs); + $process->inheritEnvironmentVariables(); try { $process->mustRun(); diff --git a/lib/Plugin.php b/lib/Plugin.php index 0d6c8b2..bdbc4c7 100644 --- a/lib/Plugin.php +++ b/lib/Plugin.php @@ -31,11 +31,6 @@ class Plugin extends ServerPlugin { */ protected $storageManager; - /** - * @var ConfigBuilder - */ - - protected $config; /** * Creates the Taskwarrior plugin @@ -43,14 +38,16 @@ class Plugin extends ServerPlugin { * @param CalendarProcessor $TWCalManager * */ - function __construct($config = null){ - if (isset($config)) { - $this->config = $config; - } else { - $this->config = new ConfigBuilder(); - } - $this->storageManager = new StorageManager($this->config); - $this->addStorages(); + function __construct($configDir){ + $configs = $this->buildConfigurations($configDir); + $this->storageManager = new StorageManager($configs); + $this->initializeStorages($configDir, $configs); + } + + public function buildConfigurations($configDir) { + $this->config = new ConfigBuilder($configDir); + $this->config->add(new TaskwarriorConfig()); + return $this->config->loadYaml(); } /** @@ -58,10 +55,9 @@ class Plugin extends ServerPlugin { * */ - public function addStorages() { - $taskwarrior = new Taskwarrior(new Console(['rc.verbose=nothing', 'rc.hooks=off']), new TaskwarriorConfig()); + public function initializeStorages($configDir, $configs) { + $taskwarrior = new Taskwarrior(new Console(['rc.verbose=nothing', 'rc.hooks=off']), $configDir, $configs); $this->storageManager->addStorage(Taskwarrior::NAME, $taskwarrior); - $this->storageManager->init(); } /** diff --git a/lib/StorageManager.php b/lib/StorageManager.php index a8944fa..036bf11 100644 --- a/lib/StorageManager.php +++ b/lib/StorageManager.php @@ -3,7 +3,6 @@ namespace Aerex\BaikalStorage; use Sabre\VObject\Component\VCalendar as Calendar; -use Aerex\BaikalStorage\Configs\ConfigBuilder; class StorageManager { @@ -15,13 +14,12 @@ class StorageManager { /** - * @var Config + * @var array() */ - private $configBuilder; private $configs; - public function __construct($configBuilder){ - $this->configBuilder = $configBuilder; + public function __construct($configs){ + $this->configs = $configs; } public function getStorages() { @@ -33,14 +31,9 @@ class StorageManager { } public function addStorage($name, $storage) { - $this->configBuilder->add($storage->getConfig()); $this->storages[$name] = $storage; } - public function init() { - $this->configs = $this->configBuilder->loadYaml(); - } - public function import(Calendar $calendar) { if (!isset($this->configs)) { throw new \Exception('StorageManger was not initialize or configs are not defined'); @@ -50,7 +43,6 @@ class StorageManager { if (!isset($storage)){ throw new \Exception(); } - $storage->setRawConfigs($this->configs[$key]); $storage->save($calendar); } } diff --git a/lib/Storages/IStorage.php b/lib/Storages/IStorage.php index ba2891a..f35407f 100644 --- a/lib/Storages/IStorage.php +++ b/lib/Storages/IStorage.php @@ -8,5 +8,4 @@ interface IStorage { public function save(Calendar $c); public function refresh(); public function getConfig(); - public function setRawConfigs($rawConfigs); } diff --git a/lib/Storages/Taskwarrior.php b/lib/Storages/Taskwarrior.php index 01785d7..36a5cf7 100644 --- a/lib/Storages/Taskwarrior.php +++ b/lib/Storages/Taskwarrior.php @@ -8,25 +8,22 @@ use Carbon\Carbon; class Taskwarrior implements IStorage { private const DATA_FILES = ['pending.data', 'completed.data', 'undo.data']; - private $rawConfigs; public const NAME = 'taskwarrior'; private $tasks = []; - public function __construct($console, $config) { + private $configDir; + private $configs; + public function __construct($console, $configDir, $configs) { $this->console = $console; - $this->config = $config; + $this->configDir = $configDir; + $this->configs = $configs['taskwarrior']; } public function getConfig() { return $this->config; } - public function setRawConfigs($rawConfigs) { - $this->rawConfigs = $rawConfigs; - } - public function refresh() { - $dataDir = $this->rawConfigs['data_dir']; - $fp = fopen(sprintf('%s/taskwarrior-baikal-storage.lock', $dataDir), 'a'); + $fp = fopen(sprintf('%s/taskwarrior-baikal-storage.lock', $this->configDir), 'a'); if (!$fp || !flock($fp, LOCK_EX | LOCK_NB, $eWouldBlock) || $eWouldBlock) { fputs(STDERR, 'Could not get lock'); @@ -35,55 +32,125 @@ class Taskwarrior implements IStorage { $mtime = 0; $tasksUpdated = false; foreach (Taskwarrior::DATA_FILES as $dataFile) { - $fmtime = filemtime(sprintf('%s/%s', $this->config['data_dir'], $dataFile)); - if ($fmtime > $mtime) { - $mtime = $fmtime; - $tasksUpdated = true; - } + $fmtime = filemtime(sprintf('%s/%s', $this->configs['taskdata'], $dataFile)); + if ($fmtime > $mtime) { + $mtime = $fmtime; + $tasksUpdated = true; + } } if ($tasksUpdated) { - $tasks = $this->console->execute('task', ['export']); + $tasks = json_decode($this->console->execute('task', ['export'], null, + ['TASKRC' => $this->configs['taskrc'], 'TASKDATA' => $this->configs['taskdata']]), true); foreach ($tasks as $task) { $this->tasks[$task['uuid']] = $task; } } fclose($fp); - unlink(sprintf('%s/taskwarrior-baikal-storage.lock', $dataDir)); + unlink(sprintf('%s/taskwarrior-baikal-storage.lock', $this->configDir)); } public function vObjectToTask($vtodo) { - if ($this->tasks['uid'] == $vtodo->UID) { + if (isset($this->tasks['uid']) && $this->tasks['uid'] == $vtodo->UID) { $task = $this->tasks['uid']; } else { $task = []; - $task['uid'] = $vtodo->UID; + $task['uid'] = (string)$vtodo->UID; } if (!isset($vtodo->DESCRIPTION) && isset($vtodo->SUMMARY)){ - $task['description'] = $vtodo->SUMMARY; + $task['description'] = (string)$vtodo->SUMMARY; } else { - $task['description'] = $vtodo->DESCRIPTION; + $task['description'] = (string)$vtodo->DESCRIPTION; } if (isset($vtodo->DTSTAMP)){ $task['entry'] = new Carbon($vtodo->DTSTAMP->getDateTime()->format(\DateTime::W3C)); } + if (isset($vtodo->DTSTART)) { + $task['start'] = new Carbon($vtodo->DTSTART->getDateTime()->format(\DateTime::W3C)); + } + + if (isset($vtodo->DTEND)){ + $task['end'] = new Carbon($vtodo->DTEND->getDateTime()->format(\DateTime::W3C)); + } + + if (isset($vtodo->{'LAST-MODIFIED'})) { + $task['modified'] = new Carbon($vtodo->{'LAST-MODIFIED'}->getDateTime()->format(\DateTime::W3C)); + } + + if (isset($vtodo->PRIORITY)) { + $priority = $vtodo->PRIORITY->getJsonValue(); + if ($priority < 5) { + $task['priority'] = 'H'; + } else if ($priority === 5) { + $task['priority'] = 'M'; + } else if ($priority > 5 && $priority < 10) { + $task['priority'] = 'L'; + } + } + if (isset($vtodo->DUE)){ $task['due'] = new Carbon($vtodo->DUE->getDateTime()->format(\DateTime::W3C)); } - return $task; - } - - public function save(Calendar $c) { - if (!isset($c->VTODO)){ - throw new \Exception('Calendar event does not contain VTODO'); + if (isset($vtodo->RRULE)) { + $rules = $vtodo->RRULE->getParts(); + if (isset($rules['FREQ'])) { + $task['recu'] = $rules['FREQ']; + } + if (isset($rules['UNTIL'])) { + $task['until'] = $rules['UNTIL']; + } + } + + if (isset($vtodo->STATUS)) { + switch((string)$vtodo->STATUS) { + case 'NEEDS-ACTION': + $task['status'] = 'pending'; + break; + case 'COMPLETED': + $task['status'] = 'completed'; + if (!isset($task['end'])) { + $task['end'] = new Carbon($vtodo->DTSTAMP->getDateTime()->format(\DateTime::W3C)); + } + break; + case 'CANCELED': + $task['status'] = 'deleted'; + if (!isset($task['end'])) { + $task['end'] = new Carbon($vtodo->DTSTAMP->getDateTime()->format(\DateTime::W3C)); + } + break; + } + + } + + if (isset($vtodo->CATEGORIES)) { + $task['tags'] = []; + foreach ($vtodo->CATEGORIES as $category) { + if (isset($this->configs['project_tag_suffix'])) { + $projTagSuffixRegExp = sprintf('/^%s_/', $this->configs['project_tag_suffix']); + if (preg_match($category, $projTagSuffixRegExp)) { + $task['project'] = preg_replace($projTagSuffixRegExp, '', $category); + continue; + } + } + $task['tags'] = $category; + } + } + + return $task; + } + + public function save(Calendar $c) { + if (!isset($c->VTODO)){ + throw new \Exception('Calendar event does not contain VTODO'); + } + $this->refresh(); + $task = $this->vObjectToTask($c->VTODO); + $this->console->execute('task', ['import'], $task, + ['TASKRC' => $this->configs['taskrc'],'TASKDATA' => $this->configs['taskdata']]); + } } - $this->refresh(); - $task = $this->vObjectToTask($c->VTODO); - $this->console->execute('task', ['import'], $task); - } -} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e25f316..908e180 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,7 +9,7 @@ - ./lib/tests + ./lib/tests/ diff --git a/tests/Fixtures/taskwarrior_config.yml b/tests/Fixtures/taskwarrior_config.yml index ad36eae..d6cdbd4 100644 --- a/tests/Fixtures/taskwarrior_config.yml +++ b/tests/Fixtures/taskwarrior_config.yml @@ -1,2 +1,4 @@ taskwarrior: - data_dir: '~/task' + taskdata: '/home/user/.task' + taskrc: '~/home/user/.taskrc' + project_tag_suffix: 'project_' diff --git a/tests/StorageManagerTest.php b/tests/StorageManagerTest.php index f7c8d11..0bd36b6 100644 --- a/tests/StorageManagerTest.php +++ b/tests/StorageManagerTest.php @@ -31,40 +31,25 @@ class StorageManagerTest extends TestCase { } public function testAddTaskwarriorStorage() { - $this->mockConfigBuilder->expects($this->once()) - ->method('readContent') - ->willReturn(file_get_contents(__DIR__ . '/Fixtures/taskwarrior_config.yml')); - $tw = new Taskwarrior($this->mockConsole, new TaskwarriorConfig()); + $configs = ['taskwarrior' => ['taskrc' => '', 'taskdata' => '']]; + $tw = new Taskwarrior($this->mockConsole, '', $configs); $manager = new StorageManager($this->mockConfigBuilder); $manager->addStorage(Taskwarrior::NAME, $tw); $storages = $manager->getStorages(); - $manager->init(); $configs = $manager->getConfigs(); $this->assertEquals(sizeof(array_keys($storages)), 1, 'Taskwarrior storage was not added'); - $this->assertEquals(sizeof(array_keys($configs)), 1, 'Taskwarrior config was not loaded'); $this->assertArrayHasKey('taskwarrior', $storages, 'Storages should have taskwarrior'); - $this->assertArrayHasKey('taskwarrior', $configs, 'Configs should have taskwarrior'); } public function testTaskwarriorImport() { $cal = new Calendar(); - $this->mockConfigBuilder->expects($this->once()) - ->method('readContent') - ->willReturn(file_get_contents(__DIR__ . '/Fixtures/taskwarrior_config.yml')); $this->mockStorage->expects($this->once()) ->method('save') ->with($this->equalTo($cal)); - $this->mockStorage->expects($this->once()) - ->method('setRawConfigs') - ->with($this->equalTo(['data_dir' => '~/.task'])); - $this->mockStorage->expects($this->once()) - ->method('getConfig') - ->willReturn(new TaskwarriorConfig()); - $manager = new StorageManager($this->mockConfigBuilder); + $configs = ['taskwarrior' => ['taskrc' => '', 'taskdata' => '']]; + $manager = new StorageManager($configs); $manager->addStorage(Taskwarrior::NAME, $this->mockStorage); - $manager->init(); - $manager->import($cal); } diff --git a/tests/Storages/TaskwarriorTest.php b/tests/Storages/TaskwarriorTest.php new file mode 100644 index 0000000..48de9fd --- /dev/null +++ b/tests/Storages/TaskwarriorTest.php @@ -0,0 +1,51 @@ +mockConsole = $this->createMock(AbstractConsole::class); + } + + public function testVObjectToTask() { + $configs = ['taskwarrior' => ['taskrc' => '', 'taskdata' => '']]; + $this->taskwarrior = new Taskwarrior($this->mockConsole, '', $configs); + $vcalendar = new Calendar([ + 'VTODO' => [ + 'SUMMARY' => 'Finish project', + 'DTSTAMP' => new \DateTime('2020-07-04 10:00:00'), + 'DTSTART' => new \DateTime('2020-07-04 12:00:00'), + 'DTEND' => new \DateTime('2020-07-05 01:00:00'), + 'DUE' => new \DateTime('2020-07-05 03:00:00'), + 'LAST_MODIFIED' => new \DateTime('2020-07-04 13:00:00'), + 'PRIORITY' => 5, + 'RRULE' => 'FREQ=MONTHLY' + ] + ]); + echo $vcalendar->VTODO->RRULE->getJsonValue()[0]['freq']; + + $task = $this->taskwarrior->vObjectToTask($vcalendar->VTODO); + $this->assertArrayHasKey('uid', $task, 'task should have a uid'); + $this->assertEquals((string)$vcalendar->VTODO->UID, $task['uid']); + $this->assertArrayHasKey('description', $task, 'task should have description'); + $this->assertEquals((string)$vcalendar->VTODO->SUMMARY, $task['description']); + $this->assertArrayHasKey('due', $task, 'task should have due'); + $this->assertEquals($vcalendar->VTODO->DUE->getDateTime(), $task['due']); + $this->assertArrayHasKey('entry', $task, 'task should have an entry'); + $this->assertEquals($vcalendar->VTODO->DTSTAMP->getDateTime(), $task['entry']); + $this->assertArrayHasKey('start', $task, 'task should have start'); + $this->assertEquals($vcalendar->VTODO->DTSTART->getDateTime(), $task['start']); + } + +}