summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBernhard Posselt <nukeawhale@gmail.com>2013-09-11 16:42:03 +0200
committerBernhard Posselt <nukeawhale@gmail.com>2013-09-12 01:00:32 +0200
commit09f60e75c90e5734a3b11a0cca944bd42bc41665 (patch)
tree8ecdc41cd6bb7c2b338e8e82b2b1e090b4d82ce4
parent24cab805e7484a5d206974d05f8de38641435f8c (diff)
#342 implement export
-rw-r--r--appinfo/routes.php6
-rw-r--r--businesslayer/itembusinesslayer.php9
-rw-r--r--controller/exportcontroller.php39
-rw-r--r--css/settings.css16
-rw-r--r--db/item.php17
-rw-r--r--db/itemmapper.php9
-rw-r--r--dependencyinjection/dicontainer.php1
-rw-r--r--img/download.svg58
-rw-r--r--templates/part.settings.php28
-rw-r--r--tests/unit/businesslayer/ItemBusinessLayerTest.php12
-rw-r--r--tests/unit/controller/ExportControllerTest.php62
-rw-r--r--tests/unit/db/ItemMapperTest.php12
-rw-r--r--tests/unit/db/ItemTest.php40
-rw-r--r--tests/unit/utility/ConfigTest.php4
-rw-r--r--utility/config.php2
15 files changed, 301 insertions, 14 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 617b63c53..90c5f2378 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -196,6 +196,12 @@ $this->create('news_export_opml', '/export/opml')->get()->action(
}
);
+$this->create('news_export_articles', '/export/articles')->get()->action(
+ function($params){
+ App::main('ExportController', 'articles', $params, new DIContainer());
+ }
+);
+
/**
* User Settings
*/
diff --git a/businesslayer/itembusinesslayer.php b/businesslayer/itembusinesslayer.php
index 9280153a6..6a00d8ba1 100644
--- a/businesslayer/itembusinesslayer.php
+++ b/businesslayer/itembusinesslayer.php
@@ -228,4 +228,13 @@ class ItemBusinessLayer extends BusinessLayer {
}
+ /**
+ * @param string $userId from which user the items should be taken
+ * @return array of items which are starred or unread
+ */
+ public function getUnreadOrStarred($userId) {
+ return $this->mapper->findAllUnreadOrStarred($userId);
+ }
+
+
}
diff --git a/controller/exportcontroller.php b/controller/exportcontroller.php
index 637219706..357c54d54 100644
--- a/controller/exportcontroller.php
+++ b/controller/exportcontroller.php
@@ -29,9 +29,11 @@ use \OCA\AppFramework\Controller\Controller;
use \OCA\AppFramework\Core\API;
use \OCA\AppFramework\Http\Request;
use \OCA\AppFramework\Http\TextDownloadResponse;
+use \OCA\AppFramework\Http\JSONResponse;
use \OCA\News\BusinessLayer\FeedBusinessLayer;
use \OCA\News\BusinessLayer\FolderBusinessLayer;
+use \OCA\News\BusinessLayer\ItemBusinessLayer;
use \OCA\News\Utility\OPMLExporter;
class ExportController extends Controller {
@@ -39,15 +41,18 @@ class ExportController extends Controller {
private $opmlExporter;
private $folderBusinessLayer;
private $feedBusinessLayer;
+ private $itemBusinessLayer;
public function __construct(API $api, Request $request,
FeedBusinessLayer $feedBusinessLayer,
FolderBusinessLayer $folderBusinessLayer,
+ ItemBusinessLayer $itemBusinessLayer,
OPMLExporter $opmlExporter){
parent::__construct($api, $request);
$this->feedBusinessLayer = $feedBusinessLayer;
$this->folderBusinessLayer = $folderBusinessLayer;
$this->opmlExporter = $opmlExporter;
+ $this->itemBusinessLayer = $itemBusinessLayer;
}
@@ -57,12 +62,40 @@ class ExportController extends Controller {
* @CSRFExemption
*/
public function opml(){
- $user = $this->api->getUserId();
- $feeds = $this->feedBusinessLayer->findAll($user);
- $folders = $this->folderBusinessLayer->findAll($user);
+ $userId = $this->api->getUserId();
+ $feeds = $this->feedBusinessLayer->findAll($userId);
+ $folders = $this->folderBusinessLayer->findAll($userId);
$opml = $this->opmlExporter->build($folders, $feeds)->saveXML();
return new TextDownloadResponse($opml, 'subscriptions.opml', 'text/xml');
}
+ /**
+ * @IsAdminExemption
+ * @IsSubAdminExemption
+ * @CSRFExemption
+ */
+ public function articles(){
+ $userId = $this->api->getUserId();
+ $feeds = $this->feedBusinessLayer->findAll($userId);
+ $items = $this->itemBusinessLayer->getUnreadOrStarred($userId);
+
+ // build assoc array for fast access
+ $feedsDict = array();
+ foreach($feeds as $feed) {
+ $feedsDict['feed' . $feed->getId()] = $feed;
+ }
+
+ $articles = array();
+ foreach($items as $item) {
+ array_push($articles, $item->toExport($feedsDict));
+ }
+
+ $response = new JSONResponse($articles);
+ $response->addHeader('Content-Disposition',
+ 'attachment; filename="articles.json"');
+ return $response;
+ }
+
+
} \ No newline at end of file
diff --git a/css/settings.css b/css/settings.css
index 54338ed5f..7cba9dde0 100644
--- a/css/settings.css
+++ b/css/settings.css
@@ -34,4 +34,20 @@
#app-settings-content {
padding-bottom: 25px;
+}
+
+.upload-icon,
+.download-icon {
+ padding-left: 25px;
+ background-repeat: no-repeat;
+ background-position: 5px center;
+ opacity: .8;
+}
+
+.upload-icon {
+ background-image: url('%webroot%/core/img/actions/upload.svg');
+}
+
+.download-icon {
+ background-image: url('%appswebroot%/news/img/download.svg');
} \ No newline at end of file
diff --git a/db/item.php b/db/item.php
index 1326b65ba..c83da572d 100644
--- a/db/item.php
+++ b/db/item.php
@@ -109,6 +109,23 @@ class Item extends Entity implements IAPI {
}
+ public function toExport($feeds) {
+ return array(
+ 'guid' => $this->getGuid(),
+ 'url' => $this->getUrl(),
+ 'title' => $this->getTitle(),
+ 'author' => $this->getAuthor(),
+ 'pubDate' => $this->getPubDate(),
+ 'body' => $this->getBody(),
+ 'enclosureMime' => $this->getEnclosureMime(),
+ 'enclosureLink' => $this->getEnclosureLink(),
+ 'unread' => $this->isUnread(),
+ 'starred' => $this->isStarred(),
+ 'feedLink' => $feeds['feed'. $this->getFeedId()]->getLink()
+ );
+ }
+
+
public function setAuthor($name) {
parent::setAuthor(strip_tags($name));
}
diff --git a/db/itemmapper.php b/db/itemmapper.php
index 0a78b02df..8fa40e8eb 100644
--- a/db/itemmapper.php
+++ b/db/itemmapper.php
@@ -230,6 +230,15 @@ class ItemMapper extends Mapper implements IMapper {
}
+ public function findAllUnreadOrStarred($userId) {
+ $params = array($userId);
+ $status = StatusFlag::UNREAD | StatusFlag::STARRED;
+ $sql = 'AND ((`items`.`status` & ' . $status . ') > 0) ';
+ $sql = $this->makeSelectQuery($sql);
+ return $this->findAllRows($sql, $params);
+ }
+
+
public function findByGuidHash($guidHash, $feedId, $userId){
$sql = $this->makeSelectQuery(
'AND `items`.`guid_hash` = ? ' .
diff --git a/dependencyinjection/dicontainer.php b/dependencyinjection/dicontainer.php
index 8ad816f8d..77e7b4aa6 100644
--- a/dependencyinjection/dicontainer.php
+++ b/dependencyinjection/dicontainer.php
@@ -165,6 +165,7 @@ class DIContainer extends BaseContainer {
return new ExportController($c['API'], $c['Request'],
$c['FeedBusinessLayer'],
$c['FolderBusinessLayer'],
+ $c['ItemBusinessLayer'],
$c['OPMLExporter']);
});
diff --git a/img/download.svg b/img/download.svg
new file mode 100644
index 000000000..ef0618017
--- /dev/null
+++ b/img/download.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ height="16"
+ width="16"
+ version="1.1"
+ id="svg2"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="upload.svg">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1021"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="14.75"
+ inkscape:cx="8.1355932"
+ inkscape:cy="8"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+ <g
+ transform="matrix(-1,0,0,-1,16,1052.4)"
+ id="g4">
+ <path
+ d="M 10,1051.4 H 6 l -1,-7 H 1 l 7,-7 7,7 h -4 z"
+ id="path6"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/templates/part.settings.php b/templates/part.settings.php
index b245ee3be..9d7763978 100644
--- a/templates/part.settings.php
+++ b/templates/part.settings.php
@@ -8,25 +8,27 @@
}"></button>
</div>
-<div id="app-settings-content">
+<div id="app-settings-content" style="display:block">
<fieldset class="personalblock">
- <legend><strong><?php p($l->t('Import / Export OPML')); ?></strong></legend>
+ <legend><strong><?php p($l->t('Subscriptions')); ?></strong></legend>
- <input type="file" id="opml-upload" name="import"
+ <input type="file" id="opml-upload" name="import" accept="text/x-opml, text/xml"
oc-read-file="import($fileContent)"/>
<button title="<?php p($l->t('Import')); ?>"
+ class="upload-icon svg"
oc-forward-click="{selector:'#opml-upload'}">
<?php p($l->t('Import')); ?>
</button>
- <a title="<?php p($l->t('Export')); ?>" class="button"
+ <a title="<?php p($l->t('Export')); ?>" class="button download-icon svg"
href="<?php p(\OCP\Util::linkToRoute('news_export_opml')); ?>"
target="_blank"
ng-show="feedBusinessLayer.getNumberOfFeeds() > 0">
<?php p($l->t('Export')); ?>
</a>
<button
+ class="download-icon svg"
title="<?php p($l->t('Export')); ?>"
ng-hide="feedBusinessLayer.getNumberOfFeeds() > 0" disabled>
<?php p($l->t('Export')); ?>
@@ -39,16 +41,28 @@
</fieldset>
<fieldset class="personalblock">
- <legend><strong><?php p($l->t('Import Google Reader JSON')); ?></strong></legend>
- <p><?php p($l->t('To import starred and shared articles from Google
- Reader please upload the .json files from the Google Takeout archive')); ?>
+ <legend><strong><?php p($l->t('Unread/Starred Articles')); ?></strong></legend>
<input type="file" id="google-upload" name="importgoogle"
oc-read-file="importGoogleReader($fileContent)"/>
<button title="<?php p($l->t('Import')); ?>"
+ class="upload-icon svg"
oc-forward-click="{selector:'#google-upload'}">
<?php p($l->t('Import')); ?>
</button>
+ <a title="<?php p($l->t('Export')); ?>" class="button download-icon svg"
+ href="<?php p(\OCP\Util::linkToRoute('news_export_articles')); ?>"
+ target="_blank"
+ ng-show="feedBusinessLayer.getNumberOfFeeds() > 0">
+ <?php p($l->t('Export')); ?>
+ </a>
+ <button
+ class="download-icon svg"
+ title="<?php p($l->t('Export')); ?>"
+ ng-hide="feedBusinessLayer.getNumberOfFeeds() > 0" disabled>
+ <?php p($l->t('Export')); ?>
+ </button>
+
<p class="error" ng-show="jsonError">
<?php p($l->t('Error when importing: file does not contain valid JSON')); ?>
</p>
diff --git a/tests/unit/businesslayer/ItemBusinessLayerTest.php b/tests/unit/businesslayer/ItemBusinessLayerTest.php
index 21a776c05..5dc6c9895 100644
--- a/tests/unit/businesslayer/ItemBusinessLayerTest.php
+++ b/tests/unit/businesslayer/ItemBusinessLayerTest.php
@@ -348,6 +348,18 @@ class ItemBusinessLayerTest extends \OCA\AppFramework\Utility\TestUtility {
}
+ public function testGetUnreadOrStarred(){
+ $star = 18;
+
+ $this->mapper->expects($this->once())
+ ->method('findAllUnreadOrStarred')
+ ->with($this->equalTo($this->user))
+ ->will($this->returnValue($star));
+
+ $result = $this->itemBusinessLayer->getUnreadOrStarred($this->user);
+
+ $this->assertEquals($star, $result);
+ }
}
diff --git a/tests/unit/controller/ExportControllerTest.php b/tests/unit/controller/ExportControllerTest.php
index 29e0b6f71..2b9d41dde 100644
--- a/tests/unit/controller/ExportControllerTest.php
+++ b/tests/unit/controller/ExportControllerTest.php
@@ -27,11 +27,14 @@ namespace OCA\News\Controller;
use \OCA\AppFramework\Http\Request;
use \OCA\AppFramework\Http\TextDownloadResponse;
+use \OCA\AppFramework\Http\JSONResponse;
use \OCA\AppFramework\Utility\ControllerTestUtility;
use \OCA\AppFramework\Db\DoesNotExistException;
use \OCA\AppFramework\Db\MultipleObjectsReturnedException;
use \OCA\News\Utility\OPMLExporter;
+use \OCA\News\Db\Item;
+use \OCA\News\Db\Feed;
require_once(__DIR__ . "/../../classloader.php");
@@ -44,6 +47,7 @@ class ExportControllerTest extends ControllerTestUtility {
private $user;
private $feedBusinessLayer;
private $folderBusinessLayer;
+ private $itemBusinessLayer;
private $opmlExporter;
/**
@@ -51,6 +55,9 @@ class ExportControllerTest extends ControllerTestUtility {
*/
public function setUp(){
$this->api = $this->getAPIMock();
+ $this->itemBusinessLayer = $this->getMockBuilder('\OCA\News\BusinessLayer\ItemBusinessLayer')
+ ->disableOriginalConstructor()
+ ->getMock();
$this->feedBusinessLayer = $this->getMockBuilder('\OCA\News\BusinessLayer\FeedBusinessLayer')
->disableOriginalConstructor()
->getMock();
@@ -60,7 +67,8 @@ class ExportControllerTest extends ControllerTestUtility {
$this->request = new Request();
$this->opmlExporter = new OPMLExporter();
$this->controller = new ExportController($this->api, $this->request,
- $this->feedBusinessLayer, $this->folderBusinessLayer, $this->opmlExporter);
+ $this->feedBusinessLayer, $this->folderBusinessLayer,
+ $this->itemBusinessLayer, $this->opmlExporter);
$this->user = 'john';
}
@@ -72,6 +80,13 @@ class ExportControllerTest extends ControllerTestUtility {
}
+ public function testArticlesAnnotations(){
+ $annotations = array('IsAdminExemption', 'IsSubAdminExemption',
+ 'CSRFExemption');
+ $this->assertAnnotations($this->controller, 'articles', $annotations);
+ }
+
+
public function testOpmlExportNoFeeds(){
$opml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" .
@@ -100,4 +115,49 @@ class ExportControllerTest extends ControllerTestUtility {
}
+ public function testGetAllArticles(){
+ $item1 = new Item();
+ $item1->setFeedId(3);
+ $item2 = new Item();
+ $item2->setFeedId(5);
+
+ $feed1 = new Feed();
+ $feed1->setId(3);
+ $feed1->setLink('http://goo');
+ $feed2 = new Feed();
+ $feed2->setId(5);
+ $feed2->setLink('http://gee');
+ $feeds = array($feed1, $feed2);
+
+ $articles = array(
+ $item1, $item2
+ );
+
+ $this->api->expects($this->once())
+ ->method('getUserId')
+ ->will($this->returnValue($this->user));
+ $this->feedBusinessLayer->expects($this->once())
+ ->method('findAll')
+ ->with($this->equalTo($this->user))
+ ->will($this->returnValue($feeds));
+ $this->itemBusinessLayer->expects($this->once())
+ ->method('getUnreadOrStarred')
+ ->with($this->equalTo($this->user))
+ ->will($this->returnValue($articles));
+
+
+ $return = $this->controller->articles();
+ $headers = $return->getHeaders();
+ $this->assertTrue($return instanceof JSONResponse);
+ $this->assertEquals('attachment; filename="articles.json"', $headers ['Content-Disposition']);
+
+ $this->assertEquals('[{"guid":null,"url":null,"title":null,' .
+ '"author":null,"pubDate":null,"body":null,"enclosureMime":null,' .
+ '"enclosureLink":null,"unread":false,"starred":false,' .
+ '"feedLink":"http:\/\/goo"},{"guid":null,"url":null,"title":null,' .
+ '"author":null,"pubDate":null,"body":null,"enclosureMime":null,' .
+ '"enclosureLink":null,"unread":false,"starred":false,' .
+ '"feedLink":"http:\/\/gee"}]', $return->render());
+ }
+
} \ No newline at end of file
diff --git a/tests/unit/db/ItemMapperTest.php b/tests/unit/db/ItemMapperTest.php
index eb04b1514..ae045ce31 100644
--- a/tests/unit/db/ItemMapperTest.php
+++ b/tests/unit/db/ItemMapperTest.php
@@ -221,6 +221,18 @@ class ItemMapperTest extends \OCA\AppFramework\Utility\MapperTestUtility {
}
+ public function testFindAllUnreadOrStarred(){
+ $status = StatusFlag::UNREAD | StatusFlag::STARRED;
+ $sql = 'AND ((`items`.`status` & ' . $status . ') > 0) ';
+ $sql = $this->makeSelectQuery($sql);
+ $params = array($this->user);
+ $this->setMapperResult($sql, $params, $this->rows);
+ $result = $this->mapper->findAllUnreadOrStarred($this->user);
+
+ $this->assertEquals($this->items, $result);
+ }
+
+
public function testFindAllFeed(){
$sql = 'AND `items`.`feed_id` = ? ' .
'AND `items`.`id` < ? ';
diff --git a/tests/unit/db/ItemTest.php b/tests/unit/db/ItemTest.php
index daaf64a65..511badeeb 100644
--- a/tests/unit/db/ItemTest.php
+++ b/tests/unit/db/ItemTest.php
@@ -103,6 +103,46 @@ class ItemTest extends \PHPUnit_Framework_TestCase {
}
+ public function testToExport() {
+ $item = new Item();
+ $item->setId(3);
+ $item->setGuid('guid');
+ $item->setGuidHash('hash');
+ $item->setUrl('https://google');
+ $item->setTitle('title');
+ $item->setAuthor('author');
+ $item->setPubDate(123);
+ $item->setBody('body');
+ $item->setEnclosureMime('audio/ogg');
+ $item->setEnclosureLink('enclink');
+ $item->setFeedId(1);
+ $item->setStatus(0);
+ $item->setUnread();
+ $item->setStarred();
+ $item->setLastModified(321);
+
+ $feed = new Feed();
+ $feed->setLink('http://test');
+ $feeds = array(
+ "feed1" => $feed
+ );
+
+ $this->assertEquals(array(
+ 'guid' => 'guid',
+ 'url' => 'https://google',
+ 'title' => 'title',
+ 'author' => 'author',
+ 'pubDate' => 123,
+ 'body' => 'body',
+ 'enclosureMime' => 'audio/ogg',
+ 'enclosureLink' => 'enclink',
+ 'unread' => true,
+ 'starred' => true,
+ 'feedLink' => 'http://test'
+ ), $item->toExport($feeds));
+ }
+
+
public function testSetAuthor(){
$item = new Item();
$item->setAuthor('<a>my link</li>');
diff --git a/tests/unit/utility/ConfigTest.php b/tests/unit/utility/ConfigTest.php
index 479acabb5..0e2d6ab4e 100644
--- a/tests/unit/utility/ConfigTest.php
+++ b/tests/unit/utility/ConfigTest.php
@@ -51,7 +51,7 @@ class ConfigFetcherTest extends \OCA\AppFramework\Utility\TestUtility {
public function testDefaults() {
$this->assertEquals(60, $this->config->getAutoPurgeMinimumInterval());
- $this->assertEquals(200, $this->config->getAutoPurgeCount());
+ $this->assertEquals(5000, $this->config->getAutoPurgeCount());
$this->assertEquals(30*60, $this->config->getSimplePieCacheDuration());
$this->assertEquals(60, $this->config->getFeedFetcherTimeout());
$this->assertEquals(true, $this->config->getUseCronUpdates());
@@ -139,7 +139,7 @@ class ConfigFetcherTest extends \OCA\AppFramework\Utility\TestUtility {
$this->config->setUseCronUpdates(false);
$json = "autoPurgeMinimumInterval = 60\n" .
- "autoPurgeCount = 200\n" .
+ "autoPurgeCount = 5000\n" .
"simplePieCacheDuration = 1800\n" .
"feedFetcherTimeout = 60\n" .
"useCronUpdates = false";
diff --git a/utility/config.php b/utility/config.php
index 54145c993..3c0b1edb1 100644
--- a/utility/config.php
+++ b/utility/config.php
@@ -45,7 +45,7 @@ class Config {
public function __construct($fileSystem, API $api) {
$this->fileSystem = $fileSystem;
$this->autoPurgeMinimumInterval = 60;
- $this->autoPurgeCount = 200;
+ $this->autoPurgeCount = 5000;
$this->simplePieCacheDuration = 30*60;
$this->feedFetcherTimeout = 60;
$this->useCronUpdates = true;