Completed
Push — master ( 150a3d...219536 )
by
unknown
04:07
created

FeedService::update()   D

Complexity

Conditions 11
Paths 235

Size

Total Lines 105
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 105
rs 4.0909
cc 11
eloc 59
nc 235
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * ownCloud - News
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Alessandro Cosentino <[email protected]>
9
 * @author Bernhard Posselt <[email protected]>
10
 * @copyright Alessandro Cosentino 2012
11
 * @copyright Bernhard Posselt 2012, 2014
12
 */
13
14
namespace OCA\News\Service;
15
16
use HTMLPurifier;
17
18
use OCP\ILogger;
19
use OCP\IL10N;
20
use OCP\AppFramework\Db\DoesNotExistException;
21
use OCP\AppFramework\Utility\ITimeFactory;
22
23
use OCA\News\Db\Feed;
24
use OCA\News\Db\Item;
25
use OCA\News\Db\FeedMapper;
26
use OCA\News\Db\ItemMapper;
27
use OCA\News\Fetcher\Fetcher;
28
use OCA\News\Fetcher\FetcherException;
29
use OCA\News\Config\Config;
30
31
32
class FeedService extends Service {
33
34
    private $feedFetcher;
35
    private $itemMapper;
36
    private $feedMapper;
37
    private $logger;
38
    private $l10n;
39
    private $timeFactory;
40
    private $autoPurgeMinimumInterval;
41
    private $purifier;
42
    private $loggerParams;
43
44
    public function __construct(FeedMapper $feedMapper,
45
                                Fetcher $feedFetcher,
46
                                ItemMapper $itemMapper,
47
                                ILogger $logger,
48
                                IL10N $l10n,
49
                                ITimeFactory $timeFactory,
50
                                Config $config,
51
                                HTMLPurifier $purifier,
52
                                $LoggerParameters){
53
        parent::__construct($feedMapper);
54
        $this->feedFetcher = $feedFetcher;
55
        $this->itemMapper = $itemMapper;
56
        $this->logger = $logger;
57
        $this->l10n = $l10n;
58
        $this->timeFactory = $timeFactory;
59
        $this->autoPurgeMinimumInterval =
60
            $config->getAutoPurgeMinimumInterval();
61
        $this->purifier = $purifier;
62
        $this->feedMapper = $feedMapper;
63
        $this->loggerParams = $LoggerParameters;
64
    }
65
66
    /**
67
     * Finds all feeds of a user
68
     * @param string $userId the name of the user
69
     * @return Feed[]
70
     */
71
    public function findAll($userId){
72
        return $this->feedMapper->findAllFromUser($userId);
73
    }
74
75
76
    /**
77
     * Finds all feeds from all users
78
     * @return array of feeds
79
     */
80
    public function findAllFromAllUsers() {
81
        return $this->feedMapper->findAll();
82
    }
83
84
85
    /**
86
     * Creates a new feed
87
     * @param string $feedUrl the url to the feed
88
     * @param int $folderId the folder where it should be put into, 0 for root
89
     * folder
90
     * @param string $userId for which user the feed should be created
91
     * @param string $title if given, this is used for the opml feed title
92
     * @param string $basicAuthUser if given, basic auth is set for this feed
93
     * @param string $basicAuthPassword if given, basic auth is set for this
94
     * feed. Ignored if user is null or an empty string
95
     * @throws ServiceConflictException if the feed exists already
96
     * @throws ServiceNotFoundException if the url points to an invalid feed
97
     * @return Feed the newly created feed
98
     */
99
    public function create($feedUrl, $folderId, $userId, $title=null,
100
                           $basicAuthUser=null, $basicAuthPassword=null){
101
        // first try if the feed exists already
102
        try {
103
            list($feed, $items) = $this->feedFetcher->fetch($feedUrl, true,
104
                                  null, null, false, $basicAuthUser,
105
                                  $basicAuthPassword);
106
107
            // try again if feed exists depending on the reported link
108
            try {
109
                $this->feedMapper->findByUrlHash($feed->getUrlHash(), $userId);
110
                throw new ServiceConflictException(
111
                    $this->l10n->t('Can not add feed: Exists already'));
112
113
            // If no matching feed was found everything was ok
114
            } catch(DoesNotExistException $ex){}
115
116
            // insert feed
117
            $itemCount = count($items);
118
            $feed->setBasicAuthUser($basicAuthUser);
119
            $feed->setBasicAuthPassword($basicAuthPassword);
120
            $feed->setFolderId($folderId);
121
            $feed->setUserId($userId);
122
            $feed->setArticlesPerUpdate($itemCount);
123
124
            if ($title !== null && $title !== '') {
125
                $feed->setTitle($title);
126
            }
127
128
            $feed = $this->feedMapper->insert($feed);
129
130
            // insert items in reverse order because the first one is usually
131
            // the newest item
132
            $unreadCount = 0;
133
            for($i=$itemCount-1; $i>=0; $i--){
134
                $item = $items[$i];
135
                $item->setFeedId($feed->getId());
136
137
                // check if item exists (guidhash is the same)
138
                // and ignore it if it does
139
                try {
140
                    $this->itemMapper->findByGuidHash(
141
                        $item->getGuidHash(), $item->getFeedId(), $userId);
142
                    continue;
143
                } catch(DoesNotExistException $ex){
144
                    $unreadCount += 1;
145
                    $item->setBody($this->purifier->purify($item->getBody()));
146
                    $this->itemMapper->insert($item);
147
                }
148
            }
149
150
            // set unread count
151
            $feed->setUnreadCount($unreadCount);
152
153
            return $feed;
154
        } catch(FetcherException $ex){
155
            $this->logger->debug($ex->getMessage(), $this->loggerParams);
156
            throw new ServiceNotFoundException($ex->getMessage());
157
        }
158
    }
159
160
161
    /**
162
     * Runs all the feed updates
163
     */
164
    public function updateAll(){
165
        // TODO: this method is not covered by any tests
166
        $feeds = $this->feedMapper->findAll();
167
        foreach($feeds as $feed){
168
            try {
169
                $this->update($feed->getId(), $feed->getUserId());
170
            } catch(\Exception $ex){
171
                // something is really wrong here, log it
172
                $this->logger->error(
173
                    'Unexpected error when updating feed ' . $ex->getMessage(),
174
                    $this->loggerParams
175
                );
176
            }
177
        }
178
    }
179
180
181
    /**
182
     * Updates a single feed
183
     * @param int $feedId the id of the feed that should be updated
184
     * @param string $userId the id of the user
185
     * @param bool $forceUpdate update even if the article exists already
186
     * @throws ServiceNotFoundException if the feed does not exist
187
     * @return Feed the updated feed entity
188
     */
189
    public function update($feedId, $userId, $forceUpdate=false){
190
        $existingFeed = $this->find($feedId, $userId);
191
192
        if($existingFeed->getPreventUpdate() === true) {
193
            return $existingFeed;
194
        }
195
196
        // for backwards compability it can be that the location is not set
197
        // yet, if so use the url
198
        $location = $existingFeed->getLocation();
199
        if (!$location) {
200
            $location = $existingFeed->getUrl();
201
        }
202
203
        try {
204
            list($fetchedFeed, $items) = $this->feedFetcher->fetch(
205
                $location,
206
                false,
207
                $existingFeed->getHttpLastModified(),
208
                $existingFeed->getHttpEtag(),
209
                $existingFeed->getFullTextEnabled(),
210
                $existingFeed->getBasicAuthUser(),
211
                $existingFeed->getBasicAuthPassword()
212
            );
213
214
            // if there is no feed it means that no update took place
215
            if (!$fetchedFeed) {
216
                return $existingFeed;
217
            }
218
219
            // update number of articles on every feed update
220
            $itemCount = count($items);
221
222
            // this is needed to adjust to updates that add more items
223
            // than when the feed was created. You can't update the count
224
            // if it's lower because it may be due to the caching headers
225
            // that were sent as the request and it might cause unwanted
226
            // deletion and reappearing of feeds
227
            if ($itemCount > $existingFeed->getArticlesPerUpdate()) {
228
                $existingFeed->setArticlesPerUpdate($itemCount);
229
            }
230
231
            $existingFeed->setHttpLastModified(
232
                $fetchedFeed->getHttpLastModified());
233
            $existingFeed->setHttpEtag($fetchedFeed->getHttpEtag());
234
            $existingFeed->setLocation($fetchedFeed->getLocation());
235
236
            // insert items in reverse order because the first one is
237
            // usually the newest item
238
            for($i=$itemCount-1; $i>=0; $i--){
239
                $item = $items[$i];
240
                $item->setFeedId($existingFeed->getId());
241
242
                try {
243
                    $dbItem = $this->itemMapper->findByGuidHash(
244
                        $item->getGuidHash(), $feedId, $userId
245
                    );
246
247
                    // in case of update
248
                    if ($forceUpdate ||
249
                        $item->getPubDate() > $dbItem->getPubDate()) {
250
251
                        $dbItem->setTitle($item->getTitle());
252
                        $dbItem->setUrl($item->getUrl());
253
                        $dbItem->setAuthor($item->getAuthor());
254
                        $dbItem->setSearchIndex($item->getSearchIndex());
255
                        $dbItem->setRtl($item->getRtl());
256
                        $dbItem->setLastModified($item->getLastModified());
257
                        $dbItem->setPubDate($item->getPubDate());
258
                        $dbItem->setEnclosureMime($item->getEnclosureMime());
259
                        $dbItem->setEnclosureLink($item->getEnclosureLink());
260
                        $dbItem->setBody(
261
                            $this->purifier->purify($item->getBody())
262
                        );
263
264
                        // update modes: 0 nothing, 1 set unread
265
                        if ($existingFeed->getUpdateMode() === 1) {
266
                            $dbItem->setUnread();
267
                        }
268
269
                        $this->itemMapper->update($dbItem);
270
                    }
271
                } catch(DoesNotExistException $ex){
272
                    $item->setBody(
273
                        $this->purifier->purify($item->getBody())
274
                    );
275
                    $this->itemMapper->insert($item);
276
                }
277
            }
278
279
            // mark feed as successfully updated
280
            $existingFeed->setUpdateErrorCount(0);
281
            $existingFeed->setLastUpdateError('');
282
283
        } catch(FetcherException $ex){
284
            $existingFeed->setUpdateErrorCount(
285
                $existingFeed->getUpdateErrorCount()+1
286
            );
287
            $existingFeed->setLastUpdateError($ex->getMessage());
288
        }
289
290
        $this->feedMapper->update($existingFeed);
291
292
        return $this->find($feedId, $userId);
293
    }
294
295
    /**
296
     * Import articles
297
     * @param array $json the array with json
298
     * @param string $userId the username
299
     * @return Feed if one had to be created for nonexistent feeds
300
     */
301
    public function importArticles($json, $userId) {
302
        $url = 'http://owncloud/nofeed';
303
        $urlHash = md5($url);
304
305
        // build assoc array for fast access
306
        $feeds = $this->findAll($userId);
307
        $feedsDict = [];
308
        foreach($feeds as $feed) {
309
            $feedsDict[$feed->getLink()] = $feed;
310
        }
311
312
        $createdFeed = false;
313
314
        // loop over all items and get the corresponding feed
315
        // if the feed does not exist, create a separate feed for them
316
        foreach ($json as $entry) {
317
            $item = Item::fromImport($entry);
318
            $item->setLastModified($this->timeFactory->getTime());
319
            $feedLink = $entry['feedLink'];  // this is not set on the item yet
320
321
            if(array_key_exists($feedLink, $feedsDict)) {
322
                $feed = $feedsDict[$feedLink];
323
                $item->setFeedId($feed->getId());
324
            } elseif(array_key_exists($url, $feedsDict)) {
325
                $feed = $feedsDict[$url];
326
                $item->setFeedId($feed->getId());
327
            } else {
328
                $createdFeed = true;
329
                $feed = new Feed();
330
                $feed->setUserId($userId);
331
                $feed->setLink($url);
332
                $feed->setUrl($url);
333
                $feed->setTitle($this->l10n->t('Articles without feed'));
334
                $feed->setAdded($this->timeFactory->getTime());
335
                $feed->setFolderId(0);
336
                $feed->setPreventUpdate(true);
337
                $feed = $this->feedMapper->insert($feed);
338
339
                $item->setFeedId($feed->getId());
340
                $feedsDict[$feed->getLink()] = $feed;
341
            }
342
343
            try {
344
                // if item exists, copy the status
345
                $existingItem = $this->itemMapper->findByGuidHash(
346
                    $item->getGuidHash(), $feed->getId(), $userId);
347
                $existingItem->setStatus($item->getStatus());
348
                $this->itemMapper->update($existingItem);
349
            } catch(DoesNotExistException $ex){
350
                $item->setBody($this->purifier->purify($item->getBody()));
351
                $item->generateSearchIndex();
352
                $this->itemMapper->insert($item);
353
            }
354
        }
355
356
        if($createdFeed) {
357
            return $this->feedMapper->findByUrlHash($urlHash, $userId);
358
        }
359
360
        return null;
361
    }
362
363
364
    /**
365
     * Use this to mark a feed as deleted. That way it can be un-deleted
366
     * @param int $feedId the id of the feed that should be deleted
367
     * @param string $userId the name of the user for security reasons
368
     * @throws ServiceNotFoundException when feed does not exist
369
     */
370
    public function markDeleted($feedId, $userId) {
371
        $feed = $this->find($feedId, $userId);
372
        $feed->setDeletedAt($this->timeFactory->getTime());
373
        $this->feedMapper->update($feed);
374
    }
375
376
377
    /**
378
     * Use this to undo a feed deletion
379
     * @param int $feedId the id of the feed that should be restored
380
     * @param string $userId the name of the user for security reasons
381
     * @throws ServiceNotFoundException when feed does not exist
382
     */
383
    public function unmarkDeleted($feedId, $userId) {
384
        $feed = $this->find($feedId, $userId);
385
        $feed->setDeletedAt(0);
386
        $this->feedMapper->update($feed);
387
    }
388
389
390
    /**
391
     * Deletes all deleted feeds
392
     * @param string $userId if given it purges only feeds of that user
393
     * @param boolean $useInterval defaults to true, if true it only purges
394
     * entries in a given interval to give the user a chance to undo the
395
     * deletion
396
     */
397
    public function purgeDeleted($userId=null, $useInterval=true) {
398
        $deleteOlderThan = null;
399
400
        if ($useInterval) {
401
            $now = $this->timeFactory->getTime();
402
            $deleteOlderThan = $now - $this->autoPurgeMinimumInterval;
403
        }
404
405
        $toDelete = $this->feedMapper->getToDelete($deleteOlderThan, $userId);
406
407
        foreach ($toDelete as $feed) {
408
            $this->feedMapper->delete($feed);
409
        }
410
    }
411
412
413
    /**
414
     * Deletes all feeds of a user, delete items first since the user_id
415
     * is not defined in there
416
     * @param string $userId the name of the user
417
     */
418
    public function deleteUser($userId) {
419
        $this->feedMapper->deleteUser($userId);
420
    }
421
422
    /**
423
     * @param $feedId
424
     * @param $userId
425
     * @param $diff an array containing the fields to update, e.g.:
426
     *  [
427
     *      'ordering' => 1,
428
     *      'fullTextEnabled' => true,
429
     *      'pinned' => true,
430
     *      'updateMode' => 0,
431
     *      'title' => 'title'
432
     * ]
433
     * @throws ServiceNotFoundException if feed does not exist
434
     */
435
    public function patch($feedId, $userId, $diff=[]) {
436
        $feed = $this->find($feedId, $userId);
437
438
        foreach ($diff as $attribute => $value) {
439
            $method = 'set' . ucfirst($attribute);
440
            $feed->$method($value);
441
        }
442
443
        // special feed updates
444
        if (array_key_exists('fullTextEnabled', $diff)) {
445
            // disable caching for the next update
446
            $feed->setHttpEtag('');
447
            $feed->setHttpLastModified(0);
448
            $this->feedMapper->update($feed);
449
            return $this->update($feedId, $userId, true);
450
        }
451
452
        return $this->feedMapper->update($feed);
453
    }
454
455
}
456