diff --git a/lib/article.dart b/lib/article.dart new file mode 100644 index 0000000..0256b0f --- /dev/null +++ b/lib/article.dart @@ -0,0 +1,32 @@ +class Article { + final String? title; + final String? author; + final int length; + final String? excerpt; + final String? siteName; + final String? imageUrl; + final String? faviconUrl; + final String? content; + final String? textContent; + final String? language; + final String? publishedTime; + + Article({ + required this.title, + required this.author, + required this.length, + required this.excerpt, + required this.siteName, + required this.imageUrl, + required this.faviconUrl, + required this.content, + required this.textContent, + required this.language, + required this.publishedTime, + }); + + @override + String toString() { + return 'Article{title: $title,\n author: $author,\n length: $length,\n excerpt: $excerpt,\n siteName: $siteName,\n imageUrl: $imageUrl,\n faviconUrl: $faviconUrl,\n content: $content,\n textContent: $textContent,\n language: $language,\n publishedTime: $publishedTime}'; + } +} diff --git a/lib/readability.dart b/lib/readability.dart index d61efb9..0fcf633 100644 --- a/lib/readability.dart +++ b/lib/readability.dart @@ -9,6 +9,8 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:io'; import 'dart:isolate'; +import 'package:ffi/ffi.dart'; +import 'package:readability/article.dart'; import 'readability_bindings_generated.dart'; @@ -17,7 +19,7 @@ import 'readability_bindings_generated.dart'; /// For very short-lived functions, it is fine to call them on the main isolate. /// They will block the Dart execution while running the native function, so /// only do this for native functions which are guaranteed to be short-lived. -int sum(int a, int b) => _bindings.sum(a, b); +// int sum(int a, int b) => _bindings.sum(a, b); /// A longer lived native function, which occupies the thread calling it. /// @@ -29,12 +31,12 @@ int sum(int a, int b) => _bindings.sum(a, b); /// /// 1. Reuse a single isolate for various different kinds of requests. /// 2. Use multiple helper isolates for parallel execution. -Future sumAsync(int a, int b) async { +Future parseAsync(String url) async { final SendPort helperIsolateSendPort = await _helperIsolateSendPort; - final int requestId = _nextSumRequestId++; - final _SumRequest request = _SumRequest(requestId, a, b); - final Completer completer = Completer(); - _sumRequests[requestId] = completer; + final int requestId = _nextParseRequestId++; + final _ParseRequest request = _ParseRequest(url, requestId); + final Completer completer = Completer(); + _parseRequests[requestId] = completer; helperIsolateSendPort.send(request); return completer.future; } @@ -58,33 +60,32 @@ final DynamicLibrary _dylib = () { /// The bindings to the native functions in [_dylib]. final ReadabilityBindings _bindings = ReadabilityBindings(_dylib); - /// A request to compute `sum`. /// /// Typically sent from one isolate to another. -class _SumRequest { +class _ParseRequest { + final String url; final int id; - final int a; - final int b; - const _SumRequest(this.id, this.a, this.b); + const _ParseRequest(this.url, this.id); } -/// A response with the result of `sum`. -/// -/// Typically sent from one isolate to another. -class _SumResponse { +class ArticleResponse { + final Article article; final int id; - final int result; - const _SumResponse(this.id, this.result); + ArticleResponse({ + required this.article, + required this.id, + }); } -/// Counter to identify [_SumRequest]s and [_SumResponse]s. -int _nextSumRequestId = 0; +/// Counter to identify [_ParseRequest]s and [ArticleResponse]s. +int _nextParseRequestId = 0; -/// Mapping from [_SumRequest] `id`s to the completers corresponding to the correct future of the pending request. -final Map> _sumRequests = >{}; +/// Mapping from [_ParseRequest] `id`s to the completers corresponding to the correct future of the pending request. +final Map> _parseRequests = + >{}; /// The SendPort belonging to the helper isolate. Future _helperIsolateSendPort = () async { @@ -103,11 +104,11 @@ Future _helperIsolateSendPort = () async { completer.complete(data); return; } - if (data is _SumResponse) { + if (data is ArticleResponse) { // The helper isolate sent us a response to a request we sent. - final Completer completer = _sumRequests[data.id]!; - _sumRequests.remove(data.id); - completer.complete(data.result); + final Completer completer = _parseRequests[data.id]!; + _parseRequests.remove(data.id); + completer.complete(data); return; } throw UnsupportedError('Unsupported message type: ${data.runtimeType}'); @@ -118,10 +119,53 @@ Future _helperIsolateSendPort = () async { final ReceivePort helperReceivePort = ReceivePort() ..listen((dynamic data) { // On the helper isolate listen to requests and respond to them. - if (data is _SumRequest) { - final int result = _bindings.sum_long_running(data.a, data.b); - final _SumResponse response = _SumResponse(data.id, result); - sendPort.send(response); + if (data is _ParseRequest) { + final urlPointer = data.url.toNativeUtf8(); + final CArticle article = _bindings.Parse(urlPointer); + + // Free the native string. + calloc.free(urlPointer); + + // Convert the native article to a Dart article. + final articleDart = Article( + title: + article.title == nullptr ? null : article.title.toDartString(), + author: article.author == nullptr + ? null + : article.author.toDartString(), + length: article.length, + excerpt: article.excerpt == nullptr + ? null + : article.excerpt.toDartString(), + siteName: article.site_name == nullptr + ? null + : article.site_name.toDartString(), + imageUrl: article.image_url == nullptr + ? null + : article.image_url.toDartString(), + faviconUrl: article.favicon_url == nullptr + ? null + : article.favicon_url.toDartString(), + content: article.content == nullptr + ? null + : article.content.toDartString(), + textContent: article.text_content == nullptr + ? null + : article.text_content.toDartString(), + language: article.language == nullptr + ? null + : article.language.toDartString(), + publishedTime: article.published_time == nullptr + ? null + : article.published_time.toDartString(), + ); + ArticleResponse articleDartResponse = + ArticleResponse(article: articleDart, id: data.id); + + // Free the native article. + _bindings.FreeArticle(article); + + sendPort.send(articleDartResponse); return; } throw UnsupportedError('Unsupported message type: ${data.runtimeType}'); diff --git a/lib/readability_bindings_generated.dart b/lib/readability_bindings_generated.dart index 734471c..455d662 100644 --- a/lib/readability_bindings_generated.dart +++ b/lib/readability_bindings_generated.dart @@ -8,6 +8,8 @@ // ignore_for_file: type=lint import 'dart:ffi' as ffi; +import 'package:ffi/ffi.dart'; + /// Bindings for `src/readability.h`. /// /// Regenerate bindings with `dart run ffigen --config ffigen.yaml`. @@ -27,44 +29,92 @@ class ReadabilityBindings { lookup) : _lookup = lookup; - /// A very short-lived native function. - /// - /// For very short-lived functions, it is fine to call them on the main isolate. - /// They will block the Dart execution while running the native function, so - /// only do this for native functions which are guaranteed to be short-lived. - int sum( - int a, - int b, + CArticle Parse( + ffi.Pointer url, ) { - return _sum( - a, - b, + return _Parse( + url, ); } - late final _sumPtr = - _lookup>( - 'sum'); - late final _sum = _sumPtr.asFunction(); + late final _ParsePtr = + _lookup)>>( + 'Parse'); + late final _Parse = + _ParsePtr.asFunction)>(); - /// A longer lived native function, which occupies the thread calling it. - /// - /// Do not call these kind of native functions in the main isolate. They will - /// block Dart execution. This will cause dropped frames in Flutter applications. - /// Instead, call these native functions on a separate isolate. - int sum_long_running( - int a, - int b, + void FreeArticle( + CArticle article, ) { - return _sum_long_running( - a, - b, + return _FreeArticle( + article, ); } - late final _sum_long_runningPtr = - _lookup>( - 'sum_long_running'); - late final _sum_long_running = - _sum_long_runningPtr.asFunction(); + late final _FreeArticlePtr = + _lookup>('FreeArticle'); + late final _FreeArticle = + _FreeArticlePtr.asFunction(); } + +final class _GoString_ extends ffi.Struct { + external ffi.Pointer p; + + @ptrdiff_t() + external int n; +} + +typedef ptrdiff_t = ffi.Long; +typedef Dartptrdiff_t = int; + +final class CArticle extends ffi.Struct { + external ffi.Pointer title; + + external ffi.Pointer author; + + @ffi.Int() + external int length; + + external ffi.Pointer excerpt; + + external ffi.Pointer site_name; + + external ffi.Pointer image_url; + + external ffi.Pointer favicon_url; + + /// HTML content + external ffi.Pointer content; + + /// text content + external ffi.Pointer text_content; + + external ffi.Pointer language; + + external ffi.Pointer published_time; + + external ffi.Pointer err; + + @ffi.Int() + external int success; +} + +final class GoInterface extends ffi.Struct { + external ffi.Pointer t; + + external ffi.Pointer v; +} + +final class GoSlice extends ffi.Struct { + external ffi.Pointer data; + + @GoInt() + external int len; + + @GoInt() + external int cap; +} + +typedef GoInt = GoInt64; +typedef GoInt64 = ffi.LongLong; +typedef DartGoInt64 = int; diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt deleted file mode 100644 index 54c514f..0000000 --- a/linux/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# The Flutter tooling requires that developers have CMake 3.10 or later -# installed. You should not increase this version, as doing so will cause -# the plugin to fail to compile for some customers of the plugin. -cmake_minimum_required(VERSION 3.10) - -# Project-level configuration. -set(PROJECT_NAME "readability") -project(${PROJECT_NAME} LANGUAGES CXX) - -# Invoke the build for native code shared with the other target platforms. -# This can be changed to accommodate different builds. -add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared") - -# List of absolute paths to libraries that should be bundled with the plugin. -# This list could contain prebuilt libraries, or libraries created by an -# external build triggered from this build file. -set(readability_bundled_libraries - # Defined in ../src/CMakeLists.txt. - # This can be changed to accommodate different builds. - $ - PARENT_SCOPE -) diff --git a/pubspec.yaml b/pubspec.yaml index ea70c04..1c17fe0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,19 +1,19 @@ name: readability description: "A new Flutter FFI plugin project." version: 0.0.1 -homepage: https://git.kat.bio/SphericalKat/readability-dart +homepage: https://git.kat.directory/SphericalKat/readability-dart environment: sdk: '>=3.4.3 <4.0.0' flutter: '>=3.3.0' dependencies: + ffi: ^2.1.2 flutter: sdk: flutter plugin_platform_interface: ^2.0.2 dev_dependencies: - ffi: ^2.1.0 ffigen: ^11.0.0 flutter_test: sdk: flutter @@ -24,18 +24,6 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) - # which should be registered in the plugin registry. This is required for - # using method channels. - # The Android 'package' specifies package in which the registered class is. - # This is required for using method channels on Android. - # The 'ffiPlugin' specifies that native code should be built and bundled. - # This is required for using `dart:ffi`. - # All these are used by the tooling to maintain consistency when - # adding or updating assets for this project. - # - # Please refer to README.md for a detailed explanation. plugin: platforms: # This FFI plugin project was generated without specifying any @@ -45,44 +33,8 @@ flutter: # ------------------- android: - package: bio.kat.readability pluginClass: ReadabilityPlugin ffiPlugin: true ios: pluginClass: ReadabilityPlugin ffiPlugin: true - linux: - pluginClass: ReadabilityPlugin - ffiPlugin: true - - - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # To add custom fonts to your plugin package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 9f06bcb..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -# The Flutter tooling requires that developers have CMake 3.10 or later -# installed. You should not increase this version, as doing so will cause -# the plugin to fail to compile for some customers of the plugin. -cmake_minimum_required(VERSION 3.10) - -project(readability_library VERSION 0.0.1 LANGUAGES C) - -add_library(readability SHARED - "readability.c" -) - -set_target_properties(readability PROPERTIES - PUBLIC_HEADER readability.h - OUTPUT_NAME "readability" -) - -target_compile_definitions(readability PUBLIC DART_SHARED_LIB) diff --git a/src/readability.c b/src/readability.c deleted file mode 100644 index da4d2da..0000000 --- a/src/readability.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "readability.h" - -// A very short-lived native function. -// -// For very short-lived functions, it is fine to call them on the main isolate. -// They will block the Dart execution while running the native function, so -// only do this for native functions which are guaranteed to be short-lived. -FFI_PLUGIN_EXPORT int sum(int a, int b) { return a + b; } - -// A longer-lived native function, which occupies the thread calling it. -// -// Do not call these kind of native functions in the main isolate. They will -// block Dart execution. This will cause dropped frames in Flutter applications. -// Instead, call these native functions on a separate isolate. -FFI_PLUGIN_EXPORT int sum_long_running(int a, int b) { - // Simulate work. -#if _WIN32 - Sleep(5000); -#else - usleep(5000 * 1000); -#endif - return a + b; -} diff --git a/src/readability.h b/src/readability.h deleted file mode 100644 index 7944459..0000000 --- a/src/readability.h +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include -#include - -#if _WIN32 -#include -#else -#include -#include -#endif - -#if _WIN32 -#define FFI_PLUGIN_EXPORT __declspec(dllexport) -#else -#define FFI_PLUGIN_EXPORT -#endif - -// A very short-lived native function. -// -// For very short-lived functions, it is fine to call them on the main isolate. -// They will block the Dart execution while running the native function, so -// only do this for native functions which are guaranteed to be short-lived. -FFI_PLUGIN_EXPORT int sum(int a, int b); - -// A longer lived native function, which occupies the thread calling it. -// -// Do not call these kind of native functions in the main isolate. They will -// block Dart execution. This will cause dropped frames in Flutter applications. -// Instead, call these native functions on a separate isolate. -FFI_PLUGIN_EXPORT int sum_long_running(int a, int b);