aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJeremy Benoist <j0k3r@users.noreply.github.com>2016-09-19 07:15:40 +0200
committerGitHub <noreply@github.com>2016-09-19 07:15:40 +0200
commitda18a4682f124b02278860d23ac1d59dee995277 (patch)
treeeabbe9da7203eea41e0cb0ec3c26b9b6599cf58f
parent0ed8ce55b5caf2c88e8330afa83abef6c4aac9a4 (diff)
parent59b97fae996d8307b9d957d210d46200f6d206bf (diff)
downloadwallabag-da18a4682f124b02278860d23ac1d59dee995277.tar.gz
wallabag-da18a4682f124b02278860d23ac1d59dee995277.tar.zst
wallabag-da18a4682f124b02278860d23ac1d59dee995277.zip
Merge pull request #1941 from wallabag/v2-asynchronous-jobs
Use asynchronous jobs for imports
-rw-r--r--.travis.yml4
-rw-r--r--app/AppKernel.php1
-rw-r--r--app/DoctrineMigrations/Version20160911214952.php42
-rw-r--r--app/DoctrineMigrations/Version20160916201049.php46
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml3
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml3
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml3
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml3
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml3
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml3
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml3
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml3
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml3
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml3
-rw-r--r--app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml31
-rw-r--r--app/config/config.yml64
-rw-r--r--app/config/parameters.yml.dist12
-rw-r--r--app/config/parameters_test.yml2
-rw-r--r--composer.json8
-rw-r--r--docker-compose.yml13
-rw-r--r--docs/en/developer/rabbitmq.rst67
-rw-r--r--docs/en/developer/redis.rst62
-rw-r--r--src/Wallabag/CoreBundle/Command/InstallCommand.php9
-rw-r--r--src/Wallabag/CoreBundle/Controller/EntryController.php50
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php3
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php9
-rw-r--r--src/Wallabag/CoreBundle/Entity/Config.php31
-rw-r--r--src/Wallabag/CoreBundle/Entity/Entry.php23
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/ConfigType.php3
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml8
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.da.yml8
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.de.yml8
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.en.yml8
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.es.yml14
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml12
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml8
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.it.yml12
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml8
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml8
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml8
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml12
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig12
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig12
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig2
-rw-r--r--src/Wallabag/ImportBundle/Command/ImportCommand.php6
-rw-r--r--src/Wallabag/ImportBundle/Command/RedisWorkerCommand.php44
-rw-r--r--src/Wallabag/ImportBundle/Consumer/AMQPEntryConsumer.php17
-rw-r--r--src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php74
-rw-r--r--src/Wallabag/ImportBundle/Consumer/RedisEntryConsumer.php29
-rw-r--r--src/Wallabag/ImportBundle/Controller/ImportController.php62
-rw-r--r--src/Wallabag/ImportBundle/Controller/PocketController.php35
-rw-r--r--src/Wallabag/ImportBundle/Controller/ReadabilityController.php16
-rw-r--r--src/Wallabag/ImportBundle/Controller/WallabagController.php10
-rw-r--r--src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php10
-rw-r--r--src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php10
-rw-r--r--src/Wallabag/ImportBundle/Form/Type/UploadImportType.php1
-rw-r--r--src/Wallabag/ImportBundle/Import/AbstractImport.php144
-rw-r--r--src/Wallabag/ImportBundle/Import/PocketImport.php218
-rw-r--r--src/Wallabag/ImportBundle/Import/ReadabilityImport.php149
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagImport.php131
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV1Import.php15
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV2Import.php14
-rw-r--r--src/Wallabag/ImportBundle/Redis/Producer.php36
-rw-r--r--src/Wallabag/ImportBundle/Resources/config/rabbit.yml30
-rw-r--r--src/Wallabag/ImportBundle/Resources/config/redis.yml81
-rw-r--r--src/Wallabag/ImportBundle/Resources/config/services.yml5
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/Import/_workerEnabled.html.twig8
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/Import/check_queue.html.twig11
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/Pocket/index.html.twig12
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/Readability/index.html.twig2
-rw-r--r--src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig2
-rw-r--r--src/Wallabag/UserBundle/Resources/config/services.yml6
-rw-r--r--tests/Wallabag/ImportBundle/Command/ImportCommandTest.php86
-rw-r--r--tests/Wallabag/ImportBundle/Command/RedisWorkerCommandTest.php74
-rw-r--r--tests/Wallabag/ImportBundle/Consumer/AMQPEntryConsumerTest.php225
-rw-r--r--tests/Wallabag/ImportBundle/Consumer/RedisEntryConsumerTest.php225
-rw-r--r--tests/Wallabag/ImportBundle/Controller/PocketControllerTest.php30
-rw-r--r--tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php75
-rw-r--r--tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php74
-rw-r--r--tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php76
-rw-r--r--tests/Wallabag/ImportBundle/Import/PocketImportTest.php242
-rw-r--r--tests/Wallabag/ImportBundle/Import/ReadabilityImportTest.php97
-rw-r--r--tests/Wallabag/ImportBundle/Import/WallabagV1ImportTest.php87
-rw-r--r--tests/Wallabag/ImportBundle/Import/WallabagV2ImportTest.php83
-rw-r--r--tests/Wallabag/ImportBundle/fixtures/readability.json167
85 files changed, 2904 insertions, 465 deletions
diff --git a/.travis.yml b/.travis.yml
index 749b8ef7..c5fe7b01 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,9 @@
1language: php 1language: php
2 2
3services:
4 - rabbitmq
5 - redis
6
3# faster builds on docker-container setup 7# faster builds on docker-container setup
4sudo: false 8sudo: false
5 9
diff --git a/app/AppKernel.php b/app/AppKernel.php
index 96e45da8..52f85558 100644
--- a/app/AppKernel.php
+++ b/app/AppKernel.php
@@ -38,6 +38,7 @@ class AppKernel extends Kernel
38 new Wallabag\UserBundle\WallabagUserBundle(), 38 new Wallabag\UserBundle\WallabagUserBundle(),
39 new Wallabag\ImportBundle\WallabagImportBundle(), 39 new Wallabag\ImportBundle\WallabagImportBundle(),
40 new Wallabag\AnnotationBundle\WallabagAnnotationBundle(), 40 new Wallabag\AnnotationBundle\WallabagAnnotationBundle(),
41 new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
41 ]; 42 ];
42 43
43 if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { 44 if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
diff --git a/app/DoctrineMigrations/Version20160911214952.php b/app/DoctrineMigrations/Version20160911214952.php
new file mode 100644
index 00000000..35809cec
--- /dev/null
+++ b/app/DoctrineMigrations/Version20160911214952.php
@@ -0,0 +1,42 @@
1<?php
2
3namespace Application\Migrations;
4
5use Doctrine\DBAL\Migrations\AbstractMigration;
6use Doctrine\DBAL\Schema\Schema;
7use Symfony\Component\DependencyInjection\ContainerAwareInterface;
8use Symfony\Component\DependencyInjection\ContainerInterface;
9
10class Version20160911214952 extends AbstractMigration implements ContainerAwareInterface
11{
12 /**
13 * @var ContainerInterface
14 */
15 private $container;
16
17 public function setContainer(ContainerInterface $container = null)
18 {
19 $this->container = $container;
20 }
21
22 private function getTable($tableName)
23 {
24 return $this->container->getParameter('database_table_prefix') . $tableName;
25 }
26
27 /**
28 * @param Schema $schema
29 */
30 public function up(Schema $schema)
31 {
32 $this->addSql('INSERT INTO `'.$this->getTable('craue_config_setting').'` (`name`, `value`, `section`) VALUES (\'import_with_redis\', \'0\', \'import\')');
33 $this->addSql('INSERT INTO `'.$this->getTable('craue_config_setting').'` (`name`, `value`, `section`) VALUES (\'import_with_rabbitmq\', \'0\', \'import\')');
34 }
35
36 /**
37 * @param Schema $schema
38 */
39 public function down(Schema $schema)
40 {
41 }
42}
diff --git a/app/DoctrineMigrations/Version20160916201049.php b/app/DoctrineMigrations/Version20160916201049.php
new file mode 100644
index 00000000..202901e6
--- /dev/null
+++ b/app/DoctrineMigrations/Version20160916201049.php
@@ -0,0 +1,46 @@
1<?php
2
3namespace Application\Migrations;
4
5use Doctrine\DBAL\Migrations\AbstractMigration;
6use Doctrine\DBAL\Schema\Schema;
7use Symfony\Component\DependencyInjection\ContainerAwareInterface;
8use Symfony\Component\DependencyInjection\ContainerInterface;
9
10class Version20160916201049 extends AbstractMigration implements ContainerAwareInterface
11{
12 /**
13 * @var ContainerInterface
14 */
15 private $container;
16
17 public function setContainer(ContainerInterface $container = null)
18 {
19 $this->container = $container;
20 }
21
22 private function getTable($tableName)
23 {
24 return $this->container->getParameter('database_table_prefix') . $tableName;
25 }
26
27 /**
28 * @param Schema $schema
29 */
30 public function up(Schema $schema)
31 {
32 $this->addSql('ALTER TABLE '.$this->getTable('config').' ADD pocket_consumer_key VARCHAR(255) DEFAULT NULL');
33 $this->addSql("DELETE FROM `".$this->getTable('craue_config_setting')."` WHERE `name` = 'pocket_consumer_key';");
34 }
35
36 /**
37 * @param Schema $schema
38 */
39 public function down(Schema $schema)
40 {
41 $this->abortIf($this->connection->getDatabasePlatform()->getName() == 'sqlite', 'Migration can only be executed safely on \'mysql\' or \'postgresql\'.');
42
43 $this->addSql('ALTER TABLE `'.$this->getTable('config').'` DROP pocket_consumer_key');
44 $this->addSql("INSERT INTO `".$this->getTable('craue_config_setting')."` (`name`, `value`, `section`) VALUES ('pocket_consumer_key', NULL, 'import')");
45 }
46}
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml
index 3478d638..85079330 100644
--- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.da.yml
@@ -8,7 +8,8 @@ export_csv: Aktiver eksport til CSV
8export_json: Aktiver eksport til JSON 8export_json: Aktiver eksport til JSON
9export_txt: Aktiver eksport til TXT 9export_txt: Aktiver eksport til TXT
10export_xml: Aktiver eksport til XML 10export_xml: Aktiver eksport til XML
11pocket_consumer_key: Brugers nøgle til Pocket for at importere materialer (https://getpocket.com/developer/docs/authentication) 11# import_with_rabbitmq: Enable RabbitMQ to import data asynchronously
12# import_with_redis: Enable Redis to import data asynchronously
12shaarli_url: Shaarli-URL, hvis tjenesten er aktiv 13shaarli_url: Shaarli-URL, hvis tjenesten er aktiv
13share_diaspora: Aktiver deling til Diaspora 14share_diaspora: Aktiver deling til Diaspora
14share_mail: Aktiver deling med email 15share_mail: Aktiver deling med email
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml
index f655a27f..eaba14cd 100644
--- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.de.yml
@@ -8,7 +8,8 @@ export_csv: CSV-Export aktivieren
8export_json: JSON-Export aktivieren 8export_json: JSON-Export aktivieren
9export_txt: TXT-Export aktivieren 9export_txt: TXT-Export aktivieren
10export_xml: XML-Export aktivieren 10export_xml: XML-Export aktivieren
11pocket_consumer_key: Consumer-Key für Pocket, um Inhalte zu importieren (https://getpocket.com/developer/docs/authentication) 11# import_with_rabbitmq: Enable RabbitMQ to import data asynchronously
12# import_with_redis: Enable Redis to import data asynchronously
12shaarli_url: Shaarli-URL, sofern der Service aktiviert ist 13shaarli_url: Shaarli-URL, sofern der Service aktiviert ist
13share_diaspora: Teilen zu Diaspora aktiveren 14share_diaspora: Teilen zu Diaspora aktiveren
14share_mail: Teilen via E-Mail aktiveren 15share_mail: Teilen via E-Mail aktiveren
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml
index 48d0ec4b..8aaa27e7 100644
--- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.en.yml
@@ -8,7 +8,8 @@ export_csv: Enable CSV export
8export_json: Enable JSON export 8export_json: Enable JSON export
9export_txt: Enable TXT export 9export_txt: Enable TXT export
10export_xml: Enable XML export 10export_xml: Enable XML export
11pocket_consumer_key: Consumer key for Pocket to import contents (https://getpocket.com/developer/docs/authentication) 11import_with_rabbitmq: Enable RabbitMQ to import data asynchronously
12import_with_redis: Enable Redis to import data asynchronously
12shaarli_url: Shaarli URL, if the service is enabled 13shaarli_url: Shaarli URL, if the service is enabled
13share_diaspora: Enable share to Diaspora 14share_diaspora: Enable share to Diaspora
14share_mail: Enable share by email 15share_mail: Enable share by email
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml
index 7aac9adf..bf3a79af 100644
--- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.es.yml
@@ -8,7 +8,8 @@ export_csv: Activar exportación a CSV
8export_json: Activar exportación a JSON 8export_json: Activar exportación a JSON
9export_txt: Activar exportación a TXT 9export_txt: Activar exportación a TXT
10export_xml: Activar exportación a XML 10export_xml: Activar exportación a XML
11pocket_consumer_key: Consumer key for Pocket to import contents (https://getpocket.com/developer/docs/authentication) 11# import_with_rabbitmq: Enable RabbitMQ to import data asynchronously
12# import_with_redis: Enable Redis to import data asynchronously
12shaarli_url: Shaarli URL, si el servicio está activado 13shaarli_url: Shaarli URL, si el servicio está activado
13share_diaspora: Activar compartir con Diaspora 14share_diaspora: Activar compartir con Diaspora
14share_mail: Activar compartir con email 15share_mail: Activar compartir con email
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml
index 613cf86d..d86c4343 100644
--- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fa.yml
@@ -8,7 +8,8 @@ export_csv: فعال‌سازی برون‌سپاری به CSV
8export_json: فعال‌سازی برون‌سپاری به JSON 8export_json: فعال‌سازی برون‌سپاری به JSON
9export_txt: فعال‌سازی برون‌سپاری به TXT 9export_txt: فعال‌سازی برون‌سپاری به TXT
10export_xml: فعال‌سازی برون‌سپاری به XML 10export_xml: فعال‌سازی برون‌سپاری به XML
11pocket_consumer_key: کلید کاربری Pocket برای درون‌ریزی مطالب (https://getpocket.com/developer/docs/authentication) 11# import_with_rabbitmq: Enable RabbitMQ to import data asynchronously
12# import_with_redis: Enable Redis to import data asynchronously
12shaarli_url: نشانی Shaarli، اگر فعال بود 13shaarli_url: نشانی Shaarli، اگر فعال بود
13share_diaspora: فعال‌سازی هم‌رسانی به Diaspora 14share_diaspora: فعال‌سازی هم‌رسانی به Diaspora
14share_mail: فعال‌سازی هم‌رسانی با ایمیل 15share_mail: فعال‌سازی هم‌رسانی با ایمیل
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml
index f21f2439..5e1ecf26 100644
--- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.fr.yml
@@ -8,7 +8,8 @@ export_csv: Activer l'export CSV
8export_json: Activer l'export JSON 8export_json: Activer l'export JSON
9export_txt: Activer l'export TXT 9export_txt: Activer l'export TXT
10export_xml: Activer l'export XML 10export_xml: Activer l'export XML
11pocket_consumer_key: Clé d'authentification Pocket pour importer les données (https://getpocket.com/developer/docs/authentication) 11import_with_rabbitmq: Activer RabbitMQ pour gérer les imports de façon asynchrone
12import_with_redis: Activer Redis pour gérer les imports de façon asynchrone
12shaarli_url: URL de Shaarli, si le service Shaarli est activé 13shaarli_url: URL de Shaarli, si le service Shaarli est activé
13share_diaspora: Activer le partage vers Diaspora 14share_diaspora: Activer le partage vers Diaspora
14share_mail: Activer le partage par email 15share_mail: Activer le partage par email
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml
index 1202edd5..9d820e4b 100644
--- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.it.yml
@@ -8,7 +8,8 @@ export_csv: Abilita esportazione CSV
8export_json: Abilita esportazione JSON 8export_json: Abilita esportazione JSON
9export_txt: Abilita esportazione TXT 9export_txt: Abilita esportazione TXT
10export_xml: Abilita esportazione XML 10export_xml: Abilita esportazione XML
11pocket_consumer_key: Consumer key per Pocket per importare i contenuti (https://getpocket.com/developer/docs/authentication) 11# import_with_rabbitmq: Enable RabbitMQ to import data asynchronously
12# import_with_redis: Enable Redis to import data asynchronously
12shaarli_url: Shaarli URL, se il servizio è abilitato 13shaarli_url: Shaarli URL, se il servizio è abilitato
13share_diaspora: Abilita la condivisione con Diaspora 14share_diaspora: Abilita la condivisione con Diaspora
14share_mail: Abilita la condivisione per email 15share_mail: Abilita la condivisione per email
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml
index dd91ee49..49dc7732 100644
--- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.oc.yml
@@ -8,7 +8,8 @@ export_csv: Activar l'expòrt CSV
8export_json: Activar l'expòrt JSON 8export_json: Activar l'expòrt JSON
9export_txt: Activar l'expòrt TXT 9export_txt: Activar l'expòrt TXT
10export_xml: Activar l'expòrt XML 10export_xml: Activar l'expòrt XML
11pocket_consumer_key: Clau d'autentificacion Pocket per importar las donadas (https://getpocket.com/developer/docs/authentication) 11# import_with_rabbitmq: Enable RabbitMQ to import data asynchronously
12# import_with_redis: Enable Redis to import data asynchronously
12shaarli_url: URL de Shaarli, se lo servici Shaarli es activat 13shaarli_url: URL de Shaarli, se lo servici Shaarli es activat
13share_diaspora: Activar lo partatge cap a Diaspora 14share_diaspora: Activar lo partatge cap a Diaspora
14share_mail: Activar lo partatge per corrièl 15share_mail: Activar lo partatge per corrièl
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml
index 93b36c8f..2365f2b3 100644
--- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.pl.yml
@@ -8,7 +8,8 @@ export_csv: Włącz eksport do CSV
8export_json: Włącz eksport do JSON 8export_json: Włącz eksport do JSON
9export_txt: Włącz eksport do TXT 9export_txt: Włącz eksport do TXT
10export_xml: Włącz eksport do XML 10export_xml: Włącz eksport do XML
11pocket_consumer_key: Klucz klienta Pocket do importu zawartości (https://getpocket.com/developer/docs/authentication) 11# import_with_rabbitmq: Enable RabbitMQ to import data asynchronously
12# import_with_redis: Enable Redis to import data asynchronously
12shaarli_url: Adress URL Shaarli, jeżeli usługa jest włączona 13shaarli_url: Adress URL Shaarli, jeżeli usługa jest włączona
13share_diaspora: Włącz udostępnianie dla Diaspora 14share_diaspora: Włącz udostępnianie dla Diaspora
14share_mail: Włącz udostępnianie przez email 15share_mail: Włącz udostępnianie przez email
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml
index 326c9f90..20d664f7 100644
--- a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.ro.yml
@@ -8,7 +8,8 @@ export_csv: Permite exportare CSV
8export_json: Permite exportare JSON 8export_json: Permite exportare JSON
9export_txt: Permite exportare TXT 9export_txt: Permite exportare TXT
10export_xml: Permite exportare XML 10export_xml: Permite exportare XML
11pocket_consumer_key: Cheie consumator pentru importarea contentului din Pocket (https://getpocket.com/developer/docs/authentication) 11# import_with_rabbitmq: Enable RabbitMQ to import data asynchronously
12# import_with_redis: Enable Redis to import data asynchronously
12shaarli_url: Shaarli URL, dacă serviciul este permis 13shaarli_url: Shaarli URL, dacă serviciul este permis
13share_diaspora: Permite share către Diaspora 14share_diaspora: Permite share către Diaspora
14share_mail: Permite share prin email 15share_mail: Permite share prin email
diff --git a/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml
new file mode 100644
index 00000000..4b4d3edb
--- /dev/null
+++ b/app/Resources/CraueConfigBundle/translations/CraueConfigBundle.tr.yml
@@ -0,0 +1,31 @@
1# download_pictures: Download pictures on your server
2# carrot: Enable share to Carrot
3# diaspora_url: Diaspora URL, if the service is enabled
4# export_epub: Enable ePub export
5# export_mobi: Enable .mobi export
6# export_pdf: Enable PDF export
7# export_csv: Enable CSV export
8# export_json: Enable JSON export
9# export_txt: Enable TXT export
10# export_xml: Enable XML export
11# import_with_rabbitmq: Enable RabbitMQ to import data asynchronously
12# import_with_redis: Enable Redis to import data asynchronously
13# shaarli_url: Shaarli URL, if the service is enabled
14# share_diaspora: Enable share to Diaspora
15# share_mail: Enable share by email
16# share_shaarli: Enable share to Shaarli
17# share_twitter: Enable share to Twitter
18# show_printlink: Display a link to print content
19# wallabag_support_url: Support URL for wallabag
20# wallabag_url: URL of *your* wallabag instance
21# entry: "article"
22# export: "export"
23# import: "import"
24# misc: "misc"
25# modify_settings: "apply"
26# piwik_host: Host of your website in Piwik
27# piwik_site_id: ID of your website in Piwik
28# piwik_enabled: Enable Piwik
29# demo_mode_enabled: "Enable demo mode ? (only used for the wallabag public demo)"
30# demo_mode_username: "Demo user"
31# share_public: Allow public url for entries
diff --git a/app/config/config.yml b/app/config/config.yml
index 31bd8a8c..4b869c4f 100644
--- a/app/config/config.yml
+++ b/app/config/config.yml
@@ -215,3 +215,67 @@ lexik_maintenance:
215 response: 215 response:
216 code: 503 216 code: 503
217 status: "wallabag Service Temporarily Unavailable" 217 status: "wallabag Service Temporarily Unavailable"
218
219old_sound_rabbit_mq:
220 connections:
221 default:
222 host: "%rabbitmq_host%"
223 port: "%rabbitmq_port%"
224 user: "%rabbitmq_user%"
225 password: "%rabbitmq_password%"
226 vhost: /
227 lazy: true
228 producers:
229 import_pocket:
230 connection: default
231 exchange_options:
232 name: 'wallabag.import.pocket'
233 type: topic
234 import_readability:
235 connection: default
236 exchange_options:
237 name: 'wallabag.import.readability'
238 type: topic
239 import_wallabag_v1:
240 connection: default
241 exchange_options:
242 name: 'wallabag.import.wallabag_v1'
243 type: topic
244 import_wallabag_v2:
245 connection: default
246 exchange_options:
247 name: 'wallabag.import.wallabag_v2'
248 type: topic
249 consumers:
250 import_pocket:
251 connection: default
252 exchange_options:
253 name: 'wallabag.import.pocket'
254 type: topic
255 queue_options:
256 name: 'wallabag.import.pocket'
257 callback: wallabag_import.consumer.amqp.pocket
258 import_readability:
259 connection: default
260 exchange_options:
261 name: 'wallabag.import.readability'
262 type: topic
263 queue_options:
264 name: 'wallabag.import.readability'
265 callback: wallabag_import.consumer.amqp.readability
266 import_wallabag_v1:
267 connection: default
268 exchange_options:
269 name: 'wallabag.import.wallabag_v1'
270 type: topic
271 queue_options:
272 name: 'wallabag.import.wallabag_v1'
273 callback: wallabag_import.consumer.amqp.wallabag_v1
274 import_wallabag_v2:
275 connection: default
276 exchange_options:
277 name: 'wallabag.import.wallabag_v2'
278 type: topic
279 queue_options:
280 name: 'wallabag.import.wallabag_v2'
281 callback: wallabag_import.consumer.amqp.wallabag_v2
diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist
index d092e139..f2e5bec3 100644
--- a/app/config/parameters.yml.dist
+++ b/app/config/parameters.yml.dist
@@ -38,3 +38,15 @@ parameters:
38 fosuser_confirmation: true 38 fosuser_confirmation: true
39 39
40 from_email: no-reply@wallabag.org 40 from_email: no-reply@wallabag.org
41
42 rss_limit: 50
43
44 # RabbitMQ processing
45 rabbitmq_host: localhost
46 rabbitmq_port: 5672
47 rabbitmq_user: guest
48 rabbitmq_password: guest
49
50 # Redis processing
51 redis_host: localhost
52 redis_port: 6379
diff --git a/app/config/parameters_test.yml b/app/config/parameters_test.yml
index 696c95ef..2943b27a 100644
--- a/app/config/parameters_test.yml
+++ b/app/config/parameters_test.yml
@@ -5,4 +5,4 @@ parameters:
5 test_database_name: null 5 test_database_name: null
6 test_database_user: null 6 test_database_user: null
7 test_database_password: null 7 test_database_password: null
8 test_database_path: '%kernel.root_dir%/../data/db/wallabag_testYO.sqlite' 8 test_database_path: '%kernel.root_dir%/../data/db/wallabag_test.sqlite'
diff --git a/composer.json b/composer.json
index bdaad601..8220ed45 100644
--- a/composer.json
+++ b/composer.json
@@ -81,7 +81,10 @@
81 "lexik/maintenance-bundle": "~2.1", 81 "lexik/maintenance-bundle": "~2.1",
82 "ocramius/proxy-manager": "1.*", 82 "ocramius/proxy-manager": "1.*",
83 "white-october/pagerfanta-bundle": "^1.0", 83 "white-october/pagerfanta-bundle": "^1.0",
84 "mouf/nodejs-installer": "~1.0" 84 "mouf/nodejs-installer": "~1.0",
85 "php-amqplib/rabbitmq-bundle": "^1.8",
86 "predis/predis": "^1.0",
87 "javibravo/simpleue": "^1.0"
85 }, 88 },
86 "require-dev": { 89 "require-dev": {
87 "doctrine/doctrine-fixtures-bundle": "~2.2", 90 "doctrine/doctrine-fixtures-bundle": "~2.2",
@@ -89,7 +92,8 @@
89 "sensio/generator-bundle": "^3.0", 92 "sensio/generator-bundle": "^3.0",
90 "phpunit/phpunit": "~5.0", 93 "phpunit/phpunit": "~5.0",
91 "symfony/phpunit-bridge": "^3.0", 94 "symfony/phpunit-bridge": "^3.0",
92 "friendsofphp/php-cs-fixer": "~1.9" 95 "friendsofphp/php-cs-fixer": "~1.9",
96 "m6web/redis-mock": "^2.0"
93 }, 97 },
94 "scripts": { 98 "scripts": {
95 "post-cmd": [ 99 "post-cmd": [
diff --git a/docker-compose.yml b/docker-compose.yml
index 50d8c546..3c28f2f1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,6 +11,7 @@ services:
11 links: 11 links:
12 - php:php 12 - php:php
13 command: nginx -c /nginx.conf 13 command: nginx -c /nginx.conf
14
14 php: 15 php:
15 build: 16 build:
16 context: docker/php 17 context: docker/php
@@ -30,6 +31,7 @@ services:
30 # If all DBMS are commented out, sqlite will be used as default 31 # If all DBMS are commented out, sqlite will be used as default
31 # - ./docker/postgres/env 32 # - ./docker/postgres/env
32 # - ./docker/mariadb/env 33 # - ./docker/mariadb/env
34
33 #postgres: 35 #postgres:
34 # image: postgres:9 36 # image: postgres:9
35 # ports: 37 # ports:
@@ -38,6 +40,7 @@ services:
38 # - ./docker/data/pgsql:/var/lib/postgresql/data 40 # - ./docker/data/pgsql:/var/lib/postgresql/data
39 # env_file: 41 # env_file:
40 # - ./docker/postgres/env 42 # - ./docker/postgres/env
43
41 #mariadb: 44 #mariadb:
42 # image: mariadb:10 45 # image: mariadb:10
43 # ports: 46 # ports:
@@ -46,3 +49,13 @@ services:
46 # - ./docker/data/mariadb:/var/lib/mysql 49 # - ./docker/data/mariadb:/var/lib/mysql
47 # env_file: 50 # env_file:
48 # - ./docker/mariadb/env 51 # - ./docker/mariadb/env
52
53 rabbitmq:
54 image: rabbitmq:3-management
55 ports:
56 - "15672:15672"
57
58 redis:
59 image: redis
60 ports:
61 - "6379:6379"
diff --git a/docs/en/developer/rabbitmq.rst b/docs/en/developer/rabbitmq.rst
new file mode 100644
index 00000000..8cee45fb
--- /dev/null
+++ b/docs/en/developer/rabbitmq.rst
@@ -0,0 +1,67 @@
1Install RabbitMQ for asynchronous tasks
2=======================================
3
4In order to launch asynchronous tasks (useful for huge imports for example), we can use RabbitMQ.
5
6Requirements
7------------
8
9You need to have RabbitMQ installed on your server.
10
11Installation
12~~~~~~~~~~~~
13
14.. code:: bash
15
16 wget https://www.rabbitmq.com/rabbitmq-signing-key-public.asc
17 apt-key add rabbitmq-signing-key-public.asc
18 apt-get update
19 apt-get install rabbitmq-server
20
21Configuration and launch
22~~~~~~~~~~~~~~~~~~~~~~~~
23
24.. code:: bash
25
26 rabbitmq-plugins enable rabbitmq_management # (useful to have a web interface, available at http://localhost:15672/ (guest/guest)
27 rabbitmq-server -detached
28
29Stop RabbitMQ
30~~~~~~~~~~~~~
31
32.. code:: bash
33
34 rabbitmqctl stop
35
36
37Configure RabbitMQ in wallabag
38------------------------------
39
40Edit your ``parameters.yml`` file to edit RabbitMQ configuration. The default one should be ok:
41
42.. code:: yaml
43
44 rabbitmq_host: localhost
45 rabbitmq_port: 5672
46 rabbitmq_user: guest
47 rabbitmq_password: guest
48
49
50Launch RabbitMQ consumer
51------------------------
52
53Depending on which service you want to import from you need to enable one (or many if you want to support many) cron job:
54
55.. code:: bash
56
57 # for Pocket import
58 bin/console rabbitmq:consumer import_pocket -w
59
60 # for Readbility import
61 bin/console rabbitmq:consumer import_readability -w
62
63 # for wallabag v1 import
64 bin/console rabbitmq:consumer import_wallabag_v1 -w
65
66 # for wallabag v2 import
67 bin/console rabbitmq:consumer import_wallabag_v2 -w
diff --git a/docs/en/developer/redis.rst b/docs/en/developer/redis.rst
new file mode 100644
index 00000000..5748e260
--- /dev/null
+++ b/docs/en/developer/redis.rst
@@ -0,0 +1,62 @@
1Install Redis for asynchronous tasks
2=======================================
3
4In order to launch asynchronous tasks (useful for huge imports for example), we can use Redis.
5
6Requirements
7------------
8
9You need to have Redis installed on your server.
10
11Installation
12~~~~~~~~~~~~
13
14.. code:: bash
15
16 apt-get install redis-server
17
18Launch
19~~~~~~
20
21The server might be already running after installing, if not you can launch it using:
22
23.. code:: bash
24
25 redis-server
26
27
28Configure Redis in wallabag
29---------------------------
30
31Edit your ``parameters.yml`` file to edit Redis configuration. The default one should be ok:
32
33.. code:: yaml
34
35 redis_host: localhost
36 redis_port: 6379
37
38
39Launch Redis consumer
40------------------------
41
42Depending on which service you want to import from you need to enable one (or many if you want to support many) cron job:
43
44.. code:: bash
45
46 # for Pocket import
47 bin/console wallabag:import:redis-worker pocket -vv >> /path/to/wallabag/var/logs/redis-pocket.log
48
49 # for Readbility import
50 bin/console wallabag:import:redis-worker readability -vv >> /path/to/wallabag/var/logs/redis-readability.log
51
52 # for wallabag v1 import
53 bin/console wallabag:import:redis-worker wallabag_v1 -vv >> /path/to/wallabag/var/logs/redis-wallabag_v1.log
54
55 # for wallabag v2 import
56 bin/console wallabag:import:redis-worker wallabag_v2 -vv >> /path/to/wallabag/var/logs/redis-wallabag_v2.log
57
58If you want to launch the import only for some messages and not all, you can specify this number (here 12) and the worker will stop right after the 12th message :
59
60.. code:: bash
61
62 bin/console wallabag:import:redis-worker pocket -vv --maxIterations=12
diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php
index 143def4f..bd7b55f9 100644
--- a/src/Wallabag/CoreBundle/Command/InstallCommand.php
+++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php
@@ -317,8 +317,13 @@ class InstallCommand extends ContainerAwareCommand
317 'section' => 'export', 317 'section' => 'export',
318 ], 318 ],
319 [ 319 [
320 'name' => 'pocket_consumer_key', 320 'name' => 'import_with_redis',
321 'value' => null, 321 'value' => '0',
322 'section' => 'import',
323 ],
324 [
325 'name' => 'import_with_rabbitmq',
326 'value' => '0',
322 'section' => 'import', 327 'section' => 'import',
323 ], 328 ],
324 [ 329 [
diff --git a/src/Wallabag/CoreBundle/Controller/EntryController.php b/src/Wallabag/CoreBundle/Controller/EntryController.php
index 624576b5..40111af0 100644
--- a/src/Wallabag/CoreBundle/Controller/EntryController.php
+++ b/src/Wallabag/CoreBundle/Controller/EntryController.php
@@ -17,26 +17,35 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
17class EntryController extends Controller 17class EntryController extends Controller
18{ 18{
19 /** 19 /**
20 * @param Entry $entry 20 * Fetch content and update entry.
21 * In case it fails, entry will return to avod loosing the data.
22 *
23 * @param Entry $entry
24 * @param string $prefixMessage Should be the translation key: entry_saved or entry_reloaded
25 *
26 * @return Entry
21 */ 27 */
22 private function updateEntry(Entry $entry) 28 private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved')
23 { 29 {
30 // put default title in case of fetching content failed
31 $entry->setTitle('No title found');
32
33 $message = 'flashes.entry.notice.'.$prefixMessage;
34
24 try { 35 try {
25 $entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl()); 36 $entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
26
27 $em = $this->getDoctrine()->getManager();
28 $em->persist($entry);
29 $em->flush();
30 } catch (\Exception $e) { 37 } catch (\Exception $e) {
31 $this->get('logger')->error('Error while saving an entry', [ 38 $this->get('logger')->error('Error while saving an entry', [
32 'exception' => $e, 39 'exception' => $e,
33 'entry' => $entry, 40 'entry' => $entry,
34 ]); 41 ]);
35 42
36 return false; 43 $message = 'flashes.entry.notice.'.$prefixMessage.'_failed';
37 } 44 }
38 45
39 return true; 46 $this->get('session')->getFlashBag()->add('notice', $message);
47
48 return $entry;
40 } 49 }
41 50
42 /** 51 /**
@@ -66,12 +75,11 @@ class EntryController extends Controller
66 return $this->redirect($this->generateUrl('view', ['id' => $existingEntry->getId()])); 75 return $this->redirect($this->generateUrl('view', ['id' => $existingEntry->getId()]));
67 } 76 }
68 77
69 $message = 'flashes.entry.notice.entry_saved'; 78 $this->updateEntry($entry);
70 if (false === $this->updateEntry($entry)) {
71 $message = 'flashes.entry.notice.entry_saved_failed';
72 }
73 79
74 $this->get('session')->getFlashBag()->add('notice', $message); 80 $em = $this->getDoctrine()->getManager();
81 $em->persist($entry);
82 $em->flush();
75 83
76 return $this->redirect($this->generateUrl('homepage')); 84 return $this->redirect($this->generateUrl('homepage'));
77 } 85 }
@@ -95,6 +103,10 @@ class EntryController extends Controller
95 103
96 if (false === $this->checkIfEntryAlreadyExists($entry)) { 104 if (false === $this->checkIfEntryAlreadyExists($entry)) {
97 $this->updateEntry($entry); 105 $this->updateEntry($entry);
106
107 $em = $this->getDoctrine()->getManager();
108 $em->persist($entry);
109 $em->flush();
98 } 110 }
99 111
100 return $this->redirect($this->generateUrl('homepage')); 112 return $this->redirect($this->generateUrl('homepage'));
@@ -316,15 +328,11 @@ class EntryController extends Controller
316 { 328 {
317 $this->checkUserAction($entry); 329 $this->checkUserAction($entry);
318 330
319 $message = 'flashes.entry.notice.entry_reloaded'; 331 $this->updateEntry($entry, 'entry_reloaded');
320 if (false === $this->updateEntry($entry)) {
321 $message = 'flashes.entry.notice.entry_reload_failed';
322 }
323 332
324 $this->get('session')->getFlashBag()->add( 333 $em = $this->getDoctrine()->getManager();
325 'notice', 334 $em->persist($entry);
326 $message 335 $em->flush();
327 );
328 336
329 return $this->redirect($this->generateUrl('view', ['id' => $entry->getId()])); 337 return $this->redirect($this->generateUrl('view', ['id' => $entry->getId()]));
330 } 338 }
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php
index 03be9667..921c739f 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php
@@ -20,6 +20,7 @@ class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
20 $adminConfig->setItemsPerPage(30); 20 $adminConfig->setItemsPerPage(30);
21 $adminConfig->setReadingSpeed(1); 21 $adminConfig->setReadingSpeed(1);
22 $adminConfig->setLanguage('en'); 22 $adminConfig->setLanguage('en');
23 $adminConfig->setPocketConsumerKey('xxxxx');
23 24
24 $manager->persist($adminConfig); 25 $manager->persist($adminConfig);
25 26
@@ -30,6 +31,7 @@ class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
30 $bobConfig->setItemsPerPage(10); 31 $bobConfig->setItemsPerPage(10);
31 $bobConfig->setReadingSpeed(1); 32 $bobConfig->setReadingSpeed(1);
32 $bobConfig->setLanguage('fr'); 33 $bobConfig->setLanguage('fr');
34 $bobConfig->setPocketConsumerKey(null);
33 35
34 $manager->persist($bobConfig); 36 $manager->persist($bobConfig);
35 37
@@ -40,6 +42,7 @@ class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
40 $emptyConfig->setItemsPerPage(10); 42 $emptyConfig->setItemsPerPage(10);
41 $emptyConfig->setReadingSpeed(1); 43 $emptyConfig->setReadingSpeed(1);
42 $emptyConfig->setLanguage('en'); 44 $emptyConfig->setLanguage('en');
45 $emptyConfig->setPocketConsumerKey(null);
43 46
44 $manager->persist($emptyConfig); 47 $manager->persist($emptyConfig);
45 48
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php
index b4309304..9425f961 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php
@@ -91,8 +91,13 @@ class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface
91 'section' => 'export', 91 'section' => 'export',
92 ], 92 ],
93 [ 93 [
94 'name' => 'pocket_consumer_key', 94 'name' => 'import_with_redis',
95 'value' => null, 95 'value' => '0',
96 'section' => 'import',
97 ],
98 [
99 'name' => 'import_with_rabbitmq',
100 'value' => '0',
96 'section' => 'import', 101 'section' => 'import',
97 ], 102 ],
98 [ 103 [
diff --git a/src/Wallabag/CoreBundle/Entity/Config.php b/src/Wallabag/CoreBundle/Entity/Config.php
index a25656d3..d0f0e3f3 100644
--- a/src/Wallabag/CoreBundle/Entity/Config.php
+++ b/src/Wallabag/CoreBundle/Entity/Config.php
@@ -81,6 +81,13 @@ class Config
81 private $readingSpeed; 81 private $readingSpeed;
82 82
83 /** 83 /**
84 * @var string
85 *
86 * @ORM\Column(name="pocket_consumer_key", type="string", nullable=true)
87 */
88 private $pocketConsumerKey;
89
90 /**
84 * @ORM\OneToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="config") 91 * @ORM\OneToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="config")
85 */ 92 */
86 private $user; 93 private $user;
@@ -279,6 +286,30 @@ class Config
279 } 286 }
280 287
281 /** 288 /**
289 * Set pocketConsumerKey.
290 *
291 * @param string $pocketConsumerKey
292 *
293 * @return Config
294 */
295 public function setPocketConsumerKey($pocketConsumerKey)
296 {
297 $this->pocketConsumerKey = $pocketConsumerKey;
298
299 return $this;
300 }
301
302 /**
303 * Get pocketConsumerKey.
304 *
305 * @return string
306 */
307 public function getPocketConsumerKey()
308 {
309 return $this->pocketConsumerKey;
310 }
311
312 /**
282 * @param TaggingRule $rule 313 * @param TaggingRule $rule
283 * 314 *
284 * @return Config 315 * @return Config
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php
index c3e6b4d5..a4b0d7a8 100644
--- a/src/Wallabag/CoreBundle/Entity/Entry.php
+++ b/src/Wallabag/CoreBundle/Entity/Entry.php
@@ -97,7 +97,7 @@ class Entry
97 private $content; 97 private $content;
98 98
99 /** 99 /**
100 * @var date 100 * @var \DateTime
101 * 101 *
102 * @ORM\Column(name="created_at", type="datetime") 102 * @ORM\Column(name="created_at", type="datetime")
103 * 103 *
@@ -106,7 +106,7 @@ class Entry
106 private $createdAt; 106 private $createdAt;
107 107
108 /** 108 /**
109 * @var date 109 * @var \DateTime
110 * 110 *
111 * @ORM\Column(name="updated_at", type="datetime") 111 * @ORM\Column(name="updated_at", type="datetime")
112 * 112 *
@@ -410,7 +410,22 @@ class Entry
410 } 410 }
411 411
412 /** 412 /**
413 * @return string 413 * Set created_at.
414 * Only used when importing data from an other service.
415 *
416 * @param \DateTime $createdAt
417 *
418 * @return Entry
419 */
420 public function setCreatedAt(\DateTime $createdAt)
421 {
422 $this->createdAt = $createdAt;
423
424 return $this;
425 }
426
427 /**
428 * @return \DateTime
414 */ 429 */
415 public function getCreatedAt() 430 public function getCreatedAt()
416 { 431 {
@@ -418,7 +433,7 @@ class Entry
418 } 433 }
419 434
420 /** 435 /**
421 * @return string 436 * @return \DateTime
422 */ 437 */
423 public function getUpdatedAt() 438 public function getUpdatedAt()
424 { 439 {
diff --git a/src/Wallabag/CoreBundle/Form/Type/ConfigType.php b/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
index 7d25cc80..0bac2874 100644
--- a/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
@@ -52,6 +52,9 @@ class ConfigType extends AbstractType
52 'choices' => array_flip($this->languages), 52 'choices' => array_flip($this->languages),
53 'label' => 'config.form_settings.language_label', 53 'label' => 'config.form_settings.language_label',
54 ]) 54 ])
55 ->add('pocket_consumer_key', null, [
56 'label' => 'config.form_settings.pocket_consumer_key_label',
57 ])
55 ->add('save', SubmitType::class, [ 58 ->add('save', SubmitType::class, [
56 'label' => 'config.form.save', 59 'label' => 'config.form.save',
57 ]) 60 ])
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml
index e95ef452..23e6d3ca 100644
--- a/src/Wallabag/CoreBundle/Resources/config/services.yml
+++ b/src/Wallabag/CoreBundle/Resources/config/services.yml
@@ -125,3 +125,11 @@ services:
125 arguments: 125 arguments:
126 - "@security.token_storage" 126 - "@security.token_storage"
127 - "@router" 127 - "@router"
128
129 wallabag_core.redis.client:
130 class: Predis\Client
131 arguments:
132 -
133 host: '%redis_host%'
134 port: '%redis_port%'
135 schema: tcp
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
index 073dee28..9f051edb 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
@@ -68,6 +68,7 @@ config:
68 # 200_word: 'I read ~200 words per minute' 68 # 200_word: 'I read ~200 words per minute'
69 # 300_word: 'I read ~300 words per minute' 69 # 300_word: 'I read ~300 words per minute'
70 # 400_word: 'I read ~400 words per minute' 70 # 400_word: 'I read ~400 words per minute'
71 pocket_consumer_key_label: Brugers nøgle til Pocket for at importere materialer
71 form_rss: 72 form_rss:
72 description: 'RSS-feeds fra wallabag gør det muligt at læse de artikler, der gemmes i wallabag, med din RSS-læser. Det kræver, at du genererer et token først.' 73 description: 'RSS-feeds fra wallabag gør det muligt at læse de artikler, der gemmes i wallabag, med din RSS-læser. Det kræver, at du genererer et token først.'
73 token_label: 'RSS-Token' 74 token_label: 'RSS-Token'
@@ -346,6 +347,8 @@ import:
346 # page_title: 'Import > Readability' 347 # page_title: 'Import > Readability'
347 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 348 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
348 # how_to: 'Please select your Readability export and click on the below button to upload and import it.' 349 # how_to: 'Please select your Readability export and click on the below button to upload and import it.'
350 worker:
351 # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
349 352
350developer: 353developer:
351 # page_title: 'Developer' 354 # page_title: 'Developer'
@@ -411,10 +414,10 @@ flashes:
411 notice: 414 notice:
412 # entry_already_saved: 'Entry already saved on %date%' 415 # entry_already_saved: 'Entry already saved on %date%'
413 # entry_saved: 'Entry saved' 416 # entry_saved: 'Entry saved'
414 # entry_saved_failed: 'Failed to save entry' 417 # entry_saved_failed: 'Entry saved but fetching content failed'
415 # entry_updated: 'Entry updated' 418 # entry_updated: 'Entry updated'
416 # entry_reloaded: 'Entry reloaded' 419 # entry_reloaded: 'Entry reloaded'
417 # entry_reload_failed: 'Failed to reload entry' 420 # entry_reload_failed: 'Entry reloaded but fetching content failed'
418 entry_archived: 'Artikel arkiveret' 421 entry_archived: 'Artikel arkiveret'
419 entry_unarchived: 'Artikel ikke længere arkiveret' 422 entry_unarchived: 'Artikel ikke længere arkiveret'
420 entry_starred: 'Artikel markeret som favorit' 423 entry_starred: 'Artikel markeret som favorit'
@@ -428,6 +431,7 @@ flashes:
428 # failed: 'Import failed, please try again.' 431 # failed: 'Import failed, please try again.'
429 # failed_on_file: 'Error while processing import. Please verify your import file.' 432 # failed_on_file: 'Error while processing import. Please verify your import file.'
430 # summary: 'Import summary: %imported% imported, %skipped% already saved.' 433 # summary: 'Import summary: %imported% imported, %skipped% already saved.'
434 # summary_with_queue: 'Import summary: %queued% queued.'
431 developer: 435 developer:
432 notice: 436 notice:
433 # client_created: 'New client created.' 437 # client_created: 'New client created.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
index 4cfd240f..cbfacd55 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
@@ -68,6 +68,7 @@ config:
68 200_word: 'Ich lese ~200 Wörter pro Minute' 68 200_word: 'Ich lese ~200 Wörter pro Minute'
69 300_word: 'Ich lese ~300 Wörter pro Minute' 69 300_word: 'Ich lese ~300 Wörter pro Minute'
70 400_word: 'Ich lese ~400 Wörter pro Minute' 70 400_word: 'Ich lese ~400 Wörter pro Minute'
71 pocket_consumer_key_label: Consumer-Key für Pocket, um Inhalte zu importieren
71 form_rss: 72 form_rss:
72 description: 'Die RSS-Feeds von wallabag erlauben es dir, deine gespeicherten Artikel mit deinem bevorzugten RSS-Reader zu lesen. Vorher musst du jedoch einen Token erstellen.' 73 description: 'Die RSS-Feeds von wallabag erlauben es dir, deine gespeicherten Artikel mit deinem bevorzugten RSS-Reader zu lesen. Vorher musst du jedoch einen Token erstellen.'
73 token_label: 'RSS-token' 74 token_label: 'RSS-token'
@@ -346,6 +347,8 @@ import:
346 page_title: 'Aus Readability importieren' 347 page_title: 'Aus Readability importieren'
347 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 348 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
348 # how_to: 'Please select your Readability export and click on the below button to upload and import it.' 349 # how_to: 'Please select your Readability export and click on the below button to upload and import it.'
350 worker:
351 # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
349 352
350developer: 353developer:
351 page_title: 'Entwickler' 354 page_title: 'Entwickler'
@@ -411,10 +414,10 @@ flashes:
411 notice: 414 notice:
412 entry_already_saved: 'Eintrag bereits am %date% gespeichert' 415 entry_already_saved: 'Eintrag bereits am %date% gespeichert'
413 entry_saved: 'Eintrag gespeichert' 416 entry_saved: 'Eintrag gespeichert'
414 # entry_saved_failed: 'Failed to save entry' 417 # entry_saved_failed: 'Entry saved but fetching content failed'
415 entry_updated: 'Eintrag aktualisiert' 418 entry_updated: 'Eintrag aktualisiert'
416 entry_reloaded: 'Eintrag neugeladen' 419 entry_reloaded: 'Eintrag neugeladen'
417 entry_reload_failed: 'Neuladen des Eintrags fehlgeschlagen' 420 # entry_reload_failed: 'Entry reloaded but fetching content failed'
418 entry_archived: 'Artikel archiviert' 421 entry_archived: 'Artikel archiviert'
419 entry_unarchived: 'Artikel dearchiviert' 422 entry_unarchived: 'Artikel dearchiviert'
420 entry_starred: 'Artikel favorisiert' 423 entry_starred: 'Artikel favorisiert'
@@ -428,6 +431,7 @@ flashes:
428 failed: 'Import fehlgeschlagen, bitte erneut probieren.' 431 failed: 'Import fehlgeschlagen, bitte erneut probieren.'
429 failed_on_file: 'Fehler während des Imports. Bitte überprüfe deine Import-Datei.' 432 failed_on_file: 'Fehler während des Imports. Bitte überprüfe deine Import-Datei.'
430 summary: 'Import-Zusammenfassung: %imported% importiert, %skipped% bereits gespeichert.' 433 summary: 'Import-Zusammenfassung: %imported% importiert, %skipped% bereits gespeichert.'
434 # summary_with_queue: 'Import summary: %queued% queued.'
431 developer: 435 developer:
432 notice: 436 notice:
433 client_created: 'Neuer Client erstellt.' 437 client_created: 'Neuer Client erstellt.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
index 42374b40..21e2405c 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
@@ -68,6 +68,7 @@ config:
68 200_word: 'I read ~200 words per minute' 68 200_word: 'I read ~200 words per minute'
69 300_word: 'I read ~300 words per minute' 69 300_word: 'I read ~300 words per minute'
70 400_word: 'I read ~400 words per minute' 70 400_word: 'I read ~400 words per minute'
71 pocket_consumer_key_label: Consumer key for Pocket to import contents
71 form_rss: 72 form_rss:
72 description: 'RSS feeds provided by wallabag allow you to read your saved articles with your favourite RSS reader. You need to generate a token first.' 73 description: 'RSS feeds provided by wallabag allow you to read your saved articles with your favourite RSS reader. You need to generate a token first.'
73 token_label: 'RSS token' 74 token_label: 'RSS token'
@@ -346,6 +347,8 @@ import:
346 page_title: 'Import > Readability' 347 page_title: 'Import > Readability'
347 description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 348 description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
348 how_to: 'Please select your Readability export and click on the below button to upload and import it.' 349 how_to: 'Please select your Readability export and click on the below button to upload and import it.'
350 worker:
351 enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
349 352
350developer: 353developer:
351 page_title: 'Developer' 354 page_title: 'Developer'
@@ -413,10 +416,10 @@ flashes:
413 notice: 416 notice:
414 entry_already_saved: 'Entry already saved on %date%' 417 entry_already_saved: 'Entry already saved on %date%'
415 entry_saved: 'Entry saved' 418 entry_saved: 'Entry saved'
416 entry_saved_failed: 'Failed to save entry' 419 entry_saved_failed: 'Entry saved but fetching content failed'
417 entry_updated: 'Entry updated' 420 entry_updated: 'Entry updated'
418 entry_reloaded: 'Entry reloaded' 421 entry_reloaded: 'Entry reloaded'
419 entry_reload_failed: 'Failed to reload entry' 422 entry_reload_failed: 'Entry reloaded but fetching content failed'
420 entry_archived: 'Entry archived' 423 entry_archived: 'Entry archived'
421 entry_unarchived: 'Entry unarchived' 424 entry_unarchived: 'Entry unarchived'
422 entry_starred: 'Entry starred' 425 entry_starred: 'Entry starred'
@@ -430,6 +433,7 @@ flashes:
430 failed: 'Import failed, please try again.' 433 failed: 'Import failed, please try again.'
431 failed_on_file: 'Error while processing import. Please verify your import file.' 434 failed_on_file: 'Error while processing import. Please verify your import file.'
432 summary: 'Import summary: %imported% imported, %skipped% already saved.' 435 summary: 'Import summary: %imported% imported, %skipped% already saved.'
436 summary_with_queue: 'Import summary: %queued% queued.'
433 developer: 437 developer:
434 notice: 438 notice:
435 client_created: 'New client %name% created.' 439 client_created: 'New client %name% created.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
index ee84cc62..43f376d4 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
@@ -68,6 +68,7 @@ config:
68 200_word: 'Leo ~200 palabras por minuto' 68 200_word: 'Leo ~200 palabras por minuto'
69 300_word: 'Leo ~300 palabras por minuto' 69 300_word: 'Leo ~300 palabras por minuto'
70 400_word: 'Leo ~400 palabras por minuto' 70 400_word: 'Leo ~400 palabras por minuto'
71 # pocket_consumer_key_label: Consumer key for Pocket to import contents
71 form_rss: 72 form_rss:
72 description: 'Los feeds RSS de wallabag permiten leer los artículos guardados con su lector RSS favorito. Necesita generar un token primero' 73 description: 'Los feeds RSS de wallabag permiten leer los artículos guardados con su lector RSS favorito. Necesita generar un token primero'
73 token_label: 'RSS token' 74 token_label: 'RSS token'
@@ -346,6 +347,8 @@ import:
346 page_title: 'Importar > Readability' 347 page_title: 'Importar > Readability'
347 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 348 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
348 # how_to: 'Please select your Readability export and click on the below button to upload and import it.' 349 # how_to: 'Please select your Readability export and click on the below button to upload and import it.'
350 worker:
351 # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
349 352
350developer: 353developer:
351 page_title: 'Promotor' 354 page_title: 'Promotor'
@@ -411,10 +414,10 @@ flashes:
411 notice: 414 notice:
412 entry_already_saved: 'Entrada ya guardada por %fecha%' 415 entry_already_saved: 'Entrada ya guardada por %fecha%'
413 entry_saved: 'Entrada guardada' 416 entry_saved: 'Entrada guardada'
414 # entry_saved_failed: 'Failed to save entry' 417 # entry_saved_failed: 'Entry saved but fetching content failed'
415 entry_updated: 'Entrada actualizada' 418 entry_updated: 'Entrada actualizada'
416 entry_reloaded: 'Entrada recargada' 419 entry_reloaded: 'Entrada recargada'
417 entry_reload_failed: 'Entrada recargada reprobada' 420 # entry_reload_failed: 'Entry reloaded but fetching content failed'
418 entry_archived: 'Artículo archivado' 421 entry_archived: 'Artículo archivado'
419 entry_unarchived: 'Artículo desarchivado' 422 entry_unarchived: 'Artículo desarchivado'
420 entry_starred: 'Artículo guardado en los favoritos' 423 entry_starred: 'Artículo guardado en los favoritos'
@@ -425,9 +428,10 @@ flashes:
425 tag_added: 'Etiqueta añadida' 428 tag_added: 'Etiqueta añadida'
426 import: 429 import:
427 notice: 430 notice:
428 failed: 'Importación reprobada, por favor inténtelo de nuevo.' 431 failed: 'Importación reprobada, por favor inténtelo de nuevo.'
429 failed_on_file: 'Se ocurre un error por procesar importación. Por favor verifique su archivo importado.' 432 failed_on_file: 'Se ocurre un error por procesar importación. Por favor verifique su archivo importado.'
430 summary: 'Resúmen importado: %importado% importado, %saltados% ya guardado.' 433 summary: 'Resúmen importado: %importado% importado, %saltados% ya guardado.'
434 # summary_with_queue: 'Import summary: %queued% queued.'
431 developer: 435 developer:
432 notice: 436 notice:
433 client_created: 'Nuevo cliente creado.' 437 client_created: 'Nuevo cliente creado.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
index e9af1e8d..56418ef9 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
@@ -68,6 +68,7 @@ config:
68 200_word: 'من تقریباً ۲۰۰ واژه را در دقیقه می‌خوانم' 68 200_word: 'من تقریباً ۲۰۰ واژه را در دقیقه می‌خوانم'
69 300_word: 'من تقریباً ۳۰۰ واژه را در دقیقه می‌خوانم' 69 300_word: 'من تقریباً ۳۰۰ واژه را در دقیقه می‌خوانم'
70 400_word: 'من تقریباً ۴۰۰ واژه را در دقیقه می‌خوانم' 70 400_word: 'من تقریباً ۴۰۰ واژه را در دقیقه می‌خوانم'
71 pocket_consumer_key_label: کلید کاربری Pocket برای درون‌ریزی مطالب
71 form_rss: 72 form_rss:
72 description: 'با خوراک آر-اس-اس که wallabag در اختیارتان می‌گذارد، می‌توانید مقاله‌های ذخیره‌شده را در نرم‌افزار آر-اس-اس دلخواه خود بخوانید. برای این کار نخست باید یک کد بسازید.' 73 description: 'با خوراک آر-اس-اس که wallabag در اختیارتان می‌گذارد، می‌توانید مقاله‌های ذخیره‌شده را در نرم‌افزار آر-اس-اس دلخواه خود بخوانید. برای این کار نخست باید یک کد بسازید.'
73 token_label: 'کد آر-اس-اس' 74 token_label: 'کد آر-اس-اس'
@@ -344,8 +345,10 @@ import:
344 description: 'این برنامه همهٔ داده‌های شما را در نسخهٔ ۲ wallabag درون‌ریزی می‌کند. به بخش «همهٔ مقاله‌ها» بروید و در بخش «برون‌ریزی» روی "JSON" کلیک کنید. با این کار شما پرونده‌ای به شکل "All articles.json" دریافت خواهید کرد.' 345 description: 'این برنامه همهٔ داده‌های شما را در نسخهٔ ۲ wallabag درون‌ریزی می‌کند. به بخش «همهٔ مقاله‌ها» بروید و در بخش «برون‌ریزی» روی "JSON" کلیک کنید. با این کار شما پرونده‌ای به شکل "All articles.json" دریافت خواهید کرد.'
345 readability: 346 readability:
346 page_title: 'درون‌ریزی > Readability' 347 page_title: 'درون‌ریزی > Readability'
347 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 348 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
348 # how_to: 'Please select your Readability export and click on the below button to upload and import it.' 349 # how_to: 'Please select your Readability export and click on the below button to upload and import it.'
350 worker:
351 # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
349 352
350developer: 353developer:
351 # page_title: 'Developer' 354 # page_title: 'Developer'
@@ -411,10 +414,10 @@ flashes:
411 notice: 414 notice:
412 entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود' 415 entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود'
413 entry_saved: 'مقاله ذخیره شد' 416 entry_saved: 'مقاله ذخیره شد'
414 # entry_saved_failed: 'Failed to save entry' 417 # entry_saved_failed: 'Entry saved but fetching content failed'
415 entry_updated: 'مقاله به‌روز شد' 418 entry_updated: 'مقاله به‌روز شد'
416 entry_reloaded: 'مقاله به‌روز شد' 419 entry_reloaded: 'مقاله به‌روز شد'
417 entry_reload_failed: 'به‌روزرسانی مقاله شکست خورد' 420 # entry_reload_failed: 'Entry reloaded but fetching content failed'
418 entry_archived: 'مقاله بایگانی شد' 421 entry_archived: 'مقاله بایگانی شد'
419 entry_unarchived: 'مقاله از بایگانی درآمد' 422 entry_unarchived: 'مقاله از بایگانی درآمد'
420 entry_starred: 'مقاله برگزیده شد' 423 entry_starred: 'مقاله برگزیده شد'
@@ -428,6 +431,7 @@ flashes:
428 failed: 'درون‌ریزی شکست خورد. لطفاً دوباره تلاش کنید.' 431 failed: 'درون‌ریزی شکست خورد. لطفاً دوباره تلاش کنید.'
429 failed_on_file: 'خطا هنگام پردازش پروندهٔ ورودی. آیا پروندهٔ درون‌ریزی شده سالم است؟' 432 failed_on_file: 'خطا هنگام پردازش پروندهٔ ورودی. آیا پروندهٔ درون‌ریزی شده سالم است؟'
430 summary: 'گزارش درون‌ریزی: %imported% وارد شد, %skipped% از قبل ذخیره شده بود.' 433 summary: 'گزارش درون‌ریزی: %imported% وارد شد, %skipped% از قبل ذخیره شده بود.'
434 # summary_with_queue: 'Import summary: %queued% queued.'
431 developer: 435 developer:
432 notice: 436 notice:
433 # client_created: 'New client created.' 437 # client_created: 'New client created.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
index 402cdf4a..bde21866 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
@@ -68,6 +68,7 @@ config:
68 200_word: 'Je lis environ 200 mots par minute' 68 200_word: 'Je lis environ 200 mots par minute'
69 300_word: 'Je lis environ 300 mots par minute' 69 300_word: 'Je lis environ 300 mots par minute'
70 400_word: 'Je lis environ 400 mots par minute' 70 400_word: 'Je lis environ 400 mots par minute'
71 pocket_consumer_key_label: Clé d'authentification Pocket pour importer les données
71 form_rss: 72 form_rss:
72 description: "Les flux RSS fournis par wallabag vous permettent de lire vos articles sauvegardés dans votre lecteur de flux préféré. Pour pouvoir les utiliser, vous devez d'abord créer un jeton." 73 description: "Les flux RSS fournis par wallabag vous permettent de lire vos articles sauvegardés dans votre lecteur de flux préféré. Pour pouvoir les utiliser, vous devez d'abord créer un jeton."
73 token_label: 'Jeton RSS' 74 token_label: 'Jeton RSS'
@@ -346,6 +347,8 @@ import:
346 page_title: 'Importer > Readability' 347 page_title: 'Importer > Readability'
347 description: 'Cet outil va importer toutes vos données de Readability. Sur la page des outils (https://www.readability.com/tools/), cliquez sur "Export your data" dans la section "Data Export". Vous allez recevoir un email avec un lien pour télécharger le json.' 348 description: 'Cet outil va importer toutes vos données de Readability. Sur la page des outils (https://www.readability.com/tools/), cliquez sur "Export your data" dans la section "Data Export". Vous allez recevoir un email avec un lien pour télécharger le json.'
348 how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l'importer." 349 how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l'importer."
350 worker:
351 enabled: "Les imports sont asynchrones. Une fois l'import commencé un worker externe traitera les messages un par un. Le service activé est :"
349 352
350developer: 353developer:
351 page_title: 'Développeur' 354 page_title: 'Développeur'
@@ -413,10 +416,10 @@ flashes:
413 notice: 416 notice:
414 entry_already_saved: 'Article déjà sauvergardé le %date%' 417 entry_already_saved: 'Article déjà sauvergardé le %date%'
415 entry_saved: 'Article enregistré' 418 entry_saved: 'Article enregistré'
416 entry_saved_failed: "L'enregistrement a écho" 419 entry_saved_failed: 'Article enregistré mais impossible de récupérer le contenu'
417 entry_updated: 'Article mis à jour' 420 entry_updated: 'Article mis à jour'
418 entry_reloaded: 'Article rechargé' 421 entry_reloaded: 'Article rechargé'
419 entry_reload_failed: "Le rechargement de l'article a échoué" 422 entry_reload_failed: "Article mis à jour mais impossible de récupérer le contenu"
420 entry_archived: 'Article marqué comme lu' 423 entry_archived: 'Article marqué comme lu'
421 entry_unarchived: 'Article marqué comme non lu' 424 entry_unarchived: 'Article marqué comme non lu'
422 entry_starred: 'Article ajouté dans les favoris' 425 entry_starred: 'Article ajouté dans les favoris'
@@ -430,6 +433,7 @@ flashes:
430 failed: "L'import a échoué, veuillez ré-essayer" 433 failed: "L'import a échoué, veuillez ré-essayer"
431 failed_on_file: "Erreur lors du traitement de l'import. Vérifier votre fichier." 434 failed_on_file: "Erreur lors du traitement de l'import. Vérifier votre fichier."
432 summary: "Rapport d'import: %imported% importés, %skipped% déjà présent." 435 summary: "Rapport d'import: %imported% importés, %skipped% déjà présent."
436 summary_with_queue: "Rapport d'import: %queued% en cours de traitement."
433 developer: 437 developer:
434 notice: 438 notice:
435 client_created: 'Nouveau client %name% créé' 439 client_created: 'Nouveau client %name% créé'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
index 3aee4816..26bb31ba 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
@@ -68,6 +68,7 @@ config:
68 200_word: 'Leggo ~200 parole al minuto' 68 200_word: 'Leggo ~200 parole al minuto'
69 300_word: 'Leggo ~300 parole al minuto' 69 300_word: 'Leggo ~300 parole al minuto'
70 400_word: 'Leggo ~400 parole al minuto' 70 400_word: 'Leggo ~400 parole al minuto'
71 pocket_consumer_key_label: Consumer key per Pocket per importare i contenuti
71 form_rss: 72 form_rss:
72 description: 'I feed RSS generati da wallabag ti permettono di leggere i tuoi contenuti salvati con il tuo lettore di RSS preferito. Prima, devi generare un token.' 73 description: 'I feed RSS generati da wallabag ti permettono di leggere i tuoi contenuti salvati con il tuo lettore di RSS preferito. Prima, devi generare un token.'
73 token_label: 'RSS token' 74 token_label: 'RSS token'
@@ -343,8 +344,10 @@ import:
343 description: 'Questo importatore copierà tutti i tuoi dati da un wallabag v2. Vai in "Tutti i contenuti", e, nella sidebar di esportazione, clicca su "JSON". Otterrai un file "Tutti i contenuti.json".' 344 description: 'Questo importatore copierà tutti i tuoi dati da un wallabag v2. Vai in "Tutti i contenuti", e, nella sidebar di esportazione, clicca su "JSON". Otterrai un file "Tutti i contenuti.json".'
344 readability: 345 readability:
345 page_title: 'Importa da > Readability' 346 page_title: 'Importa da > Readability'
346 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 347 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
347 # how_to: 'Please select your Readability export and click on the below button to upload and import it.' 348 # how_to: 'Please select your Readability export and click on the below button to upload and import it.'
349 worker:
350 # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
348 351
349developer: 352developer:
350 page_title: 'Sviluppatori' 353 page_title: 'Sviluppatori'
@@ -410,10 +413,10 @@ flashes:
410 notice: 413 notice:
411 entry_already_saved: 'Contenuto già salvato in data %date%' 414 entry_already_saved: 'Contenuto già salvato in data %date%'
412 entry_saved: 'Contenuto salvato' 415 entry_saved: 'Contenuto salvato'
413 # entry_saved_failed: 'Failed to save entry' 416 # entry_saved_failed: 'Entry saved but fetching content failed'
414 entry_updated: 'Contenuto aggiornato' 417 entry_updated: 'Contenuto aggiornato'
415 entry_reloaded: 'Contenuto ricaricato' 418 entry_reloaded: 'Contenuto ricaricato'
416 entry_reload_failed: 'Errore nel ricaricamento del contenuto' 419 # entry_reload_failed: 'Entry reloaded but fetching content failed'
417 entry_archived: 'Contenuto archiviato' 420 entry_archived: 'Contenuto archiviato'
418 entry_unarchived: 'Contenuto dis-archiviato' 421 entry_unarchived: 'Contenuto dis-archiviato'
419 entry_starred: 'Contenuto segnato come preferito' 422 entry_starred: 'Contenuto segnato come preferito'
@@ -427,6 +430,7 @@ flashes:
427 failed: 'Importazione fallita, riprova.' 430 failed: 'Importazione fallita, riprova.'
428 failed_on_file: 'Errore durante la processazione dei dati da importare. Verifica il tuo file di import.' 431 failed_on_file: 'Errore durante la processazione dei dati da importare. Verifica il tuo file di import.'
429 summary: 'Sommario di importazione: %imported% importati, %skipped% già salvati.' 432 summary: 'Sommario di importazione: %imported% importati, %skipped% già salvati.'
433 # summary_with_queue: 'Import summary: %queued% queued.'
430 developer: 434 developer:
431 notice: 435 notice:
432 client_created: 'Nuovo client creato.' 436 client_created: 'Nuovo client creato.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
index 7d9d4e24..c6818449 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
@@ -68,6 +68,7 @@ config:
68 200_word: "Legissi a l'entorn de 200 mots per minuta" 68 200_word: "Legissi a l'entorn de 200 mots per minuta"
69 300_word: "Legissi a l'entorn de 300 mots per minuta" 69 300_word: "Legissi a l'entorn de 300 mots per minuta"
70 400_word: "Legissi a l'entorn de 400 mots per minuta" 70 400_word: "Legissi a l'entorn de 400 mots per minuta"
71 pocket_consumer_key_label: Clau d'autentificacion Pocket per importar las donadas
71 form_rss: 72 form_rss:
72 description: "Los fluxes RSS fornits per wallabag vos permeton de legir vòstres articles salvagardats dins vòstre lector de fluxes preferit. Per los poder emplegar, vos cal, d'en primièr crear un geton." 73 description: "Los fluxes RSS fornits per wallabag vos permeton de legir vòstres articles salvagardats dins vòstre lector de fluxes preferit. Per los poder emplegar, vos cal, d'en primièr crear un geton."
73 token_label: 'Geton RSS' 74 token_label: 'Geton RSS'
@@ -346,6 +347,8 @@ import:
346 page_title: 'Importer > Readability' 347 page_title: 'Importer > Readability'
347 description: "Aquesta aisina importarà totas vòstres articles de Readability. Sus la pagina de l'aisina (https://www.readability.com/tools/), clicatz sus \"Export your data\" dins la seccion \"Data Export\". Recebretz un corrièl per telecargar un json (qu'acaba pas amb un .json de fach)." 348 description: "Aquesta aisina importarà totas vòstres articles de Readability. Sus la pagina de l'aisina (https://www.readability.com/tools/), clicatz sus \"Export your data\" dins la seccion \"Data Export\". Recebretz un corrièl per telecargar un json (qu'acaba pas amb un .json de fach)."
348 how_to: "Mercés de seleccionar vòstre Readability fichièr e de clicar sul boton dejós per lo telecargar e l'importar." 349 how_to: "Mercés de seleccionar vòstre Readability fichièr e de clicar sul boton dejós per lo telecargar e l'importar."
350 worker:
351 # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
349 352
350developer: 353developer:
351 page_title: 'Desvolopador' 354 page_title: 'Desvolopador'
@@ -411,10 +414,10 @@ flashes:
411 notice: 414 notice:
412 entry_already_saved: 'Article ja salvargardat lo %date%' 415 entry_already_saved: 'Article ja salvargardat lo %date%'
413 entry_saved: 'Article enregistrat' 416 entry_saved: 'Article enregistrat'
414 entry_saved_failed: "Fracàs de l'enregistrament de l'entrada" 417 # entry_saved_failed: 'Entry saved but fetching content failed'
415 entry_updated: 'Article mes a jorn' 418 entry_updated: 'Article mes a jorn'
416 entry_reloaded: 'Article recargat' 419 entry_reloaded: 'Article recargat'
417 entry_reload_failed: "Fracàs de l'actualizacion de l'article" 420 # entry_reload_failed: 'Entry reloaded but fetching content failed'
418 entry_archived: 'Article marcat coma legit' 421 entry_archived: 'Article marcat coma legit'
419 entry_unarchived: 'Article marcat coma pas legit' 422 entry_unarchived: 'Article marcat coma pas legit'
420 entry_starred: 'Article apondut dins los favorits' 423 entry_starred: 'Article apondut dins los favorits'
@@ -428,6 +431,7 @@ flashes:
428 failed: "L'importacion a fracassat, mercés de tornar ensajar" 431 failed: "L'importacion a fracassat, mercés de tornar ensajar"
429 failed_on_file: "Errorr pendent du tractament de l'import. Mercés de verificar vòstre fichièr." 432 failed_on_file: "Errorr pendent du tractament de l'import. Mercés de verificar vòstre fichièr."
430 summary: "Rapòrt d'import: %imported% importats, %skipped% ja presents." 433 summary: "Rapòrt d'import: %imported% importats, %skipped% ja presents."
434 # summary_with_queue: 'Import summary: %queued% queued.'
431 developer: 435 developer:
432 notice: 436 notice:
433 client_created: 'Novèl client creat' 437 client_created: 'Novèl client creat'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
index da50cd4c..fb821966 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
@@ -68,6 +68,7 @@ config:
68 200_word: 'Czytam ~200 słów na minutę' 68 200_word: 'Czytam ~200 słów na minutę'
69 300_word: 'Czytam ~300 słów na minutę' 69 300_word: 'Czytam ~300 słów na minutę'
70 400_word: 'Czytam ~400 słów na minutę' 70 400_word: 'Czytam ~400 słów na minutę'
71 pocket_consumer_key_label: Klucz klienta Pocket do importu zawartości
71 form_rss: 72 form_rss:
72 description: 'Kanały RSS prowadzone przez wallabag pozwalają Ci na czytanie twoich zapisanych artykułów w twoium ulubionym czytniku RSS. Musisz najpierw wynegenerować tokena.‌' 73 description: 'Kanały RSS prowadzone przez wallabag pozwalają Ci na czytanie twoich zapisanych artykułów w twoium ulubionym czytniku RSS. Musisz najpierw wynegenerować tokena.‌'
73 token_label: 'Token RSS' 74 token_label: 'Token RSS'
@@ -346,6 +347,8 @@ import:
346 page_title: 'Import > Readability' 347 page_title: 'Import > Readability'
347 description: 'Ten importer, zaimportuje wszystkie twoje artykuły z Readability. Na stronie narzędzi (https://www.readability.com/tools/), kliknij na "Export your data" w sekcji "Data Export". Otrzymach email z plikiem JSON (plik nie będzie zawierał rozszerzenia .json).' 348 description: 'Ten importer, zaimportuje wszystkie twoje artykuły z Readability. Na stronie narzędzi (https://www.readability.com/tools/), kliknij na "Export your data" w sekcji "Data Export". Otrzymach email z plikiem JSON (plik nie będzie zawierał rozszerzenia .json).'
348 how_to: 'Wybierz swój plik eksportu z Readability i kliknij poniższy przycisk, aby go załadować.' 349 how_to: 'Wybierz swój plik eksportu z Readability i kliknij poniższy przycisk, aby go załadować.'
350 worker:
351 # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
349 352
350developer: 353developer:
351 page_title: 'Deweloper' 354 page_title: 'Deweloper'
@@ -411,10 +414,10 @@ flashes:
411 notice: 414 notice:
412 entry_already_saved: 'Wpis już został dodany %date%' 415 entry_already_saved: 'Wpis już został dodany %date%'
413 entry_saved: 'Wpis zapisany' 416 entry_saved: 'Wpis zapisany'
414 entry_saved_failed: 'Zapis artykułu się nie powiódł' 417 # entry_saved_failed: 'Entry saved but fetching content failed'
415 entry_updated: 'Wpis zaktualizowany' 418 entry_updated: 'Wpis zaktualizowany'
416 entry_reloaded: 'Wpis ponownie załadowany' 419 entry_reloaded: 'Wpis ponownie załadowany'
417 entry_reload_failed: 'Błąd ponownego załadowania' 420 # entry_reload_failed: 'Entry reloaded but fetching content failed'
418 entry_archived: 'Wpis dodany do archiwum' 421 entry_archived: 'Wpis dodany do archiwum'
419 entry_unarchived: 'Wpis usunięty z archiwum' 422 entry_unarchived: 'Wpis usunięty z archiwum'
420 entry_starred: 'Wpis oznaczony gwiazdką' 423 entry_starred: 'Wpis oznaczony gwiazdką'
@@ -428,6 +431,7 @@ flashes:
428 failed: 'Nieudany import, prosimy spróbować ponownie.' 431 failed: 'Nieudany import, prosimy spróbować ponownie.'
429 failed_on_file: 'Błąd podczas ptrzetwarzania pliku. Sprawdż swój importowany plik.' 432 failed_on_file: 'Błąd podczas ptrzetwarzania pliku. Sprawdż swój importowany plik.'
430 summary: 'Podsumowanie importu: %imported% zaimportowane, %skipped% już zapisane.' 433 summary: 'Podsumowanie importu: %imported% zaimportowane, %skipped% już zapisane.'
434 # summary_with_queue: 'Import summary: %queued% queued.'
431 developer: 435 developer:
432 notice: 436 notice:
433 client_created: 'Nowy klient utworzony.' 437 client_created: 'Nowy klient utworzony.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
index f41609d0..3d22e29d 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
@@ -68,6 +68,7 @@ config:
68 # 200_word: 'I read ~200 words per minute' 68 # 200_word: 'I read ~200 words per minute'
69 # 300_word: 'I read ~300 words per minute' 69 # 300_word: 'I read ~300 words per minute'
70 # 400_word: 'I read ~400 words per minute' 70 # 400_word: 'I read ~400 words per minute'
71 pocket_consumer_key_label: Cheie consumator pentru importarea contentului din Pocket
71 form_rss: 72 form_rss:
72 description: 'Feed-urile RSS oferite de wallabag îți permit să-ți citești articolele salvate în reader-ul tău preferat RSS.' 73 description: 'Feed-urile RSS oferite de wallabag îți permit să-ți citești articolele salvate în reader-ul tău preferat RSS.'
73 token_label: 'RSS-Token' 74 token_label: 'RSS-Token'
@@ -346,6 +347,8 @@ import:
346 # page_title: 'Import > Readability' 347 # page_title: 'Import > Readability'
347 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 348 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
348 # how_to: 'Please select your Readability export and click on the below button to upload and import it.' 349 # how_to: 'Please select your Readability export and click on the below button to upload and import it.'
350 worker:
351 # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
349 352
350developer: 353developer:
351 # page_title: 'Developer' 354 # page_title: 'Developer'
@@ -411,10 +414,10 @@ flashes:
411 notice: 414 notice:
412 # entry_already_saved: 'Entry already saved on %date%' 415 # entry_already_saved: 'Entry already saved on %date%'
413 # entry_saved: 'Entry saved' 416 # entry_saved: 'Entry saved'
414 # entry_saved_failed: 'Failed to save entry' 417 # entry_saved_failed: 'Entry saved but fetching content failed'
415 # entry_updated: 'Entry updated' 418 # entry_updated: 'Entry updated'
416 # entry_reloaded: 'Entry reloaded' 419 # entry_reloaded: 'Entry reloaded'
417 # entry_reload_failed: 'Failed to reload entry' 420 # entry_reload_failed: 'Entry reloaded but fetching content failed'
418 entry_archived: 'Articol arhivat' 421 entry_archived: 'Articol arhivat'
419 entry_unarchived: 'Articol dezarhivat' 422 entry_unarchived: 'Articol dezarhivat'
420 entry_starred: 'Articol adăugat la favorite' 423 entry_starred: 'Articol adăugat la favorite'
@@ -428,6 +431,7 @@ flashes:
428 # failed: 'Import failed, please try again.' 431 # failed: 'Import failed, please try again.'
429 # failed_on_file: 'Error while processing import. Please verify your import file.' 432 # failed_on_file: 'Error while processing import. Please verify your import file.'
430 # summary: 'Import summary: %imported% imported, %skipped% already saved.' 433 # summary: 'Import summary: %imported% imported, %skipped% already saved.'
434 # summary_with_queue: 'Import summary: %queued% queued.'
431 developer: 435 developer:
432 notice: 436 notice:
433 # client_created: 'New client created.' 437 # client_created: 'New client created.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
index 6dfbfa89..5099b002 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
@@ -68,6 +68,7 @@ config:
68 # 200_word: 'I read ~200 words per minute' 68 # 200_word: 'I read ~200 words per minute'
69 # 300_word: 'I read ~300 words per minute' 69 # 300_word: 'I read ~300 words per minute'
70 # 400_word: 'I read ~400 words per minute' 70 # 400_word: 'I read ~400 words per minute'
71 # pocket_consumer_key_label: Consumer key for Pocket to import contents
71 form_rss: 72 form_rss:
72 description: 'wallabag RSS akışı kaydetmiş olduğunuz makalelerini favori RSS okuyucunuzda görüntülemenizi sağlar. Bunu yapabilmek için öncelikle belirteç (token) oluşturmalısınız.' 73 description: 'wallabag RSS akışı kaydetmiş olduğunuz makalelerini favori RSS okuyucunuzda görüntülemenizi sağlar. Bunu yapabilmek için öncelikle belirteç (token) oluşturmalısınız.'
73 token_label: 'RSS belirteci (token)' 74 token_label: 'RSS belirteci (token)'
@@ -344,8 +345,10 @@ import:
344 # description: 'This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.' 345 # description: 'This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.'
345 readability: 346 readability:
346 page_title: 'İçe Aktar > Readability' 347 page_title: 'İçe Aktar > Readability'
347 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).' 348 # description: 'This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).'
348 # how_to: 'Please select your Readability export and click on the below button to upload and import it.' 349 # how_to: 'Please select your Readability export and click on the below button to upload and import it.'
350 worker:
351 # enabled: "Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:"
349 352
350developer: 353developer:
351 # page_title: 'Developer' 354 # page_title: 'Developer'
@@ -411,10 +414,10 @@ flashes:
411 notice: 414 notice:
412 entry_already_saved: 'Entry already saved on %date%' 415 entry_already_saved: 'Entry already saved on %date%'
413 entry_saved: 'Makale kaydedildi' 416 entry_saved: 'Makale kaydedildi'
414 # entry_saved_failed: 'Failed to save entry' 417 # entry_saved_failed: 'Entry saved but fetching content failed'
415 # entry_updated: 'Entry updated' 418 # entry_updated: 'Entry updated'
416 entry_reloaded: 'Makale içeriği yenilendi' 419 entry_reloaded: 'Makale içeriği yenilendi'
417 # entry_reload_failed: 'Failed to reload entry' 420 # entry_reload_failed: 'Entry reloaded but fetching content failed'
418 entry_archived: 'Makale arşivlendi' 421 entry_archived: 'Makale arşivlendi'
419 entry_unarchived: 'Makale arşivden çıkartıldı' 422 entry_unarchived: 'Makale arşivden çıkartıldı'
420 entry_starred: 'Makale favorilere eklendi' 423 entry_starred: 'Makale favorilere eklendi'
@@ -428,6 +431,7 @@ flashes:
428 # failed: 'Import failed, please try again.' 431 # failed: 'Import failed, please try again.'
429 # failed_on_file: 'Error while processing import. Please verify your import file.' 432 # failed_on_file: 'Error while processing import. Please verify your import file.'
430 # summary: 'Import summary: %imported% imported, %skipped% already saved.' 433 # summary: 'Import summary: %imported% imported, %skipped% already saved.'
434 # summary_with_queue: 'Import summary: %queued% queued.'
431 developer: 435 developer:
432 notice: 436 notice:
433 # client_created: 'New client created.' 437 # client_created: 'New client created.'
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
index f89265d4..6446cf2c 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
@@ -44,6 +44,18 @@
44 </div> 44 </div>
45 </fieldset> 45 </fieldset>
46 46
47 <fieldset class="w500p inline">
48 <div class="row">
49 {{ form_label(form.config.pocket_consumer_key) }}
50 {{ form_errors(form.config.pocket_consumer_key) }}
51 {{ form_widget(form.config.pocket_consumer_key) }}
52 <p>
53 &raquo;
54 <a href="https://getpocket.com/developer/docs/authentication">https://getpocket.com/developer/docs/authentication</a>
55 </p>
56 </div>
57 </fieldset>
58
47 {{ form_rest(form.config) }} 59 {{ form_rest(form.config) }}
48 </form> 60 </form>
49 61
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
index bf390e89..5330c353 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
@@ -62,6 +62,18 @@
62 </div> 62 </div>
63 </div> 63 </div>
64 64
65 <div class="row">
66 <div class="input-field col s12">
67 {{ form_label(form.config.pocket_consumer_key) }}
68 {{ form_errors(form.config.pocket_consumer_key) }}
69 {{ form_widget(form.config.pocket_consumer_key) }}
70 <p>
71 &raquo;
72 <a href="https://getpocket.com/developer/docs/authentication">https://getpocket.com/developer/docs/authentication</a>
73 </p>
74 </div>
75 </div>
76
65 {{ form_widget(form.config.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} 77 {{ form_widget(form.config.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
66 {{ form_rest(form.config) }} 78 {{ form_rest(form.config) }}
67 </form> 79 </form>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
index 72becfa4..df05e2a4 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
@@ -19,6 +19,8 @@
19 Materialize.toast('{{ flashMessage|trans }}', 4000); 19 Materialize.toast('{{ flashMessage|trans }}', 4000);
20 </script> 20 </script>
21 {% endfor %} 21 {% endfor %}
22
23 {{ render(controller("WallabagImportBundle:Import:checkQueue")) }}
22{% endblock %} 24{% endblock %}
23 25
24{% block menu %} 26{% block menu %}
diff --git a/src/Wallabag/ImportBundle/Command/ImportCommand.php b/src/Wallabag/ImportBundle/Command/ImportCommand.php
index a4aa8531..20ecc6e1 100644
--- a/src/Wallabag/ImportBundle/Command/ImportCommand.php
+++ b/src/Wallabag/ImportBundle/Command/ImportCommand.php
@@ -26,6 +26,10 @@ class ImportCommand extends ContainerAwareCommand
26 { 26 {
27 $output->writeln('Start : '.(new \DateTime())->format('d-m-Y G:i:s').' ---'); 27 $output->writeln('Start : '.(new \DateTime())->format('d-m-Y G:i:s').' ---');
28 28
29 if (!file_exists($input->getArgument('filepath'))) {
30 throw new Exception(sprintf('File "%s" not found', $input->getArgument('filepath')));
31 }
32
29 $em = $this->getContainer()->get('doctrine')->getManager(); 33 $em = $this->getContainer()->get('doctrine')->getManager();
30 // Turning off doctrine default logs queries for saving memory 34 // Turning off doctrine default logs queries for saving memory
31 $em->getConnection()->getConfiguration()->setSQLLogger(null); 35 $em->getConnection()->getConfiguration()->setSQLLogger(null);
@@ -43,9 +47,9 @@ class ImportCommand extends ContainerAwareCommand
43 } 47 }
44 48
45 $wallabag->setMarkAsRead($input->getOption('markAsRead')); 49 $wallabag->setMarkAsRead($input->getOption('markAsRead'));
50 $wallabag->setUser($user);
46 51
47 $res = $wallabag 52 $res = $wallabag
48 ->setUser($user)
49 ->setFilepath($input->getArgument('filepath')) 53 ->setFilepath($input->getArgument('filepath'))
50 ->import(); 54 ->import();
51 55
diff --git a/src/Wallabag/ImportBundle/Command/RedisWorkerCommand.php b/src/Wallabag/ImportBundle/Command/RedisWorkerCommand.php
new file mode 100644
index 00000000..5f90e00f
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Command/RedisWorkerCommand.php
@@ -0,0 +1,44 @@
1<?php
2
3namespace Wallabag\ImportBundle\Command;
4
5use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
6use Symfony\Component\Config\Definition\Exception\Exception;
7use Symfony\Component\Console\Input\InputArgument;
8use Symfony\Component\Console\Input\InputOption;
9use Symfony\Component\Console\Input\InputInterface;
10use Symfony\Component\Console\Output\OutputInterface;
11use Simpleue\Worker\QueueWorker;
12
13class RedisWorkerCommand extends ContainerAwareCommand
14{
15 protected function configure()
16 {
17 $this
18 ->setName('wallabag:import:redis-worker')
19 ->setDescription('Launch Redis worker')
20 ->addArgument('serviceName', InputArgument::REQUIRED, 'Service to use: wallabag_v1, wallabag_v2, pocket or readability')
21 ->addOption('maxIterations', '', InputOption::VALUE_OPTIONAL, 'Number of iterations before stoping', false)
22 ;
23 }
24
25 protected function execute(InputInterface $input, OutputInterface $output)
26 {
27 $output->writeln('Worker started at: '.(new \DateTime())->format('d-m-Y G:i:s'));
28 $output->writeln('Waiting for message ...');
29
30 $serviceName = $input->getArgument('serviceName');
31
32 if (!$this->getContainer()->has('wallabag_import.queue.redis.'.$serviceName) || !$this->getContainer()->has('wallabag_import.consumer.redis.'.$serviceName)) {
33 throw new Exception(sprintf('No queue or consumer found for service name: "%s"', $input->getArgument('serviceName')));
34 }
35
36 $worker = new QueueWorker(
37 $this->getContainer()->get('wallabag_import.queue.redis.'.$serviceName),
38 $this->getContainer()->get('wallabag_import.consumer.redis.'.$serviceName),
39 $input->getOption('maxIterations')
40 );
41
42 $worker->start();
43 }
44}
diff --git a/src/Wallabag/ImportBundle/Consumer/AMQPEntryConsumer.php b/src/Wallabag/ImportBundle/Consumer/AMQPEntryConsumer.php
new file mode 100644
index 00000000..5aaa8c03
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Consumer/AMQPEntryConsumer.php
@@ -0,0 +1,17 @@
1<?php
2
3namespace Wallabag\ImportBundle\Consumer;
4
5use OldSound\RabbitMqBundle\RabbitMq\ConsumerInterface;
6use PhpAmqpLib\Message\AMQPMessage;
7
8class AMQPEntryConsumer extends AbstractConsumer implements ConsumerInterface
9{
10 /**
11 * {@inheritdoc}
12 */
13 public function execute(AMQPMessage $msg)
14 {
15 return $this->handleMessage($msg->body);
16 }
17}
diff --git a/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php b/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php
new file mode 100644
index 00000000..2b85ad76
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php
@@ -0,0 +1,74 @@
1<?php
2
3namespace Wallabag\ImportBundle\Consumer;
4
5use Doctrine\ORM\EntityManager;
6use Wallabag\ImportBundle\Import\AbstractImport;
7use Wallabag\UserBundle\Repository\UserRepository;
8use Wallabag\CoreBundle\Entity\Entry;
9use Wallabag\CoreBundle\Entity\Tag;
10use Psr\Log\LoggerInterface;
11use Psr\Log\NullLogger;
12
13abstract class AbstractConsumer
14{
15 protected $em;
16 protected $userRepository;
17 protected $import;
18 protected $logger;
19
20 public function __construct(EntityManager $em, UserRepository $userRepository, AbstractImport $import, LoggerInterface $logger = null)
21 {
22 $this->em = $em;
23 $this->userRepository = $userRepository;
24 $this->import = $import;
25 $this->logger = $logger ?: new NullLogger();
26 }
27
28 /**
29 * Handle a message and save it.
30 *
31 * @param string $body Message from the queue (in json)
32 *
33 * @return bool
34 */
35 protected function handleMessage($body)
36 {
37 $storedEntry = json_decode($body, true);
38
39 $user = $this->userRepository->find($storedEntry['userId']);
40
41 // no user? Drop message
42 if (null === $user) {
43 $this->logger->warning('Unable to retrieve user', ['entry' => $storedEntry]);
44
45 return false;
46 }
47
48 $this->import->setUser($user);
49
50 $entry = $this->import->parseEntry($storedEntry);
51
52 if (null === $entry) {
53 $this->logger->warning('Unable to parse entry', ['entry' => $storedEntry]);
54
55 return false;
56 }
57
58 try {
59 $this->em->flush();
60
61 // clear only affected entities
62 $this->em->clear(Entry::class);
63 $this->em->clear(Tag::class);
64 } catch (\Exception $e) {
65 $this->logger->warning('Unable to save entry', ['entry' => $storedEntry, 'exception' => $e]);
66
67 return false;
68 }
69
70 $this->logger->info('Content with url imported! ('.$entry->getUrl().')');
71
72 return true;
73 }
74}
diff --git a/src/Wallabag/ImportBundle/Consumer/RedisEntryConsumer.php b/src/Wallabag/ImportBundle/Consumer/RedisEntryConsumer.php
new file mode 100644
index 00000000..450b71ff
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Consumer/RedisEntryConsumer.php
@@ -0,0 +1,29 @@
1<?php
2
3namespace Wallabag\ImportBundle\Consumer;
4
5use Simpleue\Job\Job;
6
7class RedisEntryConsumer extends AbstractConsumer implements Job
8{
9 /**
10 * Handle one message by one message.
11 *
12 * @param string $job Content of the message (directly from Redis)
13 *
14 * @return bool
15 */
16 public function manage($job)
17 {
18 return $this->handleMessage($job);
19 }
20
21 /**
22 * Should tell if the given job will kill the worker.
23 * We don't want to stop it :).
24 */
25 public function isStopJob($job)
26 {
27 return false;
28 }
29}
diff --git a/src/Wallabag/ImportBundle/Controller/ImportController.php b/src/Wallabag/ImportBundle/Controller/ImportController.php
index c1486e38..ea4f7d7b 100644
--- a/src/Wallabag/ImportBundle/Controller/ImportController.php
+++ b/src/Wallabag/ImportBundle/Controller/ImportController.php
@@ -16,4 +16,66 @@ class ImportController extends Controller
16 'imports' => $this->get('wallabag_import.chain')->getAll(), 16 'imports' => $this->get('wallabag_import.chain')->getAll(),
17 ]); 17 ]);
18 } 18 }
19
20 /**
21 * Display how many messages are queue (both in Redis and RabbitMQ).
22 * Only for admins.
23 */
24 public function checkQueueAction()
25 {
26 $nbRedisMessages = null;
27 $nbRabbitMessages = null;
28
29 if (!$this->get('security.authorization_checker')->isGranted('ROLE_SUPER_ADMIN')) {
30 return $this->render('WallabagImportBundle:Import:check_queue.html.twig', [
31 'nbRedisMessages' => $nbRedisMessages,
32 'nbRabbitMessages' => $nbRabbitMessages,
33 ]);
34 }
35
36 if ($this->get('craue_config')->get('import_with_rabbitmq')) {
37 $nbRabbitMessages = $this->getTotalMessageInRabbitQueue('pocket')
38 + $this->getTotalMessageInRabbitQueue('readability')
39 + $this->getTotalMessageInRabbitQueue('wallabag_v1')
40 + $this->getTotalMessageInRabbitQueue('wallabag_v2')
41 ;
42 } elseif ($this->get('craue_config')->get('import_with_redis')) {
43 $redis = $this->get('wallabag_core.redis.client');
44
45 $nbRedisMessages = $redis->llen('wallabag.import.pocket')
46 + $redis->llen('wallabag.import.readability')
47 + $redis->llen('wallabag.import.wallabag_v1')
48 + $redis->llen('wallabag.import.wallabag_v2')
49 ;
50 }
51
52 return $this->render('WallabagImportBundle:Import:check_queue.html.twig', [
53 'nbRedisMessages' => $nbRedisMessages,
54 'nbRabbitMessages' => $nbRabbitMessages,
55 ]);
56 }
57
58 /**
59 * Count message in RabbitMQ queue.
60 * It get one message without acking it (so it'll stay in the queue)
61 * which will include the total of *other* messages in the queue.
62 * Adding one to that messages will result in the full total message.
63 *
64 * @param string $importService The import service related: pocket, readability, wallabag_v1 or wallabag_v2
65 *
66 * @return int
67 */
68 private function getTotalMessageInRabbitQueue($importService)
69 {
70 $message = $this
71 ->get('old_sound_rabbit_mq.import_'.$importService.'_consumer')
72 ->getChannel()
73 ->basic_get('wallabag.import.'.$importService);
74
75 if (null === $message) {
76 return 0;
77 }
78
79 return $message->delivery_info['message_count'] + 1;
80 }
19} 81}
diff --git a/src/Wallabag/ImportBundle/Controller/PocketController.php b/src/Wallabag/ImportBundle/Controller/PocketController.php
index 36ee25bf..56be5cbf 100644
--- a/src/Wallabag/ImportBundle/Controller/PocketController.php
+++ b/src/Wallabag/ImportBundle/Controller/PocketController.php
@@ -11,11 +11,30 @@ use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
11class PocketController extends Controller 11class PocketController extends Controller
12{ 12{
13 /** 13 /**
14 * Return Pocket Import Service with or without RabbitMQ enabled.
15 *
16 * @return \Wallabag\ImportBundle\Import\PocketImport
17 */
18 private function getPocketImportService()
19 {
20 $pocket = $this->get('wallabag_import.pocket.import');
21 $pocket->setUser($this->getUser());
22
23 if ($this->get('craue_config')->get('import_with_rabbitmq')) {
24 $pocket->setProducer($this->get('old_sound_rabbit_mq.import_pocket_producer'));
25 } elseif ($this->get('craue_config')->get('import_with_redis')) {
26 $pocket->setProducer($this->get('wallabag_import.producer.redis.pocket'));
27 }
28
29 return $pocket;
30 }
31
32 /**
14 * @Route("/pocket", name="import_pocket") 33 * @Route("/pocket", name="import_pocket")
15 */ 34 */
16 public function indexAction() 35 public function indexAction()
17 { 36 {
18 $pocket = $this->get('wallabag_import.pocket.import'); 37 $pocket = $this->getPocketImportService();
19 $form = $this->createFormBuilder($pocket) 38 $form = $this->createFormBuilder($pocket)
20 ->add('mark_as_read', CheckboxType::class, [ 39 ->add('mark_as_read', CheckboxType::class, [
21 'label' => 'import.form.mark_as_read_label', 40 'label' => 'import.form.mark_as_read_label',
@@ -24,8 +43,8 @@ class PocketController extends Controller
24 ->getForm(); 43 ->getForm();
25 44
26 return $this->render('WallabagImportBundle:Pocket:index.html.twig', [ 45 return $this->render('WallabagImportBundle:Pocket:index.html.twig', [
27 'import' => $this->get('wallabag_import.pocket.import'), 46 'import' => $this->getPocketImportService(),
28 'has_consumer_key' => '' == trim($this->get('craue_config')->get('pocket_consumer_key')) ? false : true, 47 'has_consumer_key' => '' === trim($this->getUser()->getConfig()->getPocketConsumerKey()) ? false : true,
29 'form' => $form->createView(), 48 'form' => $form->createView(),
30 ]); 49 ]);
31 } 50 }
@@ -35,7 +54,7 @@ class PocketController extends Controller
35 */ 54 */
36 public function authAction(Request $request) 55 public function authAction(Request $request)
37 { 56 {
38 $requestToken = $this->get('wallabag_import.pocket.import') 57 $requestToken = $this->getPocketImportService()
39 ->getRequestToken($this->generateUrl('import', [], UrlGeneratorInterface::ABSOLUTE_URL)); 58 ->getRequestToken($this->generateUrl('import', [], UrlGeneratorInterface::ABSOLUTE_URL));
40 59
41 if (false === $requestToken) { 60 if (false === $requestToken) {
@@ -62,7 +81,7 @@ class PocketController extends Controller
62 public function callbackAction() 81 public function callbackAction()
63 { 82 {
64 $message = 'flashes.import.notice.failed'; 83 $message = 'flashes.import.notice.failed';
65 $pocket = $this->get('wallabag_import.pocket.import'); 84 $pocket = $this->getPocketImportService();
66 85
67 $markAsRead = $this->get('session')->get('mark_as_read'); 86 $markAsRead = $this->get('session')->get('mark_as_read');
68 $this->get('session')->remove('mark_as_read'); 87 $this->get('session')->remove('mark_as_read');
@@ -83,6 +102,12 @@ class PocketController extends Controller
83 '%imported%' => $summary['imported'], 102 '%imported%' => $summary['imported'],
84 '%skipped%' => $summary['skipped'], 103 '%skipped%' => $summary['skipped'],
85 ]); 104 ]);
105
106 if (0 < $summary['queued']) {
107 $message = $this->get('translator')->trans('flashes.import.notice.summary_with_queue', [
108 '%queued%' => $summary['queued'],
109 ]);
110 }
86 } 111 }
87 112
88 $this->get('session')->getFlashBag()->add( 113 $this->get('session')->getFlashBag()->add(
diff --git a/src/Wallabag/ImportBundle/Controller/ReadabilityController.php b/src/Wallabag/ImportBundle/Controller/ReadabilityController.php
index b61aa99c..d00e22c2 100644
--- a/src/Wallabag/ImportBundle/Controller/ReadabilityController.php
+++ b/src/Wallabag/ImportBundle/Controller/ReadabilityController.php
@@ -18,15 +18,21 @@ class ReadabilityController extends Controller
18 $form->handleRequest($request); 18 $form->handleRequest($request);
19 19
20 $readability = $this->get('wallabag_import.readability.import'); 20 $readability = $this->get('wallabag_import.readability.import');
21 $readability->setUser($this->getUser());
22
23 if ($this->get('craue_config')->get('import_with_rabbitmq')) {
24 $readability->setProducer($this->get('old_sound_rabbit_mq.import_readability_producer'));
25 } elseif ($this->get('craue_config')->get('import_with_redis')) {
26 $readability->setProducer($this->get('wallabag_import.producer.redis.readability'));
27 }
21 28
22 if ($form->isValid()) { 29 if ($form->isValid()) {
23 $file = $form->get('file')->getData(); 30 $file = $form->get('file')->getData();
24 $markAsRead = $form->get('mark_as_read')->getData(); 31 $markAsRead = $form->get('mark_as_read')->getData();
25 $name = 'readability_'.$this->getUser()->getId().'.json'; 32 $name = 'readability_'.$this->getUser()->getId().'.json';
26 33
27 if (in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) { 34 if (null !== $file && in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
28 $res = $readability 35 $res = $readability
29 ->setUser($this->getUser())
30 ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name) 36 ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name)
31 ->setMarkAsRead($markAsRead) 37 ->setMarkAsRead($markAsRead)
32 ->import(); 38 ->import();
@@ -40,6 +46,12 @@ class ReadabilityController extends Controller
40 '%skipped%' => $summary['skipped'], 46 '%skipped%' => $summary['skipped'],
41 ]); 47 ]);
42 48
49 if (0 < $summary['queued']) {
50 $message = $this->get('translator')->trans('flashes.import.notice.summary_with_queue', [
51 '%queued%' => $summary['queued'],
52 ]);
53 }
54
43 unlink($this->getParameter('wallabag_import.resource_dir').'/'.$name); 55 unlink($this->getParameter('wallabag_import.resource_dir').'/'.$name);
44 } 56 }
45 57
diff --git a/src/Wallabag/ImportBundle/Controller/WallabagController.php b/src/Wallabag/ImportBundle/Controller/WallabagController.php
index 76ced0d2..9c0cde80 100644
--- a/src/Wallabag/ImportBundle/Controller/WallabagController.php
+++ b/src/Wallabag/ImportBundle/Controller/WallabagController.php
@@ -38,15 +38,15 @@ abstract class WallabagController extends Controller
38 $form->handleRequest($request); 38 $form->handleRequest($request);
39 39
40 $wallabag = $this->getImportService(); 40 $wallabag = $this->getImportService();
41 $wallabag->setUser($this->getUser());
41 42
42 if ($form->isValid()) { 43 if ($form->isValid()) {
43 $file = $form->get('file')->getData(); 44 $file = $form->get('file')->getData();
44 $markAsRead = $form->get('mark_as_read')->getData(); 45 $markAsRead = $form->get('mark_as_read')->getData();
45 $name = $this->getUser()->getId().'.json'; 46 $name = $this->getUser()->getId().'.json';
46 47
47 if (in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) { 48 if (null !== $file && in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes')) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
48 $res = $wallabag 49 $res = $wallabag
49 ->setUser($this->getUser())
50 ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name) 50 ->setFilepath($this->getParameter('wallabag_import.resource_dir').'/'.$name)
51 ->setMarkAsRead($markAsRead) 51 ->setMarkAsRead($markAsRead)
52 ->import(); 52 ->import();
@@ -60,6 +60,12 @@ abstract class WallabagController extends Controller
60 '%skipped%' => $summary['skipped'], 60 '%skipped%' => $summary['skipped'],
61 ]); 61 ]);
62 62
63 if (0 < $summary['queued']) {
64 $message = $this->get('translator')->trans('flashes.import.notice.summary_with_queue', [
65 '%queued%' => $summary['queued'],
66 ]);
67 }
68
63 unlink($this->getParameter('wallabag_import.resource_dir').'/'.$name); 69 unlink($this->getParameter('wallabag_import.resource_dir').'/'.$name);
64 } 70 }
65 71
diff --git a/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php b/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
index 3e748d57..312c7a35 100644
--- a/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
+++ b/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
@@ -12,7 +12,15 @@ class WallabagV1Controller extends WallabagController
12 */ 12 */
13 protected function getImportService() 13 protected function getImportService()
14 { 14 {
15 return $this->get('wallabag_import.wallabag_v1.import'); 15 $service = $this->get('wallabag_import.wallabag_v1.import');
16
17 if ($this->get('craue_config')->get('import_with_rabbitmq')) {
18 $service->setProducer($this->get('old_sound_rabbit_mq.import_wallabag_v1_producer'));
19 } elseif ($this->get('craue_config')->get('import_with_redis')) {
20 $service->setProducer($this->get('wallabag_import.producer.redis.wallabag_v1'));
21 }
22
23 return $service;
16 } 24 }
17 25
18 /** 26 /**
diff --git a/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php b/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php
index c2a42165..45211fe6 100644
--- a/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php
+++ b/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php
@@ -12,7 +12,15 @@ class WallabagV2Controller extends WallabagController
12 */ 12 */
13 protected function getImportService() 13 protected function getImportService()
14 { 14 {
15 return $this->get('wallabag_import.wallabag_v2.import'); 15 $service = $this->get('wallabag_import.wallabag_v2.import');
16
17 if ($this->get('craue_config')->get('import_with_rabbitmq')) {
18 $service->setProducer($this->get('old_sound_rabbit_mq.import_wallabag_v2_producer'));
19 } elseif ($this->get('craue_config')->get('import_with_redis')) {
20 $service->setProducer($this->get('wallabag_import.producer.redis.wallabag_v2'));
21 }
22
23 return $service;
16 } 24 }
17 25
18 /** 26 /**
diff --git a/src/Wallabag/ImportBundle/Form/Type/UploadImportType.php b/src/Wallabag/ImportBundle/Form/Type/UploadImportType.php
index 92a167d9..f50424c1 100644
--- a/src/Wallabag/ImportBundle/Form/Type/UploadImportType.php
+++ b/src/Wallabag/ImportBundle/Form/Type/UploadImportType.php
@@ -15,6 +15,7 @@ class UploadImportType extends AbstractType
15 $builder 15 $builder
16 ->add('file', FileType::class, [ 16 ->add('file', FileType::class, [
17 'label' => 'import.form.file_label', 17 'label' => 'import.form.file_label',
18 'required' => true,
18 ]) 19 ])
19 ->add('mark_as_read', CheckboxType::class, [ 20 ->add('mark_as_read', CheckboxType::class, [
20 'label' => 'import.form.mark_as_read_label', 21 'label' => 'import.form.mark_as_read_label',
diff --git a/src/Wallabag/ImportBundle/Import/AbstractImport.php b/src/Wallabag/ImportBundle/Import/AbstractImport.php
index 14377a35..a1a14576 100644
--- a/src/Wallabag/ImportBundle/Import/AbstractImport.php
+++ b/src/Wallabag/ImportBundle/Import/AbstractImport.php
@@ -7,12 +7,21 @@ use Psr\Log\NullLogger;
7use Doctrine\ORM\EntityManager; 7use Doctrine\ORM\EntityManager;
8use Wallabag\CoreBundle\Helper\ContentProxy; 8use Wallabag\CoreBundle\Helper\ContentProxy;
9use Wallabag\CoreBundle\Entity\Entry; 9use Wallabag\CoreBundle\Entity\Entry;
10use Wallabag\CoreBundle\Entity\Tag;
11use Wallabag\UserBundle\Entity\User;
12use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
10 13
11abstract class AbstractImport implements ImportInterface 14abstract class AbstractImport implements ImportInterface
12{ 15{
13 protected $em; 16 protected $em;
14 protected $logger; 17 protected $logger;
15 protected $contentProxy; 18 protected $contentProxy;
19 protected $producer;
20 protected $user;
21 protected $markAsRead;
22 protected $skippedEntries = 0;
23 protected $importedEntries = 0;
24 protected $queuedEntries = 0;
16 25
17 public function __construct(EntityManager $em, ContentProxy $contentProxy) 26 public function __construct(EntityManager $em, ContentProxy $contentProxy)
18 { 27 {
@@ -27,21 +36,150 @@ abstract class AbstractImport implements ImportInterface
27 } 36 }
28 37
29 /** 38 /**
39 * Set RabbitMQ/Redis Producer to send each entry to a queue.
40 * This method should be called when user has enabled RabbitMQ.
41 *
42 * @param ProducerInterface $producer
43 */
44 public function setProducer(ProducerInterface $producer)
45 {
46 $this->producer = $producer;
47 }
48
49 /**
50 * Set current user.
51 * Could the current *connected* user or one retrieve by the consumer.
52 *
53 * @param User $user
54 */
55 public function setUser(User $user)
56 {
57 $this->user = $user;
58 }
59
60 /**
61 * Set whether articles must be all marked as read.
62 *
63 * @param bool $markAsRead
64 */
65 public function setMarkAsRead($markAsRead)
66 {
67 $this->markAsRead = $markAsRead;
68
69 return $this;
70 }
71
72 /**
73 * Get whether articles must be all marked as read.
74 */
75 public function getMarkAsRead()
76 {
77 return $this->markAsRead;
78 }
79
80 /**
30 * Fetch content from the ContentProxy (using graby). 81 * Fetch content from the ContentProxy (using graby).
31 * If it fails return false instead of the updated entry. 82 * If it fails return the given entry to be saved in all case (to avoid user to loose the content).
32 * 83 *
33 * @param Entry $entry Entry to update 84 * @param Entry $entry Entry to update
34 * @param string $url Url to grab content for 85 * @param string $url Url to grab content for
35 * @param array $content An array with AT LEAST keys title, html, url, language & content_type to skip the fetchContent from the url 86 * @param array $content An array with AT LEAST keys title, html, url, language & content_type to skip the fetchContent from the url
36 * 87 *
37 * @return Entry|false 88 * @return Entry
38 */ 89 */
39 protected function fetchContent(Entry $entry, $url, array $content = []) 90 protected function fetchContent(Entry $entry, $url, array $content = [])
40 { 91 {
41 try { 92 try {
42 return $this->contentProxy->updateEntry($entry, $url, $content); 93 return $this->contentProxy->updateEntry($entry, $url, $content);
43 } catch (\Exception $e) { 94 } catch (\Exception $e) {
44 return false; 95 return $entry;
96 }
97 }
98
99 /**
100 * Parse and insert all given entries.
101 *
102 * @param $entries
103 */
104 protected function parseEntries($entries)
105 {
106 $i = 1;
107
108 foreach ($entries as $importedEntry) {
109 $entry = $this->parseEntry($importedEntry);
110
111 if (null === $entry) {
112 continue;
113 }
114
115 // flush every 20 entries
116 if (($i % 20) === 0) {
117 $this->em->flush();
118
119 // clear only affected entities
120 $this->em->clear(Entry::class);
121 $this->em->clear(Tag::class);
122 }
123 ++$i;
124 }
125
126 $this->em->flush();
127 }
128
129 /**
130 * Parse entries and send them to the queue.
131 * It should just be a simple loop on all item, no call to the database should be done
132 * to speedup queuing.
133 *
134 * Faster parse entries for Producer.
135 * We don't care to make check at this time. They'll be done by the consumer.
136 *
137 * @param array $entries
138 */
139 protected function parseEntriesForProducer(array $entries)
140 {
141 foreach ($entries as $importedEntry) {
142 // set userId for the producer (it won't know which user is connected)
143 $importedEntry['userId'] = $this->user->getId();
144
145 if ($this->markAsRead) {
146 $importedEntry = $this->setEntryAsRead($importedEntry);
147 }
148
149 ++$this->queuedEntries;
150
151 $this->producer->publish(json_encode($importedEntry));
45 } 152 }
46 } 153 }
154
155 /**
156 * {@inheritdoc}
157 */
158 public function getSummary()
159 {
160 return [
161 'skipped' => $this->skippedEntries,
162 'imported' => $this->importedEntries,
163 'queued' => $this->queuedEntries,
164 ];
165 }
166
167 /**
168 * Parse one entry.
169 *
170 * @param array $importedEntry
171 *
172 * @return Entry
173 */
174 abstract public function parseEntry(array $importedEntry);
175
176 /**
177 * Set current imported entry to archived / read.
178 * Implementation is different accross all imports.
179 *
180 * @param array $importedEntry
181 *
182 * @return array
183 */
184 abstract protected function setEntryAsRead(array $importedEntry);
47} 185}
diff --git a/src/Wallabag/ImportBundle/Import/PocketImport.php b/src/Wallabag/ImportBundle/Import/PocketImport.php
index a6f905b1..e00eb44b 100644
--- a/src/Wallabag/ImportBundle/Import/PocketImport.php
+++ b/src/Wallabag/ImportBundle/Import/PocketImport.php
@@ -6,31 +6,34 @@ use Psr\Log\NullLogger;
6use Doctrine\ORM\EntityManager; 6use Doctrine\ORM\EntityManager;
7use GuzzleHttp\Client; 7use GuzzleHttp\Client;
8use GuzzleHttp\Exception\RequestException; 8use GuzzleHttp\Exception\RequestException;
9use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
10use Wallabag\CoreBundle\Entity\Entry; 9use Wallabag\CoreBundle\Entity\Entry;
11use Wallabag\CoreBundle\Helper\ContentProxy; 10use Wallabag\CoreBundle\Helper\ContentProxy;
12use Craue\ConfigBundle\Util\Config;
13 11
14class PocketImport extends AbstractImport 12class PocketImport extends AbstractImport
15{ 13{
16 private $user;
17 private $client; 14 private $client;
18 private $consumerKey; 15 private $accessToken;
19 private $skippedEntries = 0;
20 private $importedEntries = 0;
21 private $markAsRead;
22 protected $accessToken;
23 16
24 public function __construct(TokenStorageInterface $tokenStorage, EntityManager $em, ContentProxy $contentProxy, Config $craueConfig) 17 const NB_ELEMENTS = 5000;
18
19 public function __construct(EntityManager $em, ContentProxy $contentProxy)
25 { 20 {
26 $this->user = $tokenStorage->getToken()->getUser();
27 $this->em = $em; 21 $this->em = $em;
28 $this->contentProxy = $contentProxy; 22 $this->contentProxy = $contentProxy;
29 $this->consumerKey = $craueConfig->get('pocket_consumer_key');
30 $this->logger = new NullLogger(); 23 $this->logger = new NullLogger();
31 } 24 }
32 25
33 /** 26 /**
27 * Only used for test purpose.
28 *
29 * @return string
30 */
31 public function getAccessToken()
32 {
33 return $this->accessToken;
34 }
35
36 /**
34 * {@inheritdoc} 37 * {@inheritdoc}
35 */ 38 */
36 public function getName() 39 public function getName()
@@ -66,7 +69,7 @@ class PocketImport extends AbstractImport
66 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/request', 69 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/request',
67 [ 70 [
68 'body' => json_encode([ 71 'body' => json_encode([
69 'consumer_key' => $this->consumerKey, 72 'consumer_key' => $this->user->getConfig()->getPocketConsumerKey(),
70 'redirect_uri' => $redirectUri, 73 'redirect_uri' => $redirectUri,
71 ]), 74 ]),
72 ] 75 ]
@@ -96,7 +99,7 @@ class PocketImport extends AbstractImport
96 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/authorize', 99 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/authorize',
97 [ 100 [
98 'body' => json_encode([ 101 'body' => json_encode([
99 'consumer_key' => $this->consumerKey, 102 'consumer_key' => $this->user->getConfig()->getPocketConsumerKey(),
100 'code' => $code, 103 'code' => $code,
101 ]), 104 ]),
102 ] 105 ]
@@ -116,38 +119,22 @@ class PocketImport extends AbstractImport
116 } 119 }
117 120
118 /** 121 /**
119 * Set whether articles must be all marked as read.
120 *
121 * @param bool $markAsRead
122 */
123 public function setMarkAsRead($markAsRead)
124 {
125 $this->markAsRead = $markAsRead;
126
127 return $this;
128 }
129
130 /**
131 * Get whether articles must be all marked as read.
132 */
133 public function getMarkAsRead()
134 {
135 return $this->markAsRead;
136 }
137
138 /**
139 * {@inheritdoc} 122 * {@inheritdoc}
140 */ 123 */
141 public function import() 124 public function import($offset = 0)
142 { 125 {
126 static $run = 0;
127
143 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/get', 128 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/get',
144 [ 129 [
145 'body' => json_encode([ 130 'body' => json_encode([
146 'consumer_key' => $this->consumerKey, 131 'consumer_key' => $this->user->getConfig()->getPocketConsumerKey(),
147 'access_token' => $this->accessToken, 132 'access_token' => $this->accessToken,
148 'detailType' => 'complete', 133 'detailType' => 'complete',
149 'state' => 'all', 134 'state' => 'all',
150 'sort' => 'oldest', 135 'sort' => 'newest',
136 'count' => self::NB_ELEMENTS,
137 'offset' => $offset,
151 ]), 138 ]),
152 ] 139 ]
153 ); 140 );
@@ -162,20 +149,24 @@ class PocketImport extends AbstractImport
162 149
163 $entries = $response->json(); 150 $entries = $response->json();
164 151
165 $this->parseEntries($entries['list']); 152 if ($this->producer) {
153 $this->parseEntriesForProducer($entries['list']);
154 } else {
155 $this->parseEntries($entries['list']);
156 }
166 157
167 return true; 158 // if we retrieve exactly the amount of items requested it means we can get more
168 } 159 // re-call import and offset item by the amount previous received:
160 // - first call get 5k offset 0
161 // - second call get 5k offset 5k
162 // - and so on
163 if (count($entries['list']) === self::NB_ELEMENTS) {
164 ++$run;
169 165
170 /** 166 return $this->import(self::NB_ELEMENTS * $run);
171 * {@inheritdoc} 167 }
172 */ 168
173 public function getSummary() 169 return true;
174 {
175 return [
176 'skipped' => $this->skippedEntries,
177 'imported' => $this->importedEntries,
178 ];
179 } 170 }
180 171
181 /** 172 /**
@@ -189,77 +180,74 @@ class PocketImport extends AbstractImport
189 } 180 }
190 181
191 /** 182 /**
192 * @see https://getpocket.com/developer/docs/v3/retrieve 183 * {@inheritdoc}
193 * 184 *
194 * @param $entries 185 * @see https://getpocket.com/developer/docs/v3/retrieve
195 */ 186 */
196 private function parseEntries($entries) 187 public function parseEntry(array $importedEntry)
197 { 188 {
198 $i = 1; 189 $url = isset($importedEntry['resolved_url']) && $importedEntry['resolved_url'] != '' ? $importedEntry['resolved_url'] : $importedEntry['given_url'];
199 190
200 foreach ($entries as $pocketEntry) { 191 $existingEntry = $this->em
201 $url = isset($pocketEntry['resolved_url']) && $pocketEntry['resolved_url'] != '' ? $pocketEntry['resolved_url'] : $pocketEntry['given_url']; 192 ->getRepository('WallabagCoreBundle:Entry')
202 193 ->findByUrlAndUserId($url, $this->user->getId());
203 $existingEntry = $this->em 194
204 ->getRepository('WallabagCoreBundle:Entry') 195 if (false !== $existingEntry) {
205 ->findByUrlAndUserId($url, $this->user->getId()); 196 ++$this->skippedEntries;
206 197
207 if (false !== $existingEntry) { 198 return;
208 ++$this->skippedEntries; 199 }
209 continue; 200
210 } 201 $entry = new Entry($this->user);
211 202 $entry->setUrl($url);
212 $entry = new Entry($this->user); 203
213 $entry = $this->fetchContent($entry, $url); 204 // update entry with content (in case fetching failed, the given entry will be return)
214 205 $entry = $this->fetchContent($entry, $url);
215 // jump to next entry in case of problem while getting content 206
216 if (false === $entry) { 207 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted
217 ++$this->skippedEntries; 208 $entry->setArchived($importedEntry['status'] == 1 || $this->markAsRead);
218 continue; 209
219 } 210 // 0 or 1 - 1 If the item is starred
220 211 $entry->setStarred($importedEntry['favorite'] == 1);
221 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted 212
222 if ($pocketEntry['status'] == 1 || $this->markAsRead) { 213 $title = 'Untitled';
223 $entry->setArchived(true); 214 if (isset($importedEntry['resolved_title']) && $importedEntry['resolved_title'] != '') {
224 } 215 $title = $importedEntry['resolved_title'];
225 216 } elseif (isset($importedEntry['given_title']) && $importedEntry['given_title'] != '') {
226 // 0 or 1 - 1 If the item is starred 217 $title = $importedEntry['given_title'];
227 if ($pocketEntry['favorite'] == 1) { 218 }
228 $entry->setStarred(true); 219
229 } 220 $entry->setTitle($title);
230 221
231 $title = 'Untitled'; 222 // 0, 1, or 2 - 1 if the item has images in it - 2 if the item is an image
232 if (isset($pocketEntry['resolved_title']) && $pocketEntry['resolved_title'] != '') { 223 if (isset($importedEntry['has_image']) && $importedEntry['has_image'] > 0 && isset($importedEntry['images'][1])) {
233 $title = $pocketEntry['resolved_title']; 224 $entry->setPreviewPicture($importedEntry['images'][1]['src']);
234 } elseif (isset($pocketEntry['given_title']) && $pocketEntry['given_title'] != '') { 225 }
235 $title = $pocketEntry['given_title']; 226
236 } 227 if (isset($importedEntry['tags']) && !empty($importedEntry['tags'])) {
237 228 $this->contentProxy->assignTagsToEntry(
238 $entry->setTitle($title); 229 $entry,
239 230 array_keys($importedEntry['tags'])
240 // 0, 1, or 2 - 1 if the item has images in it - 2 if the item is an image 231 );
241 if (isset($pocketEntry['has_image']) && $pocketEntry['has_image'] > 0 && isset($pocketEntry['images'][1])) {
242 $entry->setPreviewPicture($pocketEntry['images'][1]['src']);
243 }
244
245 if (isset($pocketEntry['tags']) && !empty($pocketEntry['tags'])) {
246 $this->contentProxy->assignTagsToEntry(
247 $entry,
248 array_keys($pocketEntry['tags'])
249 );
250 }
251
252 $this->em->persist($entry);
253 ++$this->importedEntries;
254
255 // flush every 20 entries
256 if (($i % 20) === 0) {
257 $this->em->flush();
258 }
259 ++$i;
260 } 232 }
261 233
262 $this->em->flush(); 234 if (!empty($importedEntry['time_added'])) {
263 $this->em->clear(); 235 $entry->setCreatedAt((new \DateTime())->setTimestamp($importedEntry['time_added']));
236 }
237
238 $this->em->persist($entry);
239 ++$this->importedEntries;
240
241 return $entry;
242 }
243
244 /**
245 * {@inheritdoc}
246 */
247 protected function setEntryAsRead(array $importedEntry)
248 {
249 $importedEntry['status'] = '1';
250
251 return $importedEntry;
264 } 252 }
265} 253}
diff --git a/src/Wallabag/ImportBundle/Import/ReadabilityImport.php b/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
index c7cfe15d..fa2b7053 100644
--- a/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
+++ b/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
@@ -3,28 +3,10 @@
3namespace Wallabag\ImportBundle\Import; 3namespace Wallabag\ImportBundle\Import;
4 4
5use Wallabag\CoreBundle\Entity\Entry; 5use Wallabag\CoreBundle\Entity\Entry;
6use Wallabag\UserBundle\Entity\User;
7 6
8class ReadabilityImport extends AbstractImport 7class ReadabilityImport extends AbstractImport
9{ 8{
10 private $user;
11 private $skippedEntries = 0;
12 private $importedEntries = 0;
13 private $filepath; 9 private $filepath;
14 private $markAsRead;
15
16 /**
17 * We define the user in a custom call because on the import command there is no logged in user.
18 * So we can't retrieve user from the `security.token_storage` service.
19 *
20 * @param User $user
21 */
22 public function setUser(User $user)
23 {
24 $this->user = $user;
25
26 return $this;
27 }
28 10
29 /** 11 /**
30 * {@inheritdoc} 12 * {@inheritdoc}
@@ -63,37 +45,6 @@ class ReadabilityImport extends AbstractImport
63 } 45 }
64 46
65 /** 47 /**
66 * Set whether articles must be all marked as read.
67 *
68 * @param bool $markAsRead
69 */
70 public function setMarkAsRead($markAsRead)
71 {
72 $this->markAsRead = $markAsRead;
73
74 return $this;
75 }
76
77 /**
78 * Get whether articles must be all marked as read.
79 */
80 public function getMarkAsRead()
81 {
82 return $this->markAsRead;
83 }
84
85 /**
86 * {@inheritdoc}
87 */
88 public function getSummary()
89 {
90 return [
91 'skipped' => $this->skippedEntries,
92 'imported' => $this->importedEntries,
93 ];
94 }
95
96 /**
97 * {@inheritdoc} 48 * {@inheritdoc}
98 */ 49 */
99 public function import() 50 public function import()
@@ -116,64 +67,66 @@ class ReadabilityImport extends AbstractImport
116 return false; 67 return false;
117 } 68 }
118 69
70 if ($this->producer) {
71 $this->parseEntriesForProducer($data['bookmarks']);
72
73 return true;
74 }
75
119 $this->parseEntries($data['bookmarks']); 76 $this->parseEntries($data['bookmarks']);
120 77
121 return true; 78 return true;
122 } 79 }
123 80
124 /** 81 /**
125 * Parse and insert all given entries. 82 * {@inheritdoc}
126 *
127 * @param $entries
128 */ 83 */
129 protected function parseEntries($entries) 84 public function parseEntry(array $importedEntry)
130 { 85 {
131 $i = 1; 86 $existingEntry = $this->em
132 87 ->getRepository('WallabagCoreBundle:Entry')
133 foreach ($entries as $importedEntry) { 88 ->findByUrlAndUserId($importedEntry['article__url'], $this->user->getId());
134 $existingEntry = $this->em 89
135 ->getRepository('WallabagCoreBundle:Entry') 90 if (false !== $existingEntry) {
136 ->findByUrlAndUserId($importedEntry['article__url'], $this->user->getId()); 91 ++$this->skippedEntries;
137 92
138 if (false !== $existingEntry) { 93 return;
139 ++$this->skippedEntries;
140 continue;
141 }
142
143 $data = [
144 'title' => $importedEntry['article__title'],
145 'url' => $importedEntry['article__url'],
146 'content_type' => '',
147 'language' => '',
148 'is_archived' => $importedEntry['archive'] || $this->markAsRead,
149 'is_starred' => $importedEntry['favorite'],
150 ];
151
152 $entry = $this->fetchContent(
153 new Entry($this->user),
154 $data['url'],
155 $data
156 );
157
158 // jump to next entry in case of problem while getting content
159 if (false === $entry) {
160 ++$this->skippedEntries;
161 continue;
162 }
163 $entry->setArchived($data['is_archived']);
164 $entry->setStarred($data['is_starred']);
165
166 $this->em->persist($entry);
167 ++$this->importedEntries;
168
169 // flush every 20 entries
170 if (($i % 20) === 0) {
171 $this->em->flush();
172 }
173 ++$i;
174 } 94 }
175 95
176 $this->em->flush(); 96 $data = [
177 $this->em->clear(); 97 'title' => $importedEntry['article__title'],
98 'url' => $importedEntry['article__url'],
99 'content_type' => '',
100 'language' => '',
101 'is_archived' => $importedEntry['archive'] || $this->markAsRead,
102 'is_starred' => $importedEntry['favorite'],
103 'created_at' => $importedEntry['date_added'],
104 ];
105
106 $entry = new Entry($this->user);
107 $entry->setUrl($data['url']);
108 $entry->setTitle($data['title']);
109
110 // update entry with content (in case fetching failed, the given entry will be return)
111 $entry = $this->fetchContent($entry, $data['url'], $data);
112
113 $entry->setArchived($data['is_archived']);
114 $entry->setStarred($data['is_starred']);
115 $entry->setCreatedAt(new \DateTime($data['created_at']));
116
117 $this->em->persist($entry);
118 ++$this->importedEntries;
119
120 return $entry;
121 }
122
123 /**
124 * {@inheritdoc}
125 */
126 protected function setEntryAsRead(array $importedEntry)
127 {
128 $importedEntry['archive'] = 1;
129
130 return $importedEntry;
178 } 131 }
179} 132}
diff --git a/src/Wallabag/ImportBundle/Import/WallabagImport.php b/src/Wallabag/ImportBundle/Import/WallabagImport.php
index 581ec178..043bb0a2 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagImport.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagImport.php
@@ -3,15 +3,10 @@
3namespace Wallabag\ImportBundle\Import; 3namespace Wallabag\ImportBundle\Import;
4 4
5use Wallabag\CoreBundle\Entity\Entry; 5use Wallabag\CoreBundle\Entity\Entry;
6use Wallabag\UserBundle\Entity\User;
7 6
8abstract class WallabagImport extends AbstractImport 7abstract class WallabagImport extends AbstractImport
9{ 8{
10 protected $user;
11 protected $skippedEntries = 0;
12 protected $importedEntries = 0;
13 protected $filepath; 9 protected $filepath;
14 protected $markAsRead;
15 // untitled in all languages from v1 10 // untitled in all languages from v1
16 protected $untitled = [ 11 protected $untitled = [
17 'Untitled', 12 'Untitled',
@@ -29,19 +24,6 @@ abstract class WallabagImport extends AbstractImport
29 ]; 24 ];
30 25
31 /** 26 /**
32 * We define the user in a custom call because on the import command there is no logged in user.
33 * So we can't retrieve user from the `security.token_storage` service.
34 *
35 * @param User $user
36 */
37 public function setUser(User $user)
38 {
39 $this->user = $user;
40
41 return $this;
42 }
43
44 /**
45 * {@inheritdoc} 27 * {@inheritdoc}
46 */ 28 */
47 abstract public function getName(); 29 abstract public function getName();
@@ -79,23 +61,18 @@ abstract class WallabagImport extends AbstractImport
79 return false; 61 return false;
80 } 62 }
81 63
64 if ($this->producer) {
65 $this->parseEntriesForProducer($data);
66
67 return true;
68 }
69
82 $this->parseEntries($data); 70 $this->parseEntries($data);
83 71
84 return true; 72 return true;
85 } 73 }
86 74
87 /** 75 /**
88 * {@inheritdoc}
89 */
90 public function getSummary()
91 {
92 return [
93 'skipped' => $this->skippedEntries,
94 'imported' => $this->importedEntries,
95 ];
96 }
97
98 /**
99 * Set file path to the json file. 76 * Set file path to the json file.
100 * 77 *
101 * @param string $filepath 78 * @param string $filepath
@@ -108,85 +85,59 @@ abstract class WallabagImport extends AbstractImport
108 } 85 }
109 86
110 /** 87 /**
111 * Set whether articles must be all marked as read. 88 * {@inheritdoc}
112 *
113 * @param bool $markAsRead
114 */ 89 */
115 public function setMarkAsRead($markAsRead) 90 public function parseEntry(array $importedEntry)
116 { 91 {
117 $this->markAsRead = $markAsRead; 92 $existingEntry = $this->em
93 ->getRepository('WallabagCoreBundle:Entry')
94 ->findByUrlAndUserId($importedEntry['url'], $this->user->getId());
118 95
119 return $this; 96 if (false !== $existingEntry) {
120 } 97 ++$this->skippedEntries;
121 98
122 /** 99 return;
123 * Parse and insert all given entries. 100 }
124 *
125 * @param $entries
126 */
127 protected function parseEntries($entries)
128 {
129 $i = 1;
130 101
131 foreach ($entries as $importedEntry) { 102 $data = $this->prepareEntry($importedEntry);
132 $existingEntry = $this->em
133 ->getRepository('WallabagCoreBundle:Entry')
134 ->findByUrlAndUserId($importedEntry['url'], $this->user->getId());
135 103
136 if (false !== $existingEntry) { 104 $entry = new Entry($this->user);
137 ++$this->skippedEntries; 105 $entry->setUrl($data['url']);
138 continue; 106 $entry->setTitle($data['title']);
139 }
140 107
141 $data = $this->prepareEntry($importedEntry, $this->markAsRead); 108 // update entry with content (in case fetching failed, the given entry will be return)
109 $entry = $this->fetchContent($entry, $data['url'], $data);
142 110
143 $entry = $this->fetchContent( 111 if (array_key_exists('tags', $data)) {
144 new Entry($this->user), 112 $this->contentProxy->assignTagsToEntry(
145 $importedEntry['url'], 113 $entry,
146 $data 114 $data['tags']
147 ); 115 );
116 }
148 117
149 // jump to next entry in case of problem while getting content 118 if (isset($importedEntry['preview_picture'])) {
150 if (false === $entry) { 119 $entry->setPreviewPicture($importedEntry['preview_picture']);
151 ++$this->skippedEntries;
152 continue;
153 }
154
155 if (array_key_exists('tags', $data)) {
156 $this->contentProxy->assignTagsToEntry(
157 $entry,
158 $data['tags']
159 );
160 }
161
162 if (isset($importedEntry['preview_picture'])) {
163 $entry->setPreviewPicture($importedEntry['preview_picture']);
164 }
165
166 $entry->setArchived($data['is_archived']);
167 $entry->setStarred($data['is_starred']);
168
169 $this->em->persist($entry);
170 ++$this->importedEntries;
171
172 // flush every 20 entries
173 if (($i % 20) === 0) {
174 $this->em->flush();
175 }
176 ++$i;
177 } 120 }
178 121
179 $this->em->flush(); 122 $entry->setArchived($data['is_archived']);
180 $this->em->clear(); 123 $entry->setStarred($data['is_starred']);
124
125 if (!empty($data['created_at'])) {
126 $entry->setCreatedAt(new \DateTime($data['created_at']));
127 }
128
129 $this->em->persist($entry);
130 ++$this->importedEntries;
131
132 return $entry;
181 } 133 }
182 134
183 /** 135 /**
184 * This should return a cleaned array for a given entry to be given to `updateEntry`. 136 * This should return a cleaned array for a given entry to be given to `updateEntry`.
185 * 137 *
186 * @param array $entry Data from the imported file 138 * @param array $entry Data from the imported file
187 * @param bool $markAsRead Should we mark as read content?
188 * 139 *
189 * @return array 140 * @return array
190 */ 141 */
191 abstract protected function prepareEntry($entry = [], $markAsRead = false); 142 abstract protected function prepareEntry($entry = []);
192} 143}
diff --git a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
index 6cf3467a..4f001062 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
@@ -31,7 +31,7 @@ class WallabagV1Import extends WallabagImport
31 /** 31 /**
32 * {@inheritdoc} 32 * {@inheritdoc}
33 */ 33 */
34 protected function prepareEntry($entry = [], $markAsRead = false) 34 protected function prepareEntry($entry = [])
35 { 35 {
36 $data = [ 36 $data = [
37 'title' => $entry['title'], 37 'title' => $entry['title'],
@@ -39,9 +39,10 @@ class WallabagV1Import extends WallabagImport
39 'url' => $entry['url'], 39 'url' => $entry['url'],
40 'content_type' => '', 40 'content_type' => '',
41 'language' => '', 41 'language' => '',
42 'is_archived' => $entry['is_read'] || $markAsRead, 42 'is_archived' => $entry['is_read'] || $this->markAsRead,
43 'is_starred' => $entry['is_fav'], 43 'is_starred' => $entry['is_fav'],
44 'tags' => '', 44 'tags' => '',
45 'created_at' => '',
45 ]; 46 ];
46 47
47 // force content to be refreshed in case on bad fetch in the v1 installation 48 // force content to be refreshed in case on bad fetch in the v1 installation
@@ -56,4 +57,14 @@ class WallabagV1Import extends WallabagImport
56 57
57 return $data; 58 return $data;
58 } 59 }
60
61 /**
62 * {@inheritdoc}
63 */
64 protected function setEntryAsRead(array $importedEntry)
65 {
66 $importedEntry['is_read'] = 1;
67
68 return $importedEntry;
69 }
59} 70}
diff --git a/src/Wallabag/ImportBundle/Import/WallabagV2Import.php b/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
index d0035b63..37c8ca14 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
@@ -31,12 +31,22 @@ class WallabagV2Import extends WallabagImport
31 /** 31 /**
32 * {@inheritdoc} 32 * {@inheritdoc}
33 */ 33 */
34 protected function prepareEntry($entry = [], $markAsRead = false) 34 protected function prepareEntry($entry = [])
35 { 35 {
36 return [ 36 return [
37 'html' => $entry['content'], 37 'html' => $entry['content'],
38 'content_type' => $entry['mimetype'], 38 'content_type' => $entry['mimetype'],
39 'is_archived' => ($entry['is_archived'] || $markAsRead), 39 'is_archived' => ($entry['is_archived'] || $this->markAsRead),
40 ] + $entry; 40 ] + $entry;
41 } 41 }
42
43 /**
44 * {@inheritdoc}
45 */
46 protected function setEntryAsRead(array $importedEntry)
47 {
48 $importedEntry['is_archived'] = 1;
49
50 return $importedEntry;
51 }
42} 52}
diff --git a/src/Wallabag/ImportBundle/Redis/Producer.php b/src/Wallabag/ImportBundle/Redis/Producer.php
new file mode 100644
index 00000000..fedc3e57
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Redis/Producer.php
@@ -0,0 +1,36 @@
1<?php
2
3namespace Wallabag\ImportBundle\Redis;
4
5use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
6use Simpleue\Queue\RedisQueue;
7
8/**
9 * This is a proxy class for "Simpleue\Queue\RedisQueue".
10 * It allow us to use the same way to publish a message between RabbitMQ & Redis: publish().
11 *
12 * It implements the ProducerInterface of RabbitMQ (yes it's ugly) so we can have the same
13 * kind of class which implements the same interface.
14 * So we can inject either a RabbitMQ producer or a Redis producer with the same signature
15 */
16class Producer implements ProducerInterface
17{
18 private $queue;
19
20 public function __construct(RedisQueue $queue)
21 {
22 $this->queue = $queue;
23 }
24
25 /**
26 * Publish a message in the Redis queue.
27 *
28 * @param string $msgBody
29 * @param string $routingKey NOT USED
30 * @param array $additionalProperties NOT USED
31 */
32 public function publish($msgBody, $routingKey = '', $additionalProperties = array())
33 {
34 $this->queue->sendJob($msgBody);
35 }
36}
diff --git a/src/Wallabag/ImportBundle/Resources/config/rabbit.yml b/src/Wallabag/ImportBundle/Resources/config/rabbit.yml
new file mode 100644
index 00000000..aa049749
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Resources/config/rabbit.yml
@@ -0,0 +1,30 @@
1# RabbitMQ stuff
2services:
3 wallabag_import.consumer.amqp.pocket:
4 class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
5 arguments:
6 - "@doctrine.orm.entity_manager"
7 - "@wallabag_user.user_repository"
8 - "@wallabag_import.pocket.import"
9 - "@logger"
10 wallabag_import.consumer.amqp.readability:
11 class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
12 arguments:
13 - "@doctrine.orm.entity_manager"
14 - "@wallabag_user.user_repository"
15 - "@wallabag_import.readability.import"
16 - "@logger"
17 wallabag_import.consumer.amqp.wallabag_v1:
18 class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
19 arguments:
20 - "@doctrine.orm.entity_manager"
21 - "@wallabag_user.user_repository"
22 - "@wallabag_import.wallabag_v1.import"
23 - "@logger"
24 wallabag_import.consumer.amqp.wallabag_v2:
25 class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
26 arguments:
27 - "@doctrine.orm.entity_manager"
28 - "@wallabag_user.user_repository"
29 - "@wallabag_import.wallabag_v2.import"
30 - "@logger"
diff --git a/src/Wallabag/ImportBundle/Resources/config/redis.yml b/src/Wallabag/ImportBundle/Resources/config/redis.yml
new file mode 100644
index 00000000..7d3248e5
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Resources/config/redis.yml
@@ -0,0 +1,81 @@
1# Redis stuff
2services:
3 # readability
4 wallabag_import.queue.redis.readability:
5 class: Simpleue\Queue\RedisQueue
6 arguments:
7 - "@wallabag_core.redis.client"
8 - "wallabag.import.readability"
9
10 wallabag_import.producer.redis.readability:
11 class: Wallabag\ImportBundle\Redis\Producer
12 arguments:
13 - "@wallabag_import.queue.redis.readability"
14
15 wallabag_import.consumer.redis.readability:
16 class: Wallabag\ImportBundle\Consumer\RedisEntryConsumer
17 arguments:
18 - "@doctrine.orm.entity_manager"
19 - "@wallabag_user.user_repository"
20 - "@wallabag_import.readability.import"
21 - "@logger"
22
23 # pocket
24 wallabag_import.queue.redis.pocket:
25 class: Simpleue\Queue\RedisQueue
26 arguments:
27 - "@wallabag_core.redis.client"
28 - "wallabag.import.pocket"
29
30 wallabag_import.producer.redis.pocket:
31 class: Wallabag\ImportBundle\Redis\Producer
32 arguments:
33 - "@wallabag_import.queue.redis.pocket"
34
35 wallabag_import.consumer.redis.pocket:
36 class: Wallabag\ImportBundle\Consumer\RedisEntryConsumer
37 arguments:
38 - "@doctrine.orm.entity_manager"
39 - "@wallabag_user.user_repository"
40 - "@wallabag_import.pocket.import"
41 - "@logger"
42
43 # wallabag v1
44 wallabag_import.queue.redis.wallabag_v1:
45 class: Simpleue\Queue\RedisQueue
46 arguments:
47 - "@wallabag_core.redis.client"
48 - "wallabag.import.wallabag_v1"
49
50 wallabag_import.producer.redis.wallabag_v1:
51 class: Wallabag\ImportBundle\Redis\Producer
52 arguments:
53 - "@wallabag_import.queue.redis.wallabag_v1"
54
55 wallabag_import.consumer.redis.wallabag_v1:
56 class: Wallabag\ImportBundle\Consumer\RedisEntryConsumer
57 arguments:
58 - "@doctrine.orm.entity_manager"
59 - "@wallabag_user.user_repository"
60 - "@wallabag_import.wallabag_v1.import"
61 - "@logger"
62
63 # wallabag v2
64 wallabag_import.queue.redis.wallabag_v2:
65 class: Simpleue\Queue\RedisQueue
66 arguments:
67 - "@wallabag_core.redis.client"
68 - "wallabag.import.wallabag_v2"
69
70 wallabag_import.producer.redis.wallabag_v2:
71 class: Wallabag\ImportBundle\Redis\Producer
72 arguments:
73 - "@wallabag_import.queue.redis.wallabag_v2"
74
75 wallabag_import.consumer.redis.wallabag_v2:
76 class: Wallabag\ImportBundle\Consumer\RedisEntryConsumer
77 arguments:
78 - "@doctrine.orm.entity_manager"
79 - "@wallabag_user.user_repository"
80 - "@wallabag_import.wallabag_v2.import"
81 - "@logger"
diff --git a/src/Wallabag/ImportBundle/Resources/config/services.yml b/src/Wallabag/ImportBundle/Resources/config/services.yml
index 520d43af..f03404ae 100644
--- a/src/Wallabag/ImportBundle/Resources/config/services.yml
+++ b/src/Wallabag/ImportBundle/Resources/config/services.yml
@@ -1,3 +1,7 @@
1imports:
2 - { resource: rabbit.yml }
3 - { resource: redis.yml }
4
1services: 5services:
2 wallabag_import.chain: 6 wallabag_import.chain:
3 class: Wallabag\ImportBundle\Import\ImportChain 7 class: Wallabag\ImportBundle\Import\ImportChain
@@ -14,7 +18,6 @@ services:
14 wallabag_import.pocket.import: 18 wallabag_import.pocket.import:
15 class: Wallabag\ImportBundle\Import\PocketImport 19 class: Wallabag\ImportBundle\Import\PocketImport
16 arguments: 20 arguments:
17 - "@security.token_storage"
18 - "@doctrine.orm.entity_manager" 21 - "@doctrine.orm.entity_manager"
19 - "@wallabag_core.content_proxy" 22 - "@wallabag_core.content_proxy"
20 - "@craue_config" 23 - "@craue_config"
diff --git a/src/Wallabag/ImportBundle/Resources/views/Import/_workerEnabled.html.twig b/src/Wallabag/ImportBundle/Resources/views/Import/_workerEnabled.html.twig
new file mode 100644
index 00000000..2390a41f
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Resources/views/Import/_workerEnabled.html.twig
@@ -0,0 +1,8 @@
1{% set redis = craue_setting('import_with_redis') %}
2{% set rabbit = craue_setting('import_with_rabbitmq') %}
3
4{% if redis or rabbit %}
5 <div class="card-panel yellow darken-1 black-text">
6 {{ 'import.worker.enabled'|trans }} <strong>{% if rabbit %}RabbitMQ{% elseif redis %}Redis{% endif %}</strong>
7 </div>
8{% endif %}
diff --git a/src/Wallabag/ImportBundle/Resources/views/Import/check_queue.html.twig b/src/Wallabag/ImportBundle/Resources/views/Import/check_queue.html.twig
new file mode 100644
index 00000000..7168ea35
--- /dev/null
+++ b/src/Wallabag/ImportBundle/Resources/views/Import/check_queue.html.twig
@@ -0,0 +1,11 @@
1{% if nbRedisMessages > 0 %}
2 <script>
3 Materialize.toast('Messages in queue: {{ nbRedisMessages }}', 4000);
4 </script>
5{% endif %}
6
7{% if nbRabbitMessages > 0 %}
8 <script>
9 Materialize.toast('Messages in queue: {{ nbRabbitMessages }}', 4000);
10 </script>
11{% endif %}
diff --git a/src/Wallabag/ImportBundle/Resources/views/Pocket/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/Pocket/index.html.twig
index 401dbd33..6195fa07 100644
--- a/src/Wallabag/ImportBundle/Resources/views/Pocket/index.html.twig
+++ b/src/Wallabag/ImportBundle/Resources/views/Pocket/index.html.twig
@@ -6,15 +6,13 @@
6<div class="row"> 6<div class="row">
7 <div class="col s12"> 7 <div class="col s12">
8 <div class="card-panel settings"> 8 <div class="card-panel settings">
9 {% include 'WallabagImportBundle:Import:_workerEnabled.html.twig' %}
10
9 {% if not has_consumer_key %} 11 {% if not has_consumer_key %}
10 <div class="card-panel red darken-1"> 12 <div class="card-panel red white-text">
11 {{ 'import.pocket.config_missing.description'|trans }} 13 {{ 'import.pocket.config_missing.description'|trans }}
12 14
13 {% if is_granted('ROLE_SUPER_ADMIN') %} 15 {{ 'import.pocket.config_missing.admin_message'|trans({'%keyurls%': '<a href="' ~ path('config') ~ '">', '%keyurle%':'</a>'})|raw }}
14 {{ 'import.pocket.config_missing.admin_message'|trans({'%keyurls%': '<a href="' ~ path('craue_config_settings_modify') ~ '#set-import">', '%keyurle%':'</a>'})|raw }}
15 {% else %}
16 {{ 'import.pocket.config_missing.user_message'|trans }}
17 {% endif %}
18 </div> 16 </div>
19 {% endif %} 17 {% endif %}
20 18
@@ -29,7 +27,7 @@
29 {{ form_label(form.mark_as_read) }} 27 {{ form_label(form.mark_as_read) }}
30 </div> 28 </div>
31 </div> 29 </div>
32 <button class="btn waves-effect waves-light" type="submit" name="action"> 30 <button class="btn waves-effect waves-light" type="submit" name="action" {% if not has_consumer_key %}disabled="disabled"{% endif %}>
33 {{ 'import.pocket.connect_to_pocket'|trans }} 31 {{ 'import.pocket.connect_to_pocket'|trans }}
34 </button> 32 </button>
35 </form> 33 </form>
diff --git a/src/Wallabag/ImportBundle/Resources/views/Readability/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/Readability/index.html.twig
index f527d309..74653b0f 100644
--- a/src/Wallabag/ImportBundle/Resources/views/Readability/index.html.twig
+++ b/src/Wallabag/ImportBundle/Resources/views/Readability/index.html.twig
@@ -6,6 +6,8 @@
6<div class="row"> 6<div class="row">
7 <div class="col s12"> 7 <div class="col s12">
8 <div class="card-panel settings"> 8 <div class="card-panel settings">
9 {% include 'WallabagImportBundle:Import:_workerEnabled.html.twig' %}
10
9 <div class="row"> 11 <div class="row">
10 <blockquote>{{ import.description|trans }}</blockquote> 12 <blockquote>{{ import.description|trans }}</blockquote>
11 <p>{{ 'import.readability.how_to'|trans }}</p> 13 <p>{{ 'import.readability.how_to'|trans }}</p>
diff --git a/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig b/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig
index 13e24c8c..0b19bc34 100644
--- a/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig
+++ b/src/Wallabag/ImportBundle/Resources/views/WallabagV1/index.html.twig
@@ -6,6 +6,8 @@
6<div class="row"> 6<div class="row">
7 <div class="col s12"> 7 <div class="col s12">
8 <div class="card-panel settings"> 8 <div class="card-panel settings">
9 {% include 'WallabagImportBundle:Import:_workerEnabled.html.twig' %}
10
9 <div class="row"> 11 <div class="row">
10 <blockquote>{{ import.description|trans }}</blockquote> 12 <blockquote>{{ import.description|trans }}</blockquote>
11 <p>{{ 'import.wallabag_v1.how_to'|trans }}</p> 13 <p>{{ 'import.wallabag_v1.how_to'|trans }}</p>
diff --git a/src/Wallabag/UserBundle/Resources/config/services.yml b/src/Wallabag/UserBundle/Resources/config/services.yml
index d79d8fa2..05830555 100644
--- a/src/Wallabag/UserBundle/Resources/config/services.yml
+++ b/src/Wallabag/UserBundle/Resources/config/services.yml
@@ -14,3 +14,9 @@ services:
14 - "@router" 14 - "@router"
15 tags: 15 tags:
16 - { name: kernel.event_subscriber } 16 - { name: kernel.event_subscriber }
17
18 wallabag_user.user_repository:
19 class: Wallabag\UserBundle\Repository\UserRepository
20 factory: [ "@doctrine.orm.default_entity_manager", getRepository ]
21 arguments:
22 - WallabagUserBundle:User
diff --git a/tests/Wallabag/ImportBundle/Command/ImportCommandTest.php b/tests/Wallabag/ImportBundle/Command/ImportCommandTest.php
new file mode 100644
index 00000000..eb7fce79
--- /dev/null
+++ b/tests/Wallabag/ImportBundle/Command/ImportCommandTest.php
@@ -0,0 +1,86 @@
1<?php
2
3namespace Tests\Wallabag\ImportBundle\Command;
4
5use Symfony\Bundle\FrameworkBundle\Console\Application;
6use Symfony\Component\Console\Tester\CommandTester;
7use Wallabag\ImportBundle\Command\ImportCommand;
8use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
9use M6Web\Component\RedisMock\RedisMockFactory;
10
11class ImportCommandTest extends WallabagCoreTestCase
12{
13 /**
14 * @expectedException Symfony\Component\Console\Exception\RuntimeException
15 * @expectedExceptionMessage Not enough arguments
16 */
17 public function testRunImportCommandWithoutArguments()
18 {
19 $application = new Application($this->getClient()->getKernel());
20 $application->add(new ImportCommand());
21
22 $command = $application->find('wallabag:import');
23
24 $tester = new CommandTester($command);
25 $tester->execute([
26 'command' => $command->getName(),
27 ]);
28 }
29
30 /**
31 * @expectedException Symfony\Component\Config\Definition\Exception\Exception
32 * @expectedExceptionMessage not found
33 */
34 public function testRunImportCommandWithoutFilepath()
35 {
36 $application = new Application($this->getClient()->getKernel());
37 $application->add(new ImportCommand());
38
39 $command = $application->find('wallabag:import');
40
41 $tester = new CommandTester($command);
42 $tester->execute([
43 'command' => $command->getName(),
44 'userId' => 1,
45 'filepath' => 1,
46 ]);
47 }
48
49 /**
50 * @expectedException Symfony\Component\Config\Definition\Exception\Exception
51 * @expectedExceptionMessage User with id
52 */
53 public function testRunImportCommandWithoutUserId()
54 {
55 $application = new Application($this->getClient()->getKernel());
56 $application->add(new ImportCommand());
57
58 $command = $application->find('wallabag:import');
59
60 $tester = new CommandTester($command);
61 $tester->execute([
62 'command' => $command->getName(),
63 'userId' => 0,
64 'filepath' => './',
65 ]);
66 }
67
68 public function testRunImportCommand()
69 {
70 $application = new Application($this->getClient()->getKernel());
71 $application->add(new ImportCommand());
72
73 $command = $application->find('wallabag:import');
74
75 $tester = new CommandTester($command);
76 $tester->execute([
77 'command' => $command->getName(),
78 'userId' => 1,
79 'filepath' => $application->getKernel()->getContainer()->getParameter('kernel.root_dir').'/../tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json',
80 '--importer' => 'v2',
81 ]);
82
83 $this->assertContains('imported', $tester->getDisplay());
84 $this->assertContains('already saved', $tester->getDisplay());
85 }
86}
diff --git a/tests/Wallabag/ImportBundle/Command/RedisWorkerCommandTest.php b/tests/Wallabag/ImportBundle/Command/RedisWorkerCommandTest.php
new file mode 100644
index 00000000..74952847
--- /dev/null
+++ b/tests/Wallabag/ImportBundle/Command/RedisWorkerCommandTest.php
@@ -0,0 +1,74 @@
1<?php
2
3namespace Tests\Wallabag\ImportBundle\Command;
4
5use Symfony\Bundle\FrameworkBundle\Console\Application;
6use Symfony\Component\Console\Tester\CommandTester;
7use Wallabag\ImportBundle\Command\RedisWorkerCommand;
8use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
9use M6Web\Component\RedisMock\RedisMockFactory;
10
11class RedisWorkerCommandTest extends WallabagCoreTestCase
12{
13 /**
14 * @expectedException Symfony\Component\Console\Exception\RuntimeException
15 * @expectedExceptionMessage Not enough arguments (missing: "serviceName")
16 */
17 public function testRunRedisWorkerCommandWithoutArguments()
18 {
19 $application = new Application($this->getClient()->getKernel());
20 $application->add(new RedisWorkerCommand());
21
22 $command = $application->find('wallabag:import:redis-worker');
23
24 $tester = new CommandTester($command);
25 $tester->execute([
26 'command' => $command->getName(),
27 ]);
28 }
29
30 /**
31 * @expectedException Symfony\Component\Config\Definition\Exception\Exception
32 * @expectedExceptionMessage No queue or consumer found for service name
33 */
34 public function testRunRedisWorkerCommandWithBadService()
35 {
36 $application = new Application($this->getClient()->getKernel());
37 $application->add(new RedisWorkerCommand());
38
39 $command = $application->find('wallabag:import:redis-worker');
40
41 $tester = new CommandTester($command);
42 $tester->execute([
43 'command' => $command->getName(),
44 'serviceName' => 'YOMONSERVICE',
45 ]);
46 }
47
48 public function testRunRedisWorkerCommand()
49 {
50 $application = new Application($this->getClient()->getKernel());
51 $application->add(new RedisWorkerCommand());
52
53 $factory = new RedisMockFactory();
54 $redisMock = $factory->getAdapter('Predis\Client', true);
55
56 $application->getKernel()->getContainer()->set('wallabag_core.redis.client', $redisMock);
57
58 // put a fake message in the queue so the worker will stop after reading that message
59 // instead of waiting for others
60 $redisMock->lpush('wallabag.import.readability', '{}');
61
62 $command = $application->find('wallabag:import:redis-worker');
63
64 $tester = new CommandTester($command);
65 $tester->execute([
66 'command' => $command->getName(),
67 'serviceName' => 'readability',
68 '--maxIterations' => 1,
69 ]);
70
71 $this->assertContains('Worker started at', $tester->getDisplay());
72 $this->assertContains('Waiting for message', $tester->getDisplay());
73 }
74}
diff --git a/tests/Wallabag/ImportBundle/Consumer/AMQPEntryConsumerTest.php b/tests/Wallabag/ImportBundle/Consumer/AMQPEntryConsumerTest.php
new file mode 100644
index 00000000..a3263771
--- /dev/null
+++ b/tests/Wallabag/ImportBundle/Consumer/AMQPEntryConsumerTest.php
@@ -0,0 +1,225 @@
1<?php
2
3namespace Tests\Wallabag\ImportBundle\Consumer\AMQP;
4
5use Wallabag\ImportBundle\Consumer\AMQPEntryConsumer;
6use PhpAmqpLib\Message\AMQPMessage;
7use Wallabag\UserBundle\Entity\User;
8use Wallabag\CoreBundle\Entity\Entry;
9
10class AMQPEntryConsumerTest extends \PHPUnit_Framework_TestCase
11{
12 public function testMessageOk()
13 {
14 $em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
15 ->disableOriginalConstructor()
16 ->getMock();
17
18 $em
19 ->expects($this->once())
20 ->method('flush');
21
22 $em
23 ->expects($this->exactly(2))
24 ->method('clear');
25
26 $body = <<<'JSON'
27{
28 "item_id": "1402935436",
29 "resolved_id": "1402935436",
30 "given_url": "http://mashable.com/2016/09/04/leslie-jones-back-on-twitter-after-hack/?utm_campaign=Mash-Prod-RSS-Feedburner-All-Partial&utm_cid=Mash-Prod-RSS-Feedburner-All-Partial",
31 "given_title": "Leslie Jones is back on Twitter and her comeback tweet rules",
32 "favorite": "0",
33 "status": "0",
34 "time_added": "1473020899",
35 "time_updated": "1473020899",
36 "time_read": "0",
37 "time_favorited": "0",
38 "sort_id": 0,
39 "resolved_title": "Leslie Jones is back on Twitter and her comeback tweet rules",
40 "resolved_url": "http://mashable.com/2016/09/04/leslie-jones-back-on-twitter-after-hack/?utm_campaign=Mash-Prod-RSS-Feedburner-All-Partial&utm_cid=Mash-Prod-RSS-Feedburner-All-Partial",
41 "excerpt": "Leslie Jones is back to communicating with her adoring public on Twitter after cowardly hacker-trolls drove her away, probably to compensate for their own failings. It all started with a mic drop ...",
42 "is_article": "1",
43 "is_index": "0",
44 "has_video": "0",
45 "has_image": "1",
46 "word_count": "200",
47 "tags": {
48 "ifttt": {
49 "item_id": "1402935436",
50 "tag": "ifttt"
51 },
52 "mashable": {
53 "item_id": "1402935436",
54 "tag": "mashable"
55 }
56 },
57 "authors": {
58 "2484273": {
59 "item_id": "1402935436",
60 "author_id": "2484273",
61 "name": "Adam Rosenberg",
62 "url": "http://mashable.com/author/adam-rosenberg/"
63 }
64 },
65 "image": {
66 "item_id": "1402935436",
67 "src": "http://i.amz.mshcdn.com/i-V5cS6_sDqFABaVR0hVSBJqG_w=/950x534/https%3A%2F%2Fblueprint-api-production.s3.amazonaws.com%2Fuploads%2Fcard%2Fimage%2F199899%2Fleslie_jones_war_dogs.jpg",
68 "width": "0",
69 "height": "0"
70 },
71 "images": {
72 "1": {
73 "item_id": "1402935436",
74 "image_id": "1",
75 "src": "http://i.amz.mshcdn.com/i-V5cS6_sDqFABaVR0hVSBJqG_w=/950x534/https%3A%2F%2Fblueprint-api-production.s3.amazonaws.com%2Fuploads%2Fcard%2Fimage%2F199899%2Fleslie_jones_war_dogs.jpg",
76 "width": "0",
77 "height": "0",
78 "credit": "Image: Steve Eichner/NameFace/Sipa USA",
79 "caption": ""
80 }
81 },
82 "userId": 1
83}
84JSON;
85
86 $user = new User();
87 $entry = new Entry($user);
88
89 $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository')
90 ->disableOriginalConstructor()
91 ->getMock();
92
93 $userRepository
94 ->expects($this->once())
95 ->method('find')
96 // userId from the body json above
97 ->with(1)
98 ->willReturn($user);
99
100 $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport')
101 ->disableOriginalConstructor()
102 ->getMock();
103
104 $import
105 ->expects($this->once())
106 ->method('setUser')
107 ->with($user);
108
109 $import
110 ->expects($this->once())
111 ->method('parseEntry')
112 ->with(json_decode($body, true))
113 ->willReturn($entry);
114
115 $consumer = new AMQPEntryConsumer(
116 $em,
117 $userRepository,
118 $import
119 );
120
121 $message = new AMQPMessage($body);
122
123 $consumer->execute($message);
124 }
125
126 public function testMessageWithBadUser()
127 {
128 $em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
129 ->disableOriginalConstructor()
130 ->getMock();
131
132 $em
133 ->expects($this->never())
134 ->method('flush');
135
136 $em
137 ->expects($this->never())
138 ->method('clear');
139
140 $body = '{ "userId": 123 }';
141
142 $user = new User();
143 $entry = new Entry($user);
144
145 $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository')
146 ->disableOriginalConstructor()
147 ->getMock();
148
149 $userRepository
150 ->expects($this->once())
151 ->method('find')
152 // userId from the body json above
153 ->with(123)
154 ->willReturn(null);
155
156 $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport')
157 ->disableOriginalConstructor()
158 ->getMock();
159
160 $consumer = new AMQPEntryConsumer(
161 $em,
162 $userRepository,
163 $import
164 );
165
166 $message = new AMQPMessage($body);
167
168 $consumer->execute($message);
169 }
170
171 public function testMessageWithEntryProcessed()
172 {
173 $em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
174 ->disableOriginalConstructor()
175 ->getMock();
176
177 $em
178 ->expects($this->never())
179 ->method('flush');
180
181 $em
182 ->expects($this->never())
183 ->method('clear');
184
185 $body = '{ "userId": 123 }';
186
187 $user = new User();
188
189 $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository')
190 ->disableOriginalConstructor()
191 ->getMock();
192
193 $userRepository
194 ->expects($this->once())
195 ->method('find')
196 // userId from the body json above
197 ->with(123)
198 ->willReturn($user);
199
200 $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport')
201 ->disableOriginalConstructor()
202 ->getMock();
203
204 $import
205 ->expects($this->once())
206 ->method('setUser')
207 ->with($user);
208
209 $import
210 ->expects($this->once())
211 ->method('parseEntry')
212 ->with(json_decode($body, true))
213 ->willReturn(null);
214
215 $consumer = new AMQPEntryConsumer(
216 $em,
217 $userRepository,
218 $import
219 );
220
221 $message = new AMQPMessage($body);
222
223 $consumer->execute($message);
224 }
225}
diff --git a/tests/Wallabag/ImportBundle/Consumer/RedisEntryConsumerTest.php b/tests/Wallabag/ImportBundle/Consumer/RedisEntryConsumerTest.php
new file mode 100644
index 00000000..5e8ee41d
--- /dev/null
+++ b/tests/Wallabag/ImportBundle/Consumer/RedisEntryConsumerTest.php
@@ -0,0 +1,225 @@
1<?php
2
3namespace Tests\Wallabag\ImportBundle\Consumer\AMQP;
4
5use Wallabag\ImportBundle\Consumer\RedisEntryConsumer;
6use Wallabag\UserBundle\Entity\User;
7use Wallabag\CoreBundle\Entity\Entry;
8
9class RedisEntryConsumerTest extends \PHPUnit_Framework_TestCase
10{
11 public function testMessageOk()
12 {
13 $em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
14 ->disableOriginalConstructor()
15 ->getMock();
16
17 $em
18 ->expects($this->once())
19 ->method('flush');
20
21 $em
22 ->expects($this->exactly(2))
23 ->method('clear');
24
25 $body = <<<'JSON'
26{
27 "item_id": "1402935436",
28 "resolved_id": "1402935436",
29 "given_url": "http://mashable.com/2016/09/04/leslie-jones-back-on-twitter-after-hack/?utm_campaign=Mash-Prod-RSS-Feedburner-All-Partial&utm_cid=Mash-Prod-RSS-Feedburner-All-Partial",
30 "given_title": "Leslie Jones is back on Twitter and her comeback tweet rules",
31 "favorite": "0",
32 "status": "0",
33 "time_added": "1473020899",
34 "time_updated": "1473020899",
35 "time_read": "0",
36 "time_favorited": "0",
37 "sort_id": 0,
38 "resolved_title": "Leslie Jones is back on Twitter and her comeback tweet rules",
39 "resolved_url": "http://mashable.com/2016/09/04/leslie-jones-back-on-twitter-after-hack/?utm_campaign=Mash-Prod-RSS-Feedburner-All-Partial&utm_cid=Mash-Prod-RSS-Feedburner-All-Partial",
40 "excerpt": "Leslie Jones is back to communicating with her adoring public on Twitter after cowardly hacker-trolls drove her away, probably to compensate for their own failings. It all started with a mic drop ...",
41 "is_article": "1",
42 "is_index": "0",
43 "has_video": "0",
44 "has_image": "1",
45 "word_count": "200",
46 "tags": {
47 "ifttt": {
48 "item_id": "1402935436",
49 "tag": "ifttt"
50 },
51 "mashable": {
52 "item_id": "1402935436",
53 "tag": "mashable"
54 }
55 },
56 "authors": {
57 "2484273": {
58 "item_id": "1402935436",
59 "author_id": "2484273",
60 "name": "Adam Rosenberg",
61 "url": "http://mashable.com/author/adam-rosenberg/"
62 }
63 },
64 "image": {
65 "item_id": "1402935436",
66 "src": "http://i.amz.mshcdn.com/i-V5cS6_sDqFABaVR0hVSBJqG_w=/950x534/https%3A%2F%2Fblueprint-api-production.s3.amazonaws.com%2Fuploads%2Fcard%2Fimage%2F199899%2Fleslie_jones_war_dogs.jpg",
67 "width": "0",
68 "height": "0"
69 },
70 "images": {
71 "1": {
72 "item_id": "1402935436",
73 "image_id": "1",
74 "src": "http://i.amz.mshcdn.com/i-V5cS6_sDqFABaVR0hVSBJqG_w=/950x534/https%3A%2F%2Fblueprint-api-production.s3.amazonaws.com%2Fuploads%2Fcard%2Fimage%2F199899%2Fleslie_jones_war_dogs.jpg",
75 "width": "0",
76 "height": "0",
77 "credit": "Image: Steve Eichner/NameFace/Sipa USA",
78 "caption": ""
79 }
80 },
81 "userId": 1
82}
83JSON;
84
85 $user = new User();
86 $entry = new Entry($user);
87
88 $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository')
89 ->disableOriginalConstructor()
90 ->getMock();
91
92 $userRepository
93 ->expects($this->once())
94 ->method('find')
95 // userId from the body json above
96 ->with(1)
97 ->willReturn($user);
98
99 $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport')
100 ->disableOriginalConstructor()
101 ->getMock();
102
103 $import
104 ->expects($this->once())
105 ->method('setUser')
106 ->with($user);
107
108 $import
109 ->expects($this->once())
110 ->method('parseEntry')
111 ->with(json_decode($body, true))
112 ->willReturn($entry);
113
114 $consumer = new RedisEntryConsumer(
115 $em,
116 $userRepository,
117 $import
118 );
119
120 $res = $consumer->manage($body);
121
122 $this->assertTrue($res);
123 }
124
125 public function testMessageWithBadUser()
126 {
127 $em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
128 ->disableOriginalConstructor()
129 ->getMock();
130
131 $em
132 ->expects($this->never())
133 ->method('flush');
134
135 $em
136 ->expects($this->never())
137 ->method('clear');
138
139 $body = '{ "userId": 123 }';
140
141 $user = new User();
142 $entry = new Entry($user);
143
144 $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository')
145 ->disableOriginalConstructor()
146 ->getMock();
147
148 $userRepository
149 ->expects($this->once())
150 ->method('find')
151 // userId from the body json above
152 ->with(123)
153 ->willReturn(null);
154
155 $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport')
156 ->disableOriginalConstructor()
157 ->getMock();
158
159 $consumer = new RedisEntryConsumer(
160 $em,
161 $userRepository,
162 $import
163 );
164
165 $res = $consumer->manage($body);
166
167 $this->assertFalse($res);
168 }
169
170 public function testMessageWithEntryProcessed()
171 {
172 $em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
173 ->disableOriginalConstructor()
174 ->getMock();
175
176 $em
177 ->expects($this->never())
178 ->method('flush');
179
180 $em
181 ->expects($this->never())
182 ->method('clear');
183
184 $body = '{ "userId": 123 }';
185
186 $user = new User();
187
188 $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository')
189 ->disableOriginalConstructor()
190 ->getMock();
191
192 $userRepository
193 ->expects($this->once())
194 ->method('find')
195 // userId from the body json above
196 ->with(123)
197 ->willReturn($user);
198
199 $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport')
200 ->disableOriginalConstructor()
201 ->getMock();
202
203 $import
204 ->expects($this->once())
205 ->method('setUser')
206 ->with($user);
207
208 $import
209 ->expects($this->once())
210 ->method('parseEntry')
211 ->with(json_decode($body, true))
212 ->willReturn(null);
213
214 $consumer = new RedisEntryConsumer(
215 $em,
216 $userRepository,
217 $import
218 );
219
220 $res = $consumer->manage($body);
221
222 $this->assertFalse($res);
223 $this->assertFalse($consumer->isStopJob($body));
224 }
225}
diff --git a/tests/Wallabag/ImportBundle/Controller/PocketControllerTest.php b/tests/Wallabag/ImportBundle/Controller/PocketControllerTest.php
index e0e61df8..35673261 100644
--- a/tests/Wallabag/ImportBundle/Controller/PocketControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/PocketControllerTest.php
@@ -17,6 +17,36 @@ class PocketControllerTest extends WallabagCoreTestCase
17 $this->assertEquals(1, $crawler->filter('button[type=submit]')->count()); 17 $this->assertEquals(1, $crawler->filter('button[type=submit]')->count());
18 } 18 }
19 19
20 public function testImportPocketWithRabbitEnabled()
21 {
22 $this->logInAs('admin');
23 $client = $this->getClient();
24
25 $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1);
26
27 $crawler = $client->request('GET', '/import/pocket');
28
29 $this->assertEquals(200, $client->getResponse()->getStatusCode());
30 $this->assertEquals(1, $crawler->filter('button[type=submit]')->count());
31
32 $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0);
33 }
34
35 public function testImportPocketWithRedisEnabled()
36 {
37 $this->logInAs('admin');
38 $client = $this->getClient();
39
40 $client->getContainer()->get('craue_config')->set('import_with_redis', 1);
41
42 $crawler = $client->request('GET', '/import/pocket');
43
44 $this->assertEquals(200, $client->getResponse()->getStatusCode());
45 $this->assertEquals(1, $crawler->filter('button[type=submit]')->count());
46
47 $client->getContainer()->get('craue_config')->set('import_with_redis', 0);
48 }
49
20 public function testImportPocketAuthBadToken() 50 public function testImportPocketAuthBadToken()
21 { 51 {
22 $this->logInAs('admin'); 52 $this->logInAs('admin');
diff --git a/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php b/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php
index 92cf4bfc..7b88d891 100644
--- a/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php
@@ -19,6 +19,74 @@ class ReadabilityControllerTest extends WallabagCoreTestCase
19 $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); 19 $this->assertEquals(1, $crawler->filter('input[type=file]')->count());
20 } 20 }
21 21
22 public function testImportReadabilityWithRabbitEnabled()
23 {
24 $this->logInAs('admin');
25 $client = $this->getClient();
26
27 $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1);
28
29 $crawler = $client->request('GET', '/import/readability');
30
31 $this->assertEquals(200, $client->getResponse()->getStatusCode());
32 $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
33 $this->assertEquals(1, $crawler->filter('input[type=file]')->count());
34
35 $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0);
36 }
37
38 public function testImportReadabilityBadFile()
39 {
40 $this->logInAs('admin');
41 $client = $this->getClient();
42
43 $crawler = $client->request('GET', '/import/readability');
44 $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
45
46 $data = [
47 'upload_import_file[file]' => '',
48 ];
49
50 $client->submit($form, $data);
51
52 $this->assertEquals(200, $client->getResponse()->getStatusCode());
53 }
54
55 public function testImportReadabilityWithRedisEnabled()
56 {
57 $this->logInAs('admin');
58 $client = $this->getClient();
59
60 $client->getContainer()->get('craue_config')->set('import_with_redis', 1);
61
62 $crawler = $client->request('GET', '/import/readability');
63
64 $this->assertEquals(200, $client->getResponse()->getStatusCode());
65 $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
66 $this->assertEquals(1, $crawler->filter('input[type=file]')->count());
67
68 $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
69
70 $file = new UploadedFile(__DIR__.'/../fixtures/readability.json', 'readability.json');
71
72 $data = [
73 'upload_import_file[file]' => $file,
74 ];
75
76 $client->submit($form, $data);
77
78 $this->assertEquals(302, $client->getResponse()->getStatusCode());
79
80 $crawler = $client->followRedirect();
81
82 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
83 $this->assertContains('flashes.import.notice.summary', $body[0]);
84
85 $this->assertNotEmpty($client->getContainer()->get('wallabag_core.redis.client')->lpop('wallabag.import.readability'));
86
87 $client->getContainer()->get('craue_config')->set('import_with_redis', 0);
88 }
89
22 public function testImportReadabilityWithFile() 90 public function testImportReadabilityWithFile()
23 { 91 {
24 $this->logInAs('admin'); 92 $this->logInAs('admin');
@@ -49,6 +117,13 @@ class ReadabilityControllerTest extends WallabagCoreTestCase
49 117
50 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); 118 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
51 $this->assertContains('flashes.import.notice.summary', $body[0]); 119 $this->assertContains('flashes.import.notice.summary', $body[0]);
120
121 $this->assertNotEmpty($content->getMimetype());
122 $this->assertNotEmpty($content->getPreviewPicture());
123 $this->assertNotEmpty($content->getLanguage());
124 $this->assertEquals(0, count($content->getTags()));
125 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
126 $this->assertEquals('2016-08-25', $content->getCreatedAt()->format('Y-m-d'));
52 } 127 }
53 128
54 public function testImportReadabilityWithFileAndMarkAllAsRead() 129 public function testImportReadabilityWithFileAndMarkAllAsRead()
diff --git a/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php b/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php
index c1025b41..98e85d45 100644
--- a/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php
@@ -19,6 +19,74 @@ class WallabagV1ControllerTest extends WallabagCoreTestCase
19 $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); 19 $this->assertEquals(1, $crawler->filter('input[type=file]')->count());
20 } 20 }
21 21
22 public function testImportWallabagWithRabbitEnabled()
23 {
24 $this->logInAs('admin');
25 $client = $this->getClient();
26
27 $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1);
28
29 $crawler = $client->request('GET', '/import/wallabag-v1');
30
31 $this->assertEquals(200, $client->getResponse()->getStatusCode());
32 $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
33 $this->assertEquals(1, $crawler->filter('input[type=file]')->count());
34
35 $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0);
36 }
37
38 public function testImportWallabagBadFile()
39 {
40 $this->logInAs('admin');
41 $client = $this->getClient();
42
43 $crawler = $client->request('GET', '/import/wallabag-v1');
44 $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
45
46 $data = [
47 'upload_import_file[file]' => '',
48 ];
49
50 $client->submit($form, $data);
51
52 $this->assertEquals(200, $client->getResponse()->getStatusCode());
53 }
54
55 public function testImportWallabagWithRedisEnabled()
56 {
57 $this->logInAs('admin');
58 $client = $this->getClient();
59
60 $client->getContainer()->get('craue_config')->set('import_with_redis', 1);
61
62 $crawler = $client->request('GET', '/import/wallabag-v1');
63
64 $this->assertEquals(200, $client->getResponse()->getStatusCode());
65 $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
66 $this->assertEquals(1, $crawler->filter('input[type=file]')->count());
67
68 $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
69
70 $file = new UploadedFile(__DIR__.'/../fixtures/wallabag-v1.json', 'wallabag-v1.json');
71
72 $data = [
73 'upload_import_file[file]' => $file,
74 ];
75
76 $client->submit($form, $data);
77
78 $this->assertEquals(302, $client->getResponse()->getStatusCode());
79
80 $crawler = $client->followRedirect();
81
82 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
83 $this->assertContains('flashes.import.notice.summary', $body[0]);
84
85 $this->assertNotEmpty($client->getContainer()->get('wallabag_core.redis.client')->lpop('wallabag.import.wallabag_v1'));
86
87 $client->getContainer()->get('craue_config')->set('import_with_redis', 0);
88 }
89
22 public function testImportWallabagWithFile() 90 public function testImportWallabagWithFile()
23 { 91 {
24 $this->logInAs('admin'); 92 $this->logInAs('admin');
@@ -56,6 +124,12 @@ class WallabagV1ControllerTest extends WallabagCoreTestCase
56 124
57 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); 125 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
58 $this->assertContains('flashes.import.notice.summary', $body[0]); 126 $this->assertContains('flashes.import.notice.summary', $body[0]);
127
128 $this->assertEmpty($content->getMimetype());
129 $this->assertEmpty($content->getPreviewPicture());
130 $this->assertEmpty($content->getLanguage());
131 $this->assertEquals(1, count($content->getTags()));
132 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
59 } 133 }
60 134
61 public function testImportWallabagWithFileAndMarkAllAsRead() 135 public function testImportWallabagWithFileAndMarkAllAsRead()
diff --git a/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php b/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php
index d8d2c8bf..74d61f9a 100644
--- a/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php
+++ b/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php
@@ -19,6 +19,74 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase
19 $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); 19 $this->assertEquals(1, $crawler->filter('input[type=file]')->count());
20 } 20 }
21 21
22 public function testImportWallabagWithRabbitEnabled()
23 {
24 $this->logInAs('admin');
25 $client = $this->getClient();
26
27 $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1);
28
29 $crawler = $client->request('GET', '/import/wallabag-v2');
30
31 $this->assertEquals(200, $client->getResponse()->getStatusCode());
32 $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
33 $this->assertEquals(1, $crawler->filter('input[type=file]')->count());
34
35 $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0);
36 }
37
38 public function testImportWallabagBadFile()
39 {
40 $this->logInAs('admin');
41 $client = $this->getClient();
42
43 $crawler = $client->request('GET', '/import/wallabag-v2');
44 $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
45
46 $data = [
47 'upload_import_file[file]' => '',
48 ];
49
50 $client->submit($form, $data);
51
52 $this->assertEquals(200, $client->getResponse()->getStatusCode());
53 }
54
55 public function testImportWallabagWithRedisEnabled()
56 {
57 $this->logInAs('admin');
58 $client = $this->getClient();
59
60 $client->getContainer()->get('craue_config')->set('import_with_redis', 1);
61
62 $crawler = $client->request('GET', '/import/wallabag-v2');
63
64 $this->assertEquals(200, $client->getResponse()->getStatusCode());
65 $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
66 $this->assertEquals(1, $crawler->filter('input[type=file]')->count());
67
68 $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
69
70 $file = new UploadedFile(__DIR__.'/../fixtures/wallabag-v2.json', 'wallabag-v2.json');
71
72 $data = [
73 'upload_import_file[file]' => $file,
74 ];
75
76 $client->submit($form, $data);
77
78 $this->assertEquals(302, $client->getResponse()->getStatusCode());
79
80 $crawler = $client->followRedirect();
81
82 $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
83 $this->assertContains('flashes.import.notice.summary', $body[0]);
84
85 $this->assertNotEmpty($client->getContainer()->get('wallabag_core.redis.client')->lpop('wallabag.import.wallabag_v2'));
86
87 $client->getContainer()->get('craue_config')->set('import_with_redis', 0);
88 }
89
22 public function testImportWallabagWithFile() 90 public function testImportWallabagWithFile()
23 { 91 {
24 $this->logInAs('admin'); 92 $this->logInAs('admin');
@@ -50,9 +118,9 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase
50 $this->getLoggedInUserId() 118 $this->getLoggedInUserId()
51 ); 119 );
52 120
53 $this->assertEmpty($content->getMimetype()); 121 $this->assertNotEmpty($content->getMimetype());
54 $this->assertEmpty($content->getPreviewPicture()); 122 $this->assertNotEmpty($content->getPreviewPicture());
55 $this->assertEmpty($content->getLanguage()); 123 $this->assertNotEmpty($content->getLanguage());
56 $this->assertEquals(0, count($content->getTags())); 124 $this->assertEquals(0, count($content->getTags()));
57 125
58 $content = $client->getContainer() 126 $content = $client->getContainer()
@@ -67,6 +135,8 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase
67 $this->assertNotEmpty($content->getPreviewPicture()); 135 $this->assertNotEmpty($content->getPreviewPicture());
68 $this->assertNotEmpty($content->getLanguage()); 136 $this->assertNotEmpty($content->getLanguage());
69 $this->assertEquals(2, count($content->getTags())); 137 $this->assertEquals(2, count($content->getTags()));
138 $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
139 $this->assertEquals('2016-09-08', $content->getCreatedAt()->format('Y-m-d'));
70 } 140 }
71 141
72 public function testImportWallabagWithEmptyFile() 142 public function testImportWallabagWithEmptyFile()
diff --git a/tests/Wallabag/ImportBundle/Import/PocketImportTest.php b/tests/Wallabag/ImportBundle/Import/PocketImportTest.php
index 8534e1c8..952521a2 100644
--- a/tests/Wallabag/ImportBundle/Import/PocketImportTest.php
+++ b/tests/Wallabag/ImportBundle/Import/PocketImportTest.php
@@ -4,21 +4,17 @@ namespace Tests\Wallabag\ImportBundle\Import;
4 4
5use Wallabag\UserBundle\Entity\User; 5use Wallabag\UserBundle\Entity\User;
6use Wallabag\CoreBundle\Entity\Entry; 6use Wallabag\CoreBundle\Entity\Entry;
7use Wallabag\CoreBundle\Entity\Config;
7use Wallabag\ImportBundle\Import\PocketImport; 8use Wallabag\ImportBundle\Import\PocketImport;
8use GuzzleHttp\Client; 9use GuzzleHttp\Client;
9use GuzzleHttp\Subscriber\Mock; 10use GuzzleHttp\Subscriber\Mock;
10use GuzzleHttp\Message\Response; 11use GuzzleHttp\Message\Response;
11use GuzzleHttp\Stream\Stream; 12use GuzzleHttp\Stream\Stream;
13use Wallabag\ImportBundle\Redis\Producer;
12use Monolog\Logger; 14use Monolog\Logger;
13use Monolog\Handler\TestHandler; 15use Monolog\Handler\TestHandler;
14 16use Simpleue\Queue\RedisQueue;
15class PocketImportMock extends PocketImport 17use M6Web\Component\RedisMock\RedisMockFactory;
16{
17 public function getAccessToken()
18 {
19 return $this->accessToken;
20 }
21}
22 18
23class PocketImportTest extends \PHPUnit_Framework_TestCase 19class PocketImportTest extends \PHPUnit_Framework_TestCase
24{ 20{
@@ -32,45 +28,24 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
32 { 28 {
33 $this->user = new User(); 29 $this->user = new User();
34 30
35 $this->tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface') 31 $config = new Config($this->user);
36 ->disableOriginalConstructor() 32 $config->setPocketConsumerKey('xxx');
37 ->getMock();
38 33
39 $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface') 34 $this->user->setConfig($config);
40 ->disableOriginalConstructor()
41 ->getMock();
42 35
43 $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') 36 $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy')
44 ->disableOriginalConstructor() 37 ->disableOriginalConstructor()
45 ->getMock(); 38 ->getMock();
46 39
47 $token->expects($this->once())
48 ->method('getUser')
49 ->willReturn($this->user);
50
51 $this->tokenStorage->expects($this->once())
52 ->method('getToken')
53 ->willReturn($token);
54
55 $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager') 40 $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
56 ->disableOriginalConstructor() 41 ->disableOriginalConstructor()
57 ->getMock(); 42 ->getMock();
58 43
59 $config = $this->getMockBuilder('Craue\ConfigBundle\Util\Config') 44 $pocket = new PocketImport(
60 ->disableOriginalConstructor()
61 ->getMock();
62
63 $config->expects($this->any())
64 ->method('get')
65 ->with('pocket_consumer_key')
66 ->willReturn($consumerKey);
67
68 $pocket = new PocketImportMock(
69 $this->tokenStorage,
70 $this->em, 45 $this->em,
71 $this->contentProxy, 46 $this->contentProxy
72 $config
73 ); 47 );
48 $pocket->setUser($this->user);
74 49
75 $this->logHandler = new TestHandler(); 50 $this->logHandler = new TestHandler();
76 $logger = new Logger('test', [$this->logHandler]); 51 $logger = new Logger('test', [$this->logHandler]);
@@ -189,10 +164,16 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
189 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", 164 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland",
190 "favorite": "1", 165 "favorite": "1",
191 "status": "1", 166 "status": "1",
167 "time_added": "1473020899",
168 "time_updated": "1473020899",
169 "time_read": "0",
170 "time_favorited": "0",
171 "sort_id": 0,
192 "resolved_title": "The Massive Ryder Cup Preview", 172 "resolved_title": "The Massive Ryder Cup Preview",
193 "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview", 173 "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
194 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", 174 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.",
195 "is_article": "1", 175 "is_article": "1",
176 "is_index": "0",
196 "has_video": "1", 177 "has_video": "1",
197 "has_image": "1", 178 "has_image": "1",
198 "word_count": "3197", 179 "word_count": "3197",
@@ -236,10 +217,16 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
236 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", 217 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland",
237 "favorite": "1", 218 "favorite": "1",
238 "status": "1", 219 "status": "1",
220 "time_added": "1473020899",
221 "time_updated": "1473020899",
222 "time_read": "0",
223 "time_favorited": "0",
224 "sort_id": 1,
239 "resolved_title": "The Massive Ryder Cup Preview", 225 "resolved_title": "The Massive Ryder Cup Preview",
240 "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview", 226 "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
241 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", 227 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.",
242 "is_article": "1", 228 "is_article": "1",
229 "is_index": "0",
243 "has_video": "0", 230 "has_video": "0",
244 "has_image": "0", 231 "has_image": "0",
245 "word_count": "3197" 232 "word_count": "3197"
@@ -279,7 +266,7 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
279 $res = $pocketImport->import(); 266 $res = $pocketImport->import();
280 267
281 $this->assertTrue($res); 268 $this->assertTrue($res);
282 $this->assertEquals(['skipped' => 1, 'imported' => 1], $pocketImport->getSummary()); 269 $this->assertEquals(['skipped' => 1, 'imported' => 1, 'queued' => 0], $pocketImport->getSummary());
283 } 270 }
284 271
285 /** 272 /**
@@ -302,6 +289,11 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
302 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", 289 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland",
303 "favorite": "1", 290 "favorite": "1",
304 "status": "1", 291 "status": "1",
292 "time_added": "1473020899",
293 "time_updated": "1473020899",
294 "time_read": "0",
295 "time_favorited": "0",
296 "sort_id": 0,
305 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", 297 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.",
306 "is_article": "1", 298 "is_article": "1",
307 "has_video": "1", 299 "has_video": "1",
@@ -315,6 +307,11 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
315 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", 307 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland",
316 "favorite": "1", 308 "favorite": "1",
317 "status": "0", 309 "status": "0",
310 "time_added": "1473020899",
311 "time_updated": "1473020899",
312 "time_read": "0",
313 "time_favorited": "0",
314 "sort_id": 1,
318 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", 315 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.",
319 "is_article": "1", 316 "is_article": "1",
320 "has_video": "0", 317 "has_video": "0",
@@ -364,7 +361,174 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
364 $res = $pocketImport->setMarkAsRead(true)->import(); 361 $res = $pocketImport->setMarkAsRead(true)->import();
365 362
366 $this->assertTrue($res); 363 $this->assertTrue($res);
367 $this->assertEquals(['skipped' => 0, 'imported' => 2], $pocketImport->getSummary()); 364 $this->assertEquals(['skipped' => 0, 'imported' => 2, 'queued' => 0], $pocketImport->getSummary());
365 }
366
367 /**
368 * Will sample results from https://getpocket.com/developer/docs/v3/retrieve.
369 */
370 public function testImportWithRabbit()
371 {
372 $client = new Client();
373
374 $body = <<<'JSON'
375{
376 "item_id": "229279689",
377 "resolved_id": "229279689",
378 "given_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
379 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland",
380 "favorite": "1",
381 "status": "1",
382 "time_added": "1473020899",
383 "time_updated": "1473020899",
384 "time_read": "0",
385 "time_favorited": "0",
386 "sort_id": 0,
387 "resolved_title": "The Massive Ryder Cup Preview",
388 "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
389 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.",
390 "is_article": "1",
391 "has_video": "0",
392 "has_image": "0",
393 "word_count": "3197"
394}
395JSON;
396
397 $mock = new Mock([
398 new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['access_token' => 'wunderbar_token']))),
399 new Response(200, ['Content-Type' => 'application/json'], Stream::factory('
400 {
401 "status": 1,
402 "list": {
403 "229279690": '.$body.'
404 }
405 }
406 ')),
407 ]);
408
409 $client->getEmitter()->attach($mock);
410
411 $pocketImport = $this->getPocketImport();
412
413 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
414 ->disableOriginalConstructor()
415 ->getMock();
416
417 $entryRepo->expects($this->never())
418 ->method('findByUrlAndUserId');
419
420 $this->em
421 ->expects($this->never())
422 ->method('getRepository');
423
424 $entry = new Entry($this->user);
425
426 $this->contentProxy
427 ->expects($this->never())
428 ->method('updateEntry');
429
430 $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer')
431 ->disableOriginalConstructor()
432 ->getMock();
433
434 $bodyAsArray = json_decode($body, true);
435 // because with just use `new User()` so it doesn't have an id
436 $bodyAsArray['userId'] = null;
437
438 $producer
439 ->expects($this->once())
440 ->method('publish')
441 ->with(json_encode($bodyAsArray));
442
443 $pocketImport->setClient($client);
444 $pocketImport->setProducer($producer);
445 $pocketImport->authorize('wunderbar_code');
446
447 $res = $pocketImport->setMarkAsRead(true)->import();
448
449 $this->assertTrue($res);
450 $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 1], $pocketImport->getSummary());
451 }
452
453 /**
454 * Will sample results from https://getpocket.com/developer/docs/v3/retrieve.
455 */
456 public function testImportWithRedis()
457 {
458 $client = new Client();
459
460 $body = <<<'JSON'
461{
462 "item_id": "229279689",
463 "resolved_id": "229279689",
464 "given_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
465 "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland",
466 "favorite": "1",
467 "status": "1",
468 "time_added": "1473020899",
469 "time_updated": "1473020899",
470 "time_read": "0",
471 "time_favorited": "0",
472 "sort_id": 0,
473 "resolved_title": "The Massive Ryder Cup Preview",
474 "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview",
475 "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.",
476 "is_article": "1",
477 "has_video": "0",
478 "has_image": "0",
479 "word_count": "3197"
480}
481JSON;
482
483 $mock = new Mock([
484 new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['access_token' => 'wunderbar_token']))),
485 new Response(200, ['Content-Type' => 'application/json'], Stream::factory('
486 {
487 "status": 1,
488 "list": {
489 "229279690": '.$body.'
490 }
491 }
492 ')),
493 ]);
494
495 $client->getEmitter()->attach($mock);
496
497 $pocketImport = $this->getPocketImport();
498
499 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
500 ->disableOriginalConstructor()
501 ->getMock();
502
503 $entryRepo->expects($this->never())
504 ->method('findByUrlAndUserId');
505
506 $this->em
507 ->expects($this->never())
508 ->method('getRepository');
509
510 $entry = new Entry($this->user);
511
512 $this->contentProxy
513 ->expects($this->never())
514 ->method('updateEntry');
515
516 $factory = new RedisMockFactory();
517 $redisMock = $factory->getAdapter('Predis\Client', true);
518
519 $queue = new RedisQueue($redisMock, 'pocket');
520 $producer = new Producer($queue);
521
522 $pocketImport->setClient($client);
523 $pocketImport->setProducer($producer);
524 $pocketImport->authorize('wunderbar_code');
525
526 $res = $pocketImport->setMarkAsRead(true)->import();
527
528 $this->assertTrue($res);
529 $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 1], $pocketImport->getSummary());
530
531 $this->assertNotEmpty($redisMock->lpop('pocket'));
368 } 532 }
369 533
370 public function testImportBadResponse() 534 public function testImportBadResponse()
@@ -402,6 +566,8 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
402 "status": 1, 566 "status": 1,
403 "list": { 567 "list": {
404 "229279689": { 568 "229279689": {
569 "status": "1",
570 "favorite": "1",
405 "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview" 571 "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview"
406 } 572 }
407 } 573 }
@@ -439,6 +605,6 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase
439 $res = $pocketImport->import(); 605 $res = $pocketImport->import();
440 606
441 $this->assertTrue($res); 607 $this->assertTrue($res);
442 $this->assertEquals(['skipped' => 1, 'imported' => 0], $pocketImport->getSummary()); 608 $this->assertEquals(['skipped' => 0, 'imported' => 1, 'queued' => 0], $pocketImport->getSummary());
443 } 609 }
444} 610}
diff --git a/tests/Wallabag/ImportBundle/Import/ReadabilityImportTest.php b/tests/Wallabag/ImportBundle/Import/ReadabilityImportTest.php
index 706d707b..d98cd486 100644
--- a/tests/Wallabag/ImportBundle/Import/ReadabilityImportTest.php
+++ b/tests/Wallabag/ImportBundle/Import/ReadabilityImportTest.php
@@ -5,8 +5,11 @@ namespace Tests\Wallabag\ImportBundle\Import;
5use Wallabag\ImportBundle\Import\ReadabilityImport; 5use Wallabag\ImportBundle\Import\ReadabilityImport;
6use Wallabag\UserBundle\Entity\User; 6use Wallabag\UserBundle\Entity\User;
7use Wallabag\CoreBundle\Entity\Entry; 7use Wallabag\CoreBundle\Entity\Entry;
8use Wallabag\ImportBundle\Redis\Producer;
8use Monolog\Logger; 9use Monolog\Logger;
9use Monolog\Handler\TestHandler; 10use Monolog\Handler\TestHandler;
11use Simpleue\Queue\RedisQueue;
12use M6Web\Component\RedisMock\RedisMockFactory;
10 13
11class ReadabilityImportTest extends \PHPUnit_Framework_TestCase 14class ReadabilityImportTest extends \PHPUnit_Framework_TestCase
12{ 15{
@@ -58,9 +61,9 @@ class ReadabilityImportTest extends \PHPUnit_Framework_TestCase
58 ->disableOriginalConstructor() 61 ->disableOriginalConstructor()
59 ->getMock(); 62 ->getMock();
60 63
61 $entryRepo->expects($this->exactly(2)) 64 $entryRepo->expects($this->exactly(24))
62 ->method('findByUrlAndUserId') 65 ->method('findByUrlAndUserId')
63 ->will($this->onConsecutiveCalls(false, true)); 66 ->willReturn(false);
64 67
65 $this->em 68 $this->em
66 ->expects($this->any()) 69 ->expects($this->any())
@@ -72,14 +75,14 @@ class ReadabilityImportTest extends \PHPUnit_Framework_TestCase
72 ->getMock(); 75 ->getMock();
73 76
74 $this->contentProxy 77 $this->contentProxy
75 ->expects($this->exactly(1)) 78 ->expects($this->exactly(24))
76 ->method('updateEntry') 79 ->method('updateEntry')
77 ->willReturn($entry); 80 ->willReturn($entry);
78 81
79 $res = $readabilityImport->import(); 82 $res = $readabilityImport->import();
80 83
81 $this->assertTrue($res); 84 $this->assertTrue($res);
82 $this->assertEquals(['skipped' => 1, 'imported' => 1], $readabilityImport->getSummary()); 85 $this->assertEquals(['skipped' => 0, 'imported' => 24, 'queued' => 0], $readabilityImport->getSummary());
83 } 86 }
84 87
85 public function testImportAndMarkAllAsRead() 88 public function testImportAndMarkAllAsRead()
@@ -93,7 +96,7 @@ class ReadabilityImportTest extends \PHPUnit_Framework_TestCase
93 96
94 $entryRepo->expects($this->exactly(2)) 97 $entryRepo->expects($this->exactly(2))
95 ->method('findByUrlAndUserId') 98 ->method('findByUrlAndUserId')
96 ->will($this->onConsecutiveCalls(false, false)); 99 ->will($this->onConsecutiveCalls(false, true));
97 100
98 $this->em 101 $this->em
99 ->expects($this->any()) 102 ->expects($this->any())
@@ -101,7 +104,7 @@ class ReadabilityImportTest extends \PHPUnit_Framework_TestCase
101 ->willReturn($entryRepo); 104 ->willReturn($entryRepo);
102 105
103 $this->contentProxy 106 $this->contentProxy
104 ->expects($this->exactly(2)) 107 ->expects($this->exactly(1))
105 ->method('updateEntry') 108 ->method('updateEntry')
106 ->willReturn(new Entry($this->user)); 109 ->willReturn(new Entry($this->user));
107 110
@@ -117,7 +120,87 @@ class ReadabilityImportTest extends \PHPUnit_Framework_TestCase
117 120
118 $this->assertTrue($res); 121 $this->assertTrue($res);
119 122
120 $this->assertEquals(['skipped' => 0, 'imported' => 2], $readabilityImport->getSummary()); 123 $this->assertEquals(['skipped' => 1, 'imported' => 1, 'queued' => 0], $readabilityImport->getSummary());
124 }
125
126 public function testImportWithRabbit()
127 {
128 $readabilityImport = $this->getReadabilityImport();
129 $readabilityImport->setFilepath(__DIR__.'/../fixtures/readability.json');
130
131 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
132 ->disableOriginalConstructor()
133 ->getMock();
134
135 $entryRepo->expects($this->never())
136 ->method('findByUrlAndUserId');
137
138 $this->em
139 ->expects($this->never())
140 ->method('getRepository');
141
142 $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry')
143 ->disableOriginalConstructor()
144 ->getMock();
145
146 $this->contentProxy
147 ->expects($this->never())
148 ->method('updateEntry');
149
150 $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer')
151 ->disableOriginalConstructor()
152 ->getMock();
153
154 $producer
155 ->expects($this->exactly(24))
156 ->method('publish');
157
158 $readabilityImport->setProducer($producer);
159
160 $res = $readabilityImport->setMarkAsRead(true)->import();
161
162 $this->assertTrue($res);
163 $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 24], $readabilityImport->getSummary());
164 }
165
166 public function testImportWithRedis()
167 {
168 $readabilityImport = $this->getReadabilityImport();
169 $readabilityImport->setFilepath(__DIR__.'/../fixtures/readability.json');
170
171 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
172 ->disableOriginalConstructor()
173 ->getMock();
174
175 $entryRepo->expects($this->never())
176 ->method('findByUrlAndUserId');
177
178 $this->em
179 ->expects($this->never())
180 ->method('getRepository');
181
182 $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry')
183 ->disableOriginalConstructor()
184 ->getMock();
185
186 $this->contentProxy
187 ->expects($this->never())
188 ->method('updateEntry');
189
190 $factory = new RedisMockFactory();
191 $redisMock = $factory->getAdapter('Predis\Client', true);
192
193 $queue = new RedisQueue($redisMock, 'readability');
194 $producer = new Producer($queue);
195
196 $readabilityImport->setProducer($producer);
197
198 $res = $readabilityImport->setMarkAsRead(true)->import();
199
200 $this->assertTrue($res);
201 $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 24], $readabilityImport->getSummary());
202
203 $this->assertNotEmpty($redisMock->lpop('readability'));
121 } 204 }
122 205
123 public function testImportBadFile() 206 public function testImportBadFile()
diff --git a/tests/Wallabag/ImportBundle/Import/WallabagV1ImportTest.php b/tests/Wallabag/ImportBundle/Import/WallabagV1ImportTest.php
index bdc47dac..5ab4ad00 100644
--- a/tests/Wallabag/ImportBundle/Import/WallabagV1ImportTest.php
+++ b/tests/Wallabag/ImportBundle/Import/WallabagV1ImportTest.php
@@ -5,8 +5,11 @@ namespace Tests\Wallabag\ImportBundle\Import;
5use Wallabag\ImportBundle\Import\WallabagV1Import; 5use Wallabag\ImportBundle\Import\WallabagV1Import;
6use Wallabag\UserBundle\Entity\User; 6use Wallabag\UserBundle\Entity\User;
7use Wallabag\CoreBundle\Entity\Entry; 7use Wallabag\CoreBundle\Entity\Entry;
8use Wallabag\ImportBundle\Redis\Producer;
8use Monolog\Logger; 9use Monolog\Logger;
9use Monolog\Handler\TestHandler; 10use Monolog\Handler\TestHandler;
11use Simpleue\Queue\RedisQueue;
12use M6Web\Component\RedisMock\RedisMockFactory;
10 13
11class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase 14class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase
12{ 15{
@@ -79,7 +82,7 @@ class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase
79 $res = $wallabagV1Import->import(); 82 $res = $wallabagV1Import->import();
80 83
81 $this->assertTrue($res); 84 $this->assertTrue($res);
82 $this->assertEquals(['skipped' => 1, 'imported' => 3], $wallabagV1Import->getSummary()); 85 $this->assertEquals(['skipped' => 1, 'imported' => 3, 'queued' => 0], $wallabagV1Import->getSummary());
83 } 86 }
84 87
85 public function testImportAndMarkAllAsRead() 88 public function testImportAndMarkAllAsRead()
@@ -117,7 +120,87 @@ class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase
117 120
118 $this->assertTrue($res); 121 $this->assertTrue($res);
119 122
120 $this->assertEquals(['skipped' => 0, 'imported' => 3], $wallabagV1Import->getSummary()); 123 $this->assertEquals(['skipped' => 0, 'imported' => 3, 'queued' => 0], $wallabagV1Import->getSummary());
124 }
125
126 public function testImportWithRabbit()
127 {
128 $wallabagV1Import = $this->getWallabagV1Import();
129 $wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1.json');
130
131 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
132 ->disableOriginalConstructor()
133 ->getMock();
134
135 $entryRepo->expects($this->never())
136 ->method('findByUrlAndUserId');
137
138 $this->em
139 ->expects($this->never())
140 ->method('getRepository');
141
142 $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry')
143 ->disableOriginalConstructor()
144 ->getMock();
145
146 $this->contentProxy
147 ->expects($this->never())
148 ->method('updateEntry');
149
150 $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer')
151 ->disableOriginalConstructor()
152 ->getMock();
153
154 $producer
155 ->expects($this->exactly(4))
156 ->method('publish');
157
158 $wallabagV1Import->setProducer($producer);
159
160 $res = $wallabagV1Import->setMarkAsRead(true)->import();
161
162 $this->assertTrue($res);
163 $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 4], $wallabagV1Import->getSummary());
164 }
165
166 public function testImportWithRedis()
167 {
168 $wallabagV1Import = $this->getWallabagV1Import();
169 $wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1.json');
170
171 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
172 ->disableOriginalConstructor()
173 ->getMock();
174
175 $entryRepo->expects($this->never())
176 ->method('findByUrlAndUserId');
177
178 $this->em
179 ->expects($this->never())
180 ->method('getRepository');
181
182 $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry')
183 ->disableOriginalConstructor()
184 ->getMock();
185
186 $this->contentProxy
187 ->expects($this->never())
188 ->method('updateEntry');
189
190 $factory = new RedisMockFactory();
191 $redisMock = $factory->getAdapter('Predis\Client', true);
192
193 $queue = new RedisQueue($redisMock, 'wallabag_v1');
194 $producer = new Producer($queue);
195
196 $wallabagV1Import->setProducer($producer);
197
198 $res = $wallabagV1Import->setMarkAsRead(true)->import();
199
200 $this->assertTrue($res);
201 $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 4], $wallabagV1Import->getSummary());
202
203 $this->assertNotEmpty($redisMock->lpop('wallabag_v1'));
121 } 204 }
122 205
123 public function testImportBadFile() 206 public function testImportBadFile()
diff --git a/tests/Wallabag/ImportBundle/Import/WallabagV2ImportTest.php b/tests/Wallabag/ImportBundle/Import/WallabagV2ImportTest.php
index 4a45e0f0..12bd6bdd 100644
--- a/tests/Wallabag/ImportBundle/Import/WallabagV2ImportTest.php
+++ b/tests/Wallabag/ImportBundle/Import/WallabagV2ImportTest.php
@@ -5,8 +5,11 @@ namespace Tests\Wallabag\ImportBundle\Import;
5use Wallabag\ImportBundle\Import\WallabagV2Import; 5use Wallabag\ImportBundle\Import\WallabagV2Import;
6use Wallabag\UserBundle\Entity\User; 6use Wallabag\UserBundle\Entity\User;
7use Wallabag\CoreBundle\Entity\Entry; 7use Wallabag\CoreBundle\Entity\Entry;
8use Wallabag\ImportBundle\Redis\Producer;
8use Monolog\Logger; 9use Monolog\Logger;
9use Monolog\Handler\TestHandler; 10use Monolog\Handler\TestHandler;
11use Simpleue\Queue\RedisQueue;
12use M6Web\Component\RedisMock\RedisMockFactory;
10 13
11class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase 14class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase
12{ 15{
@@ -75,7 +78,7 @@ class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase
75 $res = $wallabagV2Import->import(); 78 $res = $wallabagV2Import->import();
76 79
77 $this->assertTrue($res); 80 $this->assertTrue($res);
78 $this->assertEquals(['skipped' => 22, 'imported' => 2], $wallabagV2Import->getSummary()); 81 $this->assertEquals(['skipped' => 22, 'imported' => 2, 'queued' => 0], $wallabagV2Import->getSummary());
79 } 82 }
80 83
81 public function testImportAndMarkAllAsRead() 84 public function testImportAndMarkAllAsRead()
@@ -113,7 +116,79 @@ class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase
113 116
114 $this->assertTrue($res); 117 $this->assertTrue($res);
115 118
116 $this->assertEquals(['skipped' => 0, 'imported' => 2], $wallabagV2Import->getSummary()); 119 $this->assertEquals(['skipped' => 0, 'imported' => 2, 'queued' => 0], $wallabagV2Import->getSummary());
120 }
121
122 public function testImportWithRabbit()
123 {
124 $wallabagV2Import = $this->getWallabagV2Import();
125 $wallabagV2Import->setFilepath(__DIR__.'/../fixtures/wallabag-v2.json');
126
127 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
128 ->disableOriginalConstructor()
129 ->getMock();
130
131 $entryRepo->expects($this->never())
132 ->method('findByUrlAndUserId');
133
134 $this->em
135 ->expects($this->never())
136 ->method('getRepository');
137
138 $this->contentProxy
139 ->expects($this->never())
140 ->method('updateEntry');
141
142 $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer')
143 ->disableOriginalConstructor()
144 ->getMock();
145
146 $producer
147 ->expects($this->exactly(24))
148 ->method('publish');
149
150 $wallabagV2Import->setProducer($producer);
151
152 $res = $wallabagV2Import->setMarkAsRead(true)->import();
153
154 $this->assertTrue($res);
155 $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 24], $wallabagV2Import->getSummary());
156 }
157
158 public function testImportWithRedis()
159 {
160 $wallabagV2Import = $this->getWallabagV2Import();
161 $wallabagV2Import->setFilepath(__DIR__.'/../fixtures/wallabag-v2.json');
162
163 $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository')
164 ->disableOriginalConstructor()
165 ->getMock();
166
167 $entryRepo->expects($this->never())
168 ->method('findByUrlAndUserId');
169
170 $this->em
171 ->expects($this->never())
172 ->method('getRepository');
173
174 $this->contentProxy
175 ->expects($this->never())
176 ->method('updateEntry');
177
178 $factory = new RedisMockFactory();
179 $redisMock = $factory->getAdapter('Predis\Client', true);
180
181 $queue = new RedisQueue($redisMock, 'wallabag_v2');
182 $producer = new Producer($queue);
183
184 $wallabagV2Import->setProducer($producer);
185
186 $res = $wallabagV2Import->setMarkAsRead(true)->import();
187
188 $this->assertTrue($res);
189 $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 24], $wallabagV2Import->getSummary());
190
191 $this->assertNotEmpty($redisMock->lpop('wallabag_v2'));
117 } 192 }
118 193
119 public function testImportBadFile() 194 public function testImportBadFile()
@@ -152,7 +227,7 @@ class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase
152 $res = $wallabagV2Import->import(); 227 $res = $wallabagV2Import->import();
153 228
154 $this->assertFalse($res); 229 $this->assertFalse($res);
155 $this->assertEquals(['skipped' => 0, 'imported' => 0], $wallabagV2Import->getSummary()); 230 $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 0], $wallabagV2Import->getSummary());
156 } 231 }
157 232
158 public function testImportWithExceptionFromGraby() 233 public function testImportWithExceptionFromGraby()
@@ -181,6 +256,6 @@ class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase
181 $res = $wallabagV2Import->import(); 256 $res = $wallabagV2Import->import();
182 257
183 $this->assertTrue($res); 258 $this->assertTrue($res);
184 $this->assertEquals(['skipped' => 24, 'imported' => 0], $wallabagV2Import->getSummary()); 259 $this->assertEquals(['skipped' => 22, 'imported' => 2, 'queued' => 0], $wallabagV2Import->getSummary());
185 } 260 }
186} 261}
diff --git a/tests/Wallabag/ImportBundle/fixtures/readability.json b/tests/Wallabag/ImportBundle/fixtures/readability.json
index 34379905..32f6fa53 100644
--- a/tests/Wallabag/ImportBundle/fixtures/readability.json
+++ b/tests/Wallabag/ImportBundle/fixtures/readability.json
@@ -11,14 +11,165 @@
11 "archive": false 11 "archive": false
12 }, 12 },
13 { 13 {
14 "article__excerpt": "TL;DR: Re-use your DOM elements and remove the ones that are far away from the viewport. Use placeholders to account for delayed data. Here&#x2019;s a demo and the code for the infinite&hellip;", 14 "article__title": "Réfugiés: l'UE va créer 100 000 places d'accueil dans les Balkans",
15 "favorite": false, 15 "article__url": "http://www.liberation.fr/planete/2015/10/26/refugies-l-ue-va-creer-100-000-places-d-accueil-dans-les-balkans_1408867",
16 "date_archived": "2016-08-26T12:21:54", 16 "archive": false,
17 "article__url": "https://developers.google.com/web/updates/2016/07/infinite-scroller?imm_mid=0e6839&cmp=em-webops-na-na-newsltr_20160805", 17 "date_added": "2016-09-08T11:55:58+0200",
18 "date_added": "2016-08-06T05:35:26", 18 "favorite": false
19 "date_favorited": null, 19 },
20 "article__title": "Complexities of an infinite scroller | Web Updates", 20 {
21 "archive": true 21 "article__title": "No title found",
22 "article__url": "http://news.nationalgeographic.com/2016/02/160211-albatrosses-mothers-babies-animals-science/&sf20739758=1",
23 "archive": false,
24 "date_added": "2016-09-08T11:55:58+0200",
25 "favorite": true
26 },
27 {
28 "archive": 0,
29 "date_added": "2016-09-08T11:55:58+0200",
30 "favorite": 0,
31 "article__title": "Échecs",
32 "article__url": "https://fr.wikipedia.org/wiki/Échecs"
33 },
34 {
35 "archive": 0,
36 "date_added": "2016-09-08T11:55:58+0200",
37 "favorite": 0,
38 "article__title": "90% des dossiers médicaux des Coréens du sud vendus à des entreprises privées - ZATAZ",
39 "article__url": "http://www.zataz.com/90-des-dossiers-medicaux-des-coreens-du-sud-vendus-a-des-entreprises-privees/"
40 },
41 {
42 "archive": 0,
43 "date_added": "2016-09-08T11:55:58+0200",
44 "favorite": 0,
45 "article__title": "Mass Surveillance As Art",
46 "article__url": "https://www.nationaljournal.com/s/73311/mass-surveillance-art"
47 },
48 {
49 "archive": 0,
50 "date_added": "2016-09-08T11:55:58+0200",
51 "favorite": 0,
52 "article__title": "What David Cameron did to the pig, his party is now doing to the country",
53 "article__url": "http://www.newstatesman.com/2015/09/what-david-cameron-did-pig-his-party-now-doing-country"
54 },
55 {
56 "archive": 1,
57 "date_added": "2016-09-08T11:55:58+0200",
58 "favorite": 0,
59 "article__title": "CLICK HERE to support 2016 CES Winner, Revolutionary Auto-Tracking Robot",
60 "article__url": "https://www.indiegogo.com/projects/2016-ces-winner-revolutionary-auto-tracking-robot"
61 },
62 {
63 "archive": 0,
64 "date_added": "2016-09-08T11:55:58+0200",
65 "favorite": 1,
66 "article__title": "No title found",
67 "article__url": "http://carnetdevol.shost.ca/wordpress/aide-memoire-sur-les-commandes-associees-a-systemd/"
68 },
69 {
70 "archive": 1,
71 "date_added": "2016-09-08T11:55:58+0200",
72 "favorite": 0,
73 "article__title": "Présentation d'Arduino - Tuto Arduino - Le blog d'Eskimon",
74 "article__url": "http://eskimon.fr/73-arduino-101-presentation"
75 },
76 {
77 "archive": 1,
78 "date_added": "2016-09-08T11:55:58+0200",
79 "favorite": 0,
80 "article__title": "Lenovo ThinkPad X1 Carbon Ultrabook Review",
81 "article__url": "http://www.notebookcheck.net/Lenovo-ThinkPad-X1-Carbon-Ultrabook-Review.138033.0.html"
82 },
83 {
84 "archive": 0,
85 "date_added": "2016-09-08T11:55:58+0200",
86 "favorite": 0,
87 "article__title": "Visitons le Château de Landsberg !",
88 "article__url": "http://autour-du-mont-sainte-odile.overblog.com/2016/01/visitons-le-chateau-de-landsberg.html"
89 },
90 {
91 "archive": 1,
92 "date_added": "2016-09-08T11:55:58+0200",
93 "favorite": 0,
94 "article__title": "Contrer les stéréotypes par les livres : “C'est dès l'enfance qu'ils se construisent”",
95 "article__url": "https://www.actualitte.com/article/monde-edition/contrer-les-stereotypes-par-les-livres-c-est-des-l-enfance-qu-ils-se-construisent/64058"
96 },
97 {
98 "archive": 1,
99 "date_added": "2016-09-08T11:55:58+0200",
100 "favorite": 0,
101 "article__title": "[ROM][6.0.1][Layers][N5] TipsyOS official builds {UBER TCs}",
102 "article__url": "http://forum.xda-developers.com/google-nexus-5/development/rom-tipsyos-official-builds-uber-tcs-t3325989"
103 },
104 {
105 "archive": 0,
106 "date_added": "2016-09-08T11:55:58+0200",
107 "favorite": 0,
108 "article__title": "Top 15 Podcasts All Web Developers Should Follow - Envato Tuts+ Code Article",
109 "article__url": "http://code.tutsplus.com/articles/top-15-podcasts-all-web-developers-should-follow--net-14461"
110 },
111 {
112 "archive": 1,
113 "date_added": "2016-09-08T11:55:58+0200",
114 "favorite": 0,
115 "article__title": "University of Mississippi",
116 "article__url": "http://olemiss.edu"
117 },
118 {
119 "archive": 1,
120 "date_added": "2016-09-08T11:55:58+0200",
121 "favorite": 0,
122 "article__title": "FinnChristiansen.de Jetzt Dank Let’s Encrypt Per HTTPS Erreichbar",
123 "article__url": "https://www.finnchristiansen.de/2015/12/06/finnchristiansen-de-jetzt-dank-lets-encrypt-per-https-erreichbar/"
124 },
125 {
126 "archive": 1,
127 "date_added": "2016-09-08T11:55:58+0200",
128 "favorite": 0,
129 "article__title": "Le développeur et l'ingénierie logicielle",
130 "article__url": "http://wemucs.com/le-developpeur-et-lingenierie-logicielle/"
131 },
132 {
133 "archive": 1,
134 "date_added": "2016-09-08T11:55:58+0200",
135 "favorite": 0,
136 "article__title": "The Role of Methylation in Gene Expression",
137 "article__url": "http://www.nature.com/scitable/topicpage/the-role-of-methylation-in-gene-expression-1070"
138 },
139 {
140 "archive": 1,
141 "date_added": "2016-09-08T11:55:58+0200",
142 "favorite": 0,
143 "article__title": "E-Mail-Adresse kostenlos, FreeMail, De-Mail & Nachrichten",
144 "article__url": "http://web.de"
145 },
146 {
147 "archive": 1,
148 "date_added": "2016-09-08T11:55:58+0200",
149 "favorite": 0,
150 "article__title": "OpenSSH Server on Arch Linux | DominicM test",
151 "article__url": "http://dominicm.com/openssh-server-arch-linux/"
152 },
153 {
154 "archive": 1,
155 "date_added": "2016-09-08T11:55:58+0200",
156 "favorite": 0,
157 "article__title": "Site Moved | Site Help",
158 "article__url": "http://g1.com/help/sitemoved.asp"
159 },
160 {
161 "archive": 1,
162 "date_added": "2016-09-08T11:55:58+0200",
163 "favorite": 0,
164 "article__title": "#Maroc : le stylo anti-pédophiles EAGLE d’AMESYS est moins bien configuré que les faux-lowers Twitter du roi Mohammed VI",
165 "article__url": "https://reflets.info/maroc-le-stylo-anti-pedophiles-eagle-damesys-est-moins-bien-configure-que-les-faux-lowers-twitter-du-roi-mohammed-vi/"
166 },
167 {
168 "archive": 1,
169 "date_added": "2016-09-08T11:55:58+0200",
170 "favorite": 0,
171 "article__title": "Simple Cloud Infrastructure for Developers",
172 "article__url": "https://www.digitalocean.com/"
22 } 173 }
23 ], 174 ],
24 "recommendations": [] 175 "recommendations": []