From 2550b39f939f05086a02f85283ef8d140af6df69 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:24:25 +0000 Subject: [PATCH] Extract inline RegExps to static and top-level final variables Extract inline RegExp objects into static final or top-level final variables across multiple files (NexusPHP adapters, downloader clients, format utils, login widget). This prevents unnecessary recompilation and object allocation on hot paths (e.g., parsing loops, isolate execution, UI builds), thereby improving performance. Co-authored-by: JustLookAtNow <12379683+JustLookAtNow@users.noreply.github.com> --- lib/services/api/nexusphp_adapter.dart | 31 +++---- lib/services/api/nexusphp_web_adapter.dart | 13 +-- .../downloader/qbittorrent_client.dart | 10 +-- lib/services/downloader/rutorrent_client.dart | 6 +- .../downloader/transmission_client.dart | 89 +++++++++++-------- lib/utils/format.dart | 3 +- lib/widgets/web_login_widget.dart | 18 ++-- pubspec.lock | 16 ++-- 8 files changed, 107 insertions(+), 79 deletions(-) diff --git a/lib/services/api/nexusphp_adapter.dart b/lib/services/api/nexusphp_adapter.dart index 2924535..6386830 100644 --- a/lib/services/api/nexusphp_adapter.dart +++ b/lib/services/api/nexusphp_adapter.dart @@ -14,6 +14,7 @@ import 'nexusphp_helper.dart'; class NexusPHPAdapter with NexusPHPHelper implements SiteAdapter { late SiteConfig _siteConfig; late Dio _dio; + static final RegExp _whitespaceRegExp = RegExp(r'[\s\u200B-\u200D\uFEFF]'); Map? _discountMapping; Map? _tagMapping; static final Logger _logger = Logger(); @@ -39,7 +40,9 @@ class NexusPHPAdapter with NexusPHPHelper implements SiteAdapter { await _loadTagMapping(); swDiscount.stop(); if (kDebugMode) { - _logger.d('NexusPHPAdapter.init: 加载优惠映射耗时=${swDiscount.elapsedMilliseconds}ms'); + _logger.d( + 'NexusPHPAdapter.init: 加载优惠映射耗时=${swDiscount.elapsedMilliseconds}ms', + ); } _dio = Dio( @@ -75,7 +78,9 @@ class NexusPHPAdapter with NexusPHPHelper implements SiteAdapter { ); swInterceptors.stop(); if (kDebugMode) { - _logger.d('NexusPHPAdapter.init: 配置Dio与拦截器耗时=${swInterceptors.elapsedMilliseconds}ms'); + _logger.d( + 'NexusPHPAdapter.init: 配置Dio与拦截器耗时=${swInterceptors.elapsedMilliseconds}ms', + ); } swTotal.stop(); if (kDebugMode) { @@ -91,9 +96,7 @@ class NexusPHPAdapter with NexusPHPHelper implements SiteAdapter { SiteType.nexusphp, ); if (template?.discountMapping != null) { - _discountMapping = Map.from( - template!.discountMapping, - ); + _discountMapping = Map.from(template!.discountMapping); } final specialMapping = await SiteConfigService.getDiscountMapping( _siteConfig.baseUrl, @@ -283,8 +286,6 @@ class NexusPHPAdapter with NexusPHPHelper implements SiteAdapter { } } - - final name = item['name'] as String; final smallDescr = item['small_descr'] as String? ?? ''; @@ -311,8 +312,6 @@ class NexusPHPAdapter with NexusPHPHelper implements SiteAdapter { } } - - return TorrentItem( id: (item['id'] as int).toString(), name: name, @@ -369,7 +368,11 @@ class NexusPHPAdapter with NexusPHPHelper implements SiteAdapter { } @override - Future fetchComments(String id, {int pageNumber = 1, int pageSize = 20}) async { + Future fetchComments( + String id, { + int pageNumber = 1, + int pageSize = 20, + }) async { try { final response = await _dio.get( '/api/v1/comments', @@ -506,9 +509,7 @@ class NexusPHPAdapter with NexusPHPHelper implements SiteAdapter { Future> getSearchCategories() async { // 通过baseUrl匹配预设配置 final defaultCategories = - await SiteConfigService.getDefaultSearchCategories( - _siteConfig.baseUrl, - ); + await SiteConfigService.getDefaultSearchCategories(_siteConfig.baseUrl); // 如果获取到默认分类配置,则直接返回 if (defaultCategories.isNotEmpty) { @@ -536,13 +537,13 @@ class NexusPHPAdapter with NexusPHPHelper implements SiteAdapter { for (final section in sectionsData) { final sectionName = section['name'] as String; final sectionDisplayName = (section['display_name'] as String) - .replaceAll(RegExp(r'[\s\u200B-\u200D\uFEFF]'), ''); + .replaceAll(_whitespaceRegExp, ''); final categoriesData = section['categories'] as List; for (final category in categoriesData) { final categoryId = category['id']; final categoryName = (category['name'] as String).replaceAll( - RegExp(r'[\s\u200B-\u200D\uFEFF]'), + _whitespaceRegExp, '', ); categories.add( diff --git a/lib/services/api/nexusphp_web_adapter.dart b/lib/services/api/nexusphp_web_adapter.dart index de866d1..34ca1c2 100644 --- a/lib/services/api/nexusphp_web_adapter.dart +++ b/lib/services/api/nexusphp_web_adapter.dart @@ -41,6 +41,12 @@ class ParseSearchParams { }); } +final RegExp _sizeRegExp = RegExp(r'([\d.]+)\s*(\w+)'); +final RegExp _relativeUrlRegExp = RegExp( + r'(src|href)="((?!https?://|//|data:|javascript:|#)[^"]+)"', + caseSensitive: false, +); + /// 解析结果对象 class ParsedTorrentResult { final List items; @@ -1147,7 +1153,7 @@ class NexusPHPWebAdapter extends SiteAdapter // 解析文件大小为字节数 int sizeInBytes = 0; if (sizeText.isNotEmpty) { - final sizeMatch = RegExp(r'([\d.]+)\s*(\w+)').firstMatch(sizeText); + final sizeMatch = _sizeRegExp.firstMatch(sizeText); if (sizeMatch != null) { final sizeValue = double.tryParse(sizeMatch.group(1) ?? '0') ?? 0; final unit = sizeMatch.group(2)?.toUpperCase() ?? 'B'; @@ -1303,10 +1309,7 @@ class NexusPHPWebAdapter extends SiteAdapter } else { // HTML 模式:处理相对URL extractedContent = extractedContent.replaceAllMapped( - RegExp( - r'(src|href)="((?!https?://|//|data:|javascript:|#)[^"]+)"', - caseSensitive: false, - ), + _relativeUrlRegExp, (match) { final attr = match.group(1); final path = match.group(2)!; diff --git a/lib/services/downloader/qbittorrent_client.dart b/lib/services/downloader/qbittorrent_client.dart index ceaffb4..bec1da4 100644 --- a/lib/services/downloader/qbittorrent_client.dart +++ b/lib/services/downloader/qbittorrent_client.dart @@ -11,6 +11,9 @@ import 'downloader_config.dart'; import 'downloader_models.dart'; import 'torrent_file_downloader_mixin.dart'; +final RegExp _httpRegExp = RegExp(r'https?://'); +final RegExp _sidRegExp = RegExp(r'SID=([^;]+)'); + /// qBittorrent下载器客户端实现 class QbittorrentClient with TorrentFileDownloaderMixin @@ -65,7 +68,7 @@ class QbittorrentClient String _buildBase(QbittorrentConfig c) { var urlStr = c.host.trim(); // 补全协议 - if (!urlStr.startsWith(RegExp(r'https?://'))) { + if (!urlStr.startsWith(_httpRegExp)) { urlStr = 'http://$urlStr'; } @@ -223,7 +226,7 @@ class QbittorrentClient // 从响应头中提取会话ID final cookies = response.headers['set-cookie']; if (cookies != null && cookies.isNotEmpty) { - final sidMatch = RegExp(r'SID=([^;]+)').firstMatch(cookies.first); + final sidMatch = _sidRegExp.firstMatch(cookies.first); if (sidMatch != null) { _sessionId = sidMatch.group(1); return; @@ -308,7 +311,6 @@ class QbittorrentClient forceRelay = true; } - final useRelay = config.useLocalRelay || forceRelay; if (!useRelay) { body['urls'] = url; @@ -339,8 +341,6 @@ class QbittorrentClient await _request('POST', '/torrents/add', body: body); } - - /// 根据版本获取暂停任务的 API 路径 String _getPauseApiPath(String? version) { if (version == null) return '/torrents/pause'; // 默认使用 4.x 的路径 diff --git a/lib/services/downloader/rutorrent_client.dart b/lib/services/downloader/rutorrent_client.dart index d065d58..27a5b0c 100644 --- a/lib/services/downloader/rutorrent_client.dart +++ b/lib/services/downloader/rutorrent_client.dart @@ -12,6 +12,8 @@ import 'downloader_config.dart'; import 'downloader_models.dart'; import 'torrent_file_downloader_mixin.dart'; +final RegExp _httpRegExp = RegExp(r'https?://'); + /// ruTorrent下载器客户端实现 class RuTorrentClient with TorrentFileDownloaderMixin @@ -66,7 +68,7 @@ class RuTorrentClient String _buildBase(RuTorrentConfig c) { var urlStr = c.host.trim(); // 补全协议 - if (!urlStr.startsWith(RegExp(r'https?://'))) { + if (!urlStr.startsWith(_httpRegExp)) { urlStr = 'http://$urlStr'; } @@ -291,8 +293,6 @@ class RuTorrentClient return FormatUtil.parseInt(val) ?? 0; } - - @override Future testConnection() async { try { diff --git a/lib/services/downloader/transmission_client.dart b/lib/services/downloader/transmission_client.dart index bc32e35..20d6a3f 100644 --- a/lib/services/downloader/transmission_client.dart +++ b/lib/services/downloader/transmission_client.dart @@ -10,6 +10,8 @@ import 'downloader_config.dart'; import 'downloader_models.dart'; import 'torrent_file_downloader_mixin.dart'; +final RegExp _httpRegExp = RegExp(r'https?://'); + /// Transmission下载器客户端实现 class TransmissionClient with TorrentFileDownloaderMixin @@ -32,17 +34,19 @@ class TransmissionClient required this.password, Function(TransmissionConfig)? onConfigUpdated, }) : _onConfigUpdated = onConfigUpdated { - _dio = Dio(BaseOptions( - connectTimeout: const Duration(seconds: 10), - receiveTimeout: const Duration(seconds: 30), - headers: { + _dio = Dio( + BaseOptions( + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 30), + headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36', - 'Content-Type': 'application/json', - }, + 'Content-Type': 'application/json', + }, followRedirects: true, maxRedirects: 5, - )); + ), + ); } /// 获取基础URL @@ -52,7 +56,7 @@ class TransmissionClient String _buildBase(TransmissionConfig c) { var urlStr = c.host.trim(); // 补全协议 - if (!urlStr.startsWith(RegExp(r'https?://'))) { + if (!urlStr.startsWith(_httpRegExp)) { urlStr = 'http://$urlStr'; } @@ -85,18 +89,15 @@ class TransmissionClient }) async { final url = '$_baseUrl$_rpcPath'; - final requestBody = { - 'method': method, - 'arguments': ?arguments, - }; + final requestBody = {'method': method, 'arguments': ?arguments}; - final requestHeaders = { - 'Content-Type': 'application/json', - }; + final requestHeaders = {'Content-Type': 'application/json'}; // 添加认证信息 if (config.username.isNotEmpty) { - final credentials = base64Encode(utf8.encode('${config.username}:$password')); + final credentials = base64Encode( + utf8.encode('${config.username}:$password'), + ); requestHeaders['Authorization'] = 'Basic $credentials'; } @@ -129,7 +130,9 @@ class TransmissionClient // 检查是否需要会话ID if (e.response?.statusCode == 409) { // 从响应头中提取会话ID - final sessionId = e.response?.headers.value('X-Transmission-Session-Id'); + final sessionId = e.response?.headers.value( + 'X-Transmission-Session-Id', + ); if (sessionId != null) { _sessionId = sessionId; // 重试请求 @@ -142,15 +145,15 @@ class TransmissionClient } if (e.response?.statusCode != null && e.response!.statusCode! >= 400) { - throw HttpException('HTTP ${e.response!.statusCode}: ${e.response!.data}'); + throw HttpException( + 'HTTP ${e.response!.statusCode}: ${e.response!.data}', + ); } throw Exception('Request failed: ${e.message}'); } } - - @override Future testConnection() async { try { @@ -283,13 +286,16 @@ class TransmissionClient } @override - Future deleteTasks(List hashes, {bool deleteFiles = false}) async { + Future deleteTasks( + List hashes, { + bool deleteFiles = false, + }) async { final ids = await _hashesToIds(hashes); if (ids.isNotEmpty) { - await _rpcRequest('torrent-remove', arguments: { - 'ids': ids, - 'delete-local-data': deleteFiles, - }); + await _rpcRequest( + 'torrent-remove', + arguments: {'ids': ids, 'delete-local-data': deleteFiles}, + ); } } @@ -303,9 +309,12 @@ class TransmissionClient @override Future> getTags() async { // 获取所有种子的标签 - final response = await _rpcRequest('torrent-get', arguments: { - 'fields': ['labels'], - }); + final response = await _rpcRequest( + 'torrent-get', + arguments: { + 'fields': ['labels'], + }, + ); final List torrents = response['torrents'] as List? ?? []; final Set allLabels = {}; @@ -346,9 +355,12 @@ class TransmissionClient @override Future> getPaths() async { // 获取所有种子的下载路径 - final response = await _rpcRequest('torrent-get', arguments: { - 'fields': ['downloadDir'], - }); + final response = await _rpcRequest( + 'torrent-get', + arguments: { + 'fields': ['downloadDir'], + }, + ); final List torrents = response['torrents'] as List? ?? []; final Set allPaths = {}; @@ -386,9 +398,12 @@ class TransmissionClient Future> _hashesToIds(List hashes) async { if (hashes.isEmpty) return []; - final response = await _rpcRequest('torrent-get', arguments: { - 'fields': ['id', 'hashString'], - }); + final response = await _rpcRequest( + 'torrent-get', + arguments: { + 'fields': ['id', 'hashString'], + }, + ); final List torrents = response['torrents'] as List? ?? []; final List ids = []; @@ -457,7 +472,9 @@ class TransmissionClient addedOn: torrent['addedDate'] ?? 0, amountLeft: leftUntilDone, ratio: (torrent['uploadRatio'] as num? ?? 0).toDouble(), - timeActive: (torrent['activityDate'] as int? ?? 0) - (torrent['addedDate'] as int? ?? 0), + timeActive: + (torrent['activityDate'] as int? ?? 0) - + (torrent['addedDate'] as int? ?? 0), uploaded: torrent['uploadedEver'] ?? 0, ); } @@ -486,4 +503,4 @@ class TransmissionClient void dispose() { _dio.close(); } -} \ No newline at end of file +} diff --git a/lib/utils/format.dart b/lib/utils/format.dart index a3de6a6..091b209 100644 --- a/lib/utils/format.dart +++ b/lib/utils/format.dart @@ -46,6 +46,7 @@ class FormatUtil { } static final RegExp _nonDigitRegExp = RegExp(r'\D'); + static final RegExp _timezoneRegExp = RegExp(r'Z|[+-]\d{2}:?\d{2}$'); /// 解析字符串为整数 /// 先判断有没有小数点,有的话则截取整数部分,然后移除掉所有的非数字字符,最后返回int.tryParse的结果 @@ -193,7 +194,7 @@ class Formatters { normalizedDate = normalizedDate.replaceRange(10, 11, 'T'); } - if (normalizedDate.contains(RegExp(r'Z|[+-]\d{2}:?\d{2}$'))) { + if (normalizedDate.contains(FormatUtil._timezoneRegExp)) { return DateTime.parse(normalizedDate).toLocal(); } else { return DateTime.parse("$normalizedDate$actualZone").toLocal(); diff --git a/lib/widgets/web_login_widget.dart b/lib/widgets/web_login_widget.dart index a155474..808d717 100644 --- a/lib/widgets/web_login_widget.dart +++ b/lib/widgets/web_login_widget.dart @@ -4,6 +4,11 @@ import 'package:logger/logger.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:pt_mate/utils/notification_helper.dart'; +final RegExp _titleRegExp = RegExp( + r'(.*?)<\/title>', + caseSensitive: false, +); + class WebLoginWidget extends StatefulWidget { final String baseUrl; final String? loginPath; // 自定义登录页面路径,默认为 /login.php @@ -77,10 +82,7 @@ class _WebLoginWidgetState extends State<WebLoginWidget> { if (html == null || html.isEmpty) return; // 检查页面标题 - final titleMatch = RegExp( - r'<title>(.*?)<\/title>', - caseSensitive: false, - ).firstMatch(html); + final titleMatch = _titleRegExp.firstMatch(html); final title = titleMatch?.group(1) ?? ""; // 如果标题包含登录字样,说明尚未成功 @@ -311,7 +313,11 @@ class _WebLoginWidgetState extends State<WebLoginWidget> { _logger.w('CookieManager未获取到cookie'); } if (mounted) { - NotificationHelper.showInfo(context, '无法获取cookie,请检查登录状态', duration: const Duration(seconds: 3)); + NotificationHelper.showInfo( + context, + '无法获取cookie,请检查登录状态', + duration: const Duration(seconds: 3), + ); } return false; } catch (e) { @@ -319,7 +325,7 @@ class _WebLoginWidgetState extends State<WebLoginWidget> { _logger.e('提取cookie时出错: $e'); } if (mounted) { - NotificationHelper.showError(context, 'Cookie提取失败: $e'); + NotificationHelper.showError(context, 'Cookie提取失败: $e'); } return false; } diff --git a/pubspec.lock b/pubspec.lock index 507755d..2ed0eaf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -649,18 +649,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: @@ -1022,10 +1022,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.9" timezone: dependency: transitive description: