mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2026-10-28 14:08:45 -04:00
Contributes to #6139 (export part; the import will follow in another PR) Exporting favourites was collecting the whole file in memory. With many articles, it fails with the default PHP memory limit. ## Changes - New `Minz_View::helperToFile()`, similar to `helperToString()`, writing the output of a helper into a file by chunks of 64 KiB instead of keeping it in memory. - `FreshRSS_Export_Service`: the `generate*()` methods now return the path of a temporary file instead of the content, and `zip()` builds the archive from those files (`ZipArchive::addFile()` instead of `addFromString()`). Temporary files are created in `TMP_PATH` (already required to be writable) and deleted at the end of the script. - The export is sent with `readfile()`: Web UI, `cli/export-zip-for-user.php`, `cli/export-opml-for-user.php` and the OPML export of the Google Reader API. - Note: this changes what `FreshRSS_Export_Service` returns (a path instead of the content). All callers in the repository are updated, and I could not find any public extension using this service. ## Measurements Peak PHP memory (`memory_get_peak_usage(true)`), SQLite database with synthetic starred articles: | Scenario | Before | After | |---|---|---| | Web, export your favourites, 10,000 articles (69 MB JSON) | 136 MB (fails with the default `memory_limit` of 128 MB) | 6 MB | | Web, ZIP (OPML + favourites + labelled articles + 4 feeds), 10,000 articles | 152 MB | 6 MB | | CLI `export-zip-for-user.php`, 10,000 articles | 189 MB (fails with 128 MB) | 8 MB | | Web, export your favourites, 30,000 articles (208 MB JSON) | 404 MB | 10 MB | Co-authored-by: Alejandro Borrego <210245044+abp002@users.noreply.github.com>
161 lines
4.8 KiB
PHP
161 lines
4.8 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* @phpstan-import-type ExtensionFullMetadata from FreshRSS_extension_Controller
|
|
*/
|
|
class FreshRSS_View extends Minz_View {
|
|
|
|
// Main views
|
|
/** @var callable */
|
|
public $callbackBeforeEntries;
|
|
/** @var callable|null */
|
|
public $callbackBeforeFeeds;
|
|
/** @var callable */
|
|
public $callbackBeforePagination;
|
|
/** @var array<int,FreshRSS_Category> where the key is the category ID */
|
|
public array $categories;
|
|
public ?FreshRSS_Category $category = null;
|
|
public ?FreshRSS_Tag $tag = null;
|
|
public string $current_user;
|
|
/** @var iterable<FreshRSS_Entry> */
|
|
public $entries;
|
|
public ?FreshRSS_Entry $entry = null;
|
|
public ?FreshRSS_Feed $feed = null;
|
|
/** @var array<int,FreshRSS_Feed> where the key is the feed ID */
|
|
public array $feeds;
|
|
/**
|
|
* The keys are the feed IDs that have entries matching the current state and search filters (global view).
|
|
* @var array<int,int>|null
|
|
*/
|
|
public ?array $feedIdsMatching = null;
|
|
public int $nbUnreadTags;
|
|
/** @var array<int,FreshRSS_Tag> where the key is the label ID */
|
|
public array $tags;
|
|
/** @var list<array{id:int,name:string,checked:bool}> */
|
|
public array $tagsForEntry;
|
|
/** @var array<string,array<string>> */
|
|
public array $tagsForEntries;
|
|
public bool $excludeMutedFeeds;
|
|
public bool $includeSensitiveCurlParams = false;
|
|
|
|
// Search
|
|
/** @var array<int,FreshRSS_Tag> where the key is the label ID */
|
|
public array $labels;
|
|
|
|
// Subscriptions
|
|
public string $cfrom = '';
|
|
public bool $displaySlider = false;
|
|
public bool $load_ok;
|
|
public bool $onlyFeedsWithError;
|
|
public bool $signalError;
|
|
|
|
// Manage users
|
|
/** @var array{feed_count:?int,article_count:?int,database_size:?int,language:string,mail_login:string,enabled:bool,is_admin:bool,last_user_activity:string,is_default:bool} */
|
|
public array $details;
|
|
public bool $disable_aside;
|
|
public bool $show_email_field;
|
|
public string $username;
|
|
/** @var array<array{language:string,enabled:bool,is_admin:bool,enabled:bool,article_count:?int,database_size:?int,last_user_activity:string,mail_login:string,feed_count:?int,is_default:bool}> */
|
|
public array $users;
|
|
|
|
// Updates
|
|
public string $last_update_time;
|
|
/** @var array<string,'ok'|'ko'|'warn'> */
|
|
public array $status_files;
|
|
/** @var array<string,'ok'|'ko'|'warn'> */
|
|
public array $status_php;
|
|
public bool $update_to_apply;
|
|
/** @var array<string,array<string, bool>|bool> */
|
|
public array $status_database;
|
|
public bool $is_release_channel_stable;
|
|
|
|
// Archiving
|
|
public int $nb_total;
|
|
public int $size_total;
|
|
public int $size_user;
|
|
|
|
// Display
|
|
/** @var array<string,array{id:string,name:string,author:string,description:string,version:float|string,files:array<string>,theme-color?:string|array{dark?:string,light?:string,default?:string}}> */
|
|
public array $themes;
|
|
|
|
// Shortcuts
|
|
/** @var array<int, string> */
|
|
public array $list_keys;
|
|
|
|
// User queries
|
|
/** @var array<int,FreshRSS_UserQuery> where the key is the query ID */
|
|
public array $queries;
|
|
/** @var FreshRSS_UserQuery|null */
|
|
public ?FreshRSS_UserQuery $query = null;
|
|
|
|
// Export / Import
|
|
public string $content;
|
|
public string $exportPath;
|
|
public int $feedCount;
|
|
/** @var array<string,array<string>> */
|
|
public array $entryIdsTagNames = [];
|
|
public string $list_title;
|
|
public int $queryId;
|
|
public string $type;
|
|
/** @var null|array<array{name:string,size:int,mtime:int}> */
|
|
public ?array $sqliteArchives = null;
|
|
public string $sqlitePath;
|
|
public string $sqliteName;
|
|
|
|
// Form login
|
|
public int $cookie_days;
|
|
|
|
// Registration
|
|
public bool $can_register;
|
|
public string $preferred_language;
|
|
public bool $show_tos_checkbox;
|
|
public string $terms_of_service;
|
|
public string $site_title;
|
|
public string $validation_url;
|
|
|
|
// Logs
|
|
public int $currentPage;
|
|
public Minz_Paginator $logsPaginator;
|
|
public int $nbPage;
|
|
public string $logSearch = '';
|
|
|
|
// RSS view
|
|
public FreshRSS_UserQuery $userQuery;
|
|
public string $html_url = '';
|
|
public string $rss_title = '';
|
|
public string $rss_url = '';
|
|
public string $rss_base = '';
|
|
public bool $internal_rendering = false;
|
|
public string $description = '';
|
|
public string $image_url = '';
|
|
public bool $includeUserLabels = false;
|
|
public bool $excludeArticleTags = false;
|
|
public string $userLabelPrefix = '';
|
|
|
|
// Content preview
|
|
public string $fatalError;
|
|
public string $htmlContent;
|
|
public bool $selectorSuccess;
|
|
|
|
// Extensions
|
|
/** @var list<ExtensionFullMetadata> */
|
|
public array $available_extensions;
|
|
public ?Minz_Extension $ext_details = null;
|
|
/** @var array{system:array<Minz_Extension>,user:array<Minz_Extension>} */
|
|
public array $extension_list;
|
|
public ?Minz_Extension $extension = null;
|
|
/** @var array<string,string> */
|
|
public array $extensions_installed;
|
|
|
|
// Errors
|
|
public string $code;
|
|
public string $errorMessage;
|
|
/** @var array<string,string> */
|
|
public array $message;
|
|
|
|
// View modes
|
|
/** @var array<FreshRSS_ViewMode> */
|
|
public array $viewModes;
|
|
}
|