From a9bd0272931b342a68734fe325105da2421cd7dc Mon Sep 17 00:00:00 2001 From: Soumyadip Mondal Date: Sun, 16 May 2021 15:02:27 +0530 Subject: [PATCH 1/7] Flutter support merged Added Android Support Added Linux (Flutter) Support Build system only works on Linux64 Introduces bug #8 Reference #7 --- .github/workflows/test-package.yml | 6 +- .gitignore | 8 ++ .metadata | 10 ++ CHANGELOG.md | 3 + README.md | 62 +++------- analysis_options.yaml | 3 + android/.gitignore | 8 ++ android/CMakeLists.txt | 18 +++ android/build.gradle | 52 +++++++++ android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle | 1 + android/src/main/AndroidManifest.xml | 5 + .../cronet_sample/CronetSamplePlugin.kt | 41 +++++++ benchmark/benchmark.sh | 16 --- benchmark/http_based.dart | 21 ---- bin/cronet_sample.dart | 10 +- cronet_sample.iml | 19 ++++ example/.gitignore | 46 ++++++++ example/.metadata | 10 ++ example/README.md | 16 +++ example/android/.gitignore | 11 ++ example/android/app/build.gradle | 59 ++++++++++ .../android/app/src/debug/AndroidManifest.xml | 7 ++ .../android/app/src/main/AndroidManifest.xml | 44 ++++++++ .../cronet_sample_example/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 ++ .../main/res/drawable/launch_background.xml | 12 ++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 +++ .../app/src/main/res/values/styles.xml | 18 +++ .../app/src/profile/AndroidManifest.xml | 7 ++ example/android/build.gradle | 31 +++++ example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + example/android/settings.gradle | 11 ++ example/lib/main.dart | 85 ++++++++++++++ example/linux/.gitignore | 1 + example/linux/CMakeLists.txt | 106 ++++++++++++++++++ example/linux/flutter/CMakeLists.txt | 91 +++++++++++++++ .../flutter/generated_plugin_registrant.cc | 13 +++ .../flutter/generated_plugin_registrant.h | 13 +++ example/linux/flutter/generated_plugins.cmake | 16 +++ example/linux/main.cc | 6 + example/linux/my_application.cc | 104 +++++++++++++++++ example/linux/my_application.h | 18 +++ example/main.dart | 29 ----- example/pubspec.yaml | 71 ++++++++++++ example/test/widget_test.dart | 27 +++++ lib/cronet_sample.dart | 17 ++- lib/src/find_resource.dart | 4 +- lib/src/http_client.dart | 4 +- lib/src/http_client_request.dart | 8 +- lib/src/prepare_cronet.dart | 77 ++++++++----- lib/standalone.dart | 4 + linux/CMakeLists.txt | 25 +++++ linux/cronet_sample_plugin.cc | 70 ++++++++++++ .../cronet_sample/cronet_sample_plugin.h | 26 +++++ output.png | Bin 134874 -> 0 bytes pubspec.yaml | 63 ++++++++++- test/cronet_sample_test.dart | 23 ++++ 65 files changed, 1348 insertions(+), 161 deletions(-) create mode 100644 .metadata create mode 100644 CHANGELOG.md create mode 100644 android/.gitignore create mode 100644 android/CMakeLists.txt create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/kotlin/com/example/cronet_sample/CronetSamplePlugin.kt delete mode 100755 benchmark/benchmark.sh delete mode 100755 benchmark/http_based.dart create mode 100644 cronet_sample.iml create mode 100644 example/.gitignore create mode 100644 example/.metadata create mode 100644 example/README.md create mode 100644 example/android/.gitignore create mode 100644 example/android/app/build.gradle create mode 100644 example/android/app/src/debug/AndroidManifest.xml create mode 100644 example/android/app/src/main/AndroidManifest.xml create mode 100644 example/android/app/src/main/kotlin/com/example/cronet_sample_example/MainActivity.kt create mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/values-night/styles.xml create mode 100644 example/android/app/src/main/res/values/styles.xml create mode 100644 example/android/app/src/profile/AndroidManifest.xml create mode 100644 example/android/build.gradle create mode 100644 example/android/gradle.properties create mode 100644 example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 example/android/settings.gradle create mode 100644 example/lib/main.dart create mode 100644 example/linux/.gitignore create mode 100644 example/linux/CMakeLists.txt create mode 100644 example/linux/flutter/CMakeLists.txt create mode 100644 example/linux/flutter/generated_plugin_registrant.cc create mode 100644 example/linux/flutter/generated_plugin_registrant.h create mode 100644 example/linux/flutter/generated_plugins.cmake create mode 100644 example/linux/main.cc create mode 100644 example/linux/my_application.cc create mode 100644 example/linux/my_application.h delete mode 100755 example/main.dart create mode 100644 example/pubspec.yaml create mode 100644 example/test/widget_test.dart mode change 100755 => 100644 lib/cronet_sample.dart create mode 100755 lib/standalone.dart create mode 100644 linux/CMakeLists.txt create mode 100644 linux/cronet_sample_plugin.cc create mode 100644 linux/include/cronet_sample/cronet_sample_plugin.h delete mode 100644 output.png mode change 100755 => 100644 pubspec.yaml create mode 100644 test/cronet_sample_test.dart diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 90713c2..158a848 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -23,9 +23,9 @@ jobs: - uses: dart-lang/setup-dart@v1.0 with: sdk: ${{ matrix.sdk }} - - id: install - name: Install dependencies - run: dart pub get + # - id: install + # name: Install dependencies + # run: dart pub get - name: Check formatting run: dart format --output=none --set-exit-if-changed . if: always() && steps.install.outcome == 'success' diff --git a/.gitignore b/.gitignore index bac845f..9247bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,14 @@ cronet_binaries/ # AOT compiled files. *.exe +*.jar +*.cxx + +*.flutter-plugins +*.flutter-plugins-dependencies + +*.idea + # Directory for quick experiments. experiments/ diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..83d6e04 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: adc687823a831bbebe28bdccfac1a628ca621513 + channel: stable + +project_type: plugin diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/README.md b/README.md index d65459b..5c06d29 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ Ported from: https://chromium.googlesource.com/chromium/src/+/master/components/ [![Build Status](https://github.com/unsuitable001/dart_cronet_sample/workflows/Dart%20CI/badge.svg)](https://github.com/unsuitable001/dart_cronet_sample/actions?query=workflow%3A"Dart+CI") -Checkout the Flutter version with Android support: https://github.com/unsuitable001/flutter_cronet_sample +~~Checkout the Flutter version with Android support: https://github.com/unsuitable001/flutter_cronet_sample~~ + +Flutter (Android/Linux) support is now merged in this repository. ## Usage @@ -15,16 +17,21 @@ Checkout the Flutter version with Android support: https://github.com/unsuitable ```pubspec dependencies: cronet_sample: - git: https://github.com/unsuitable001/dart_cronet_sample.git + git: + url: https://github.com/unsuitable001/dart_cronet_sample.git + ref: flutter_support + ``` 2. Run this from the `root` of your project ```bash -pub get -pub run cronet_sample +flutter pub get +flutter pub run cronet_sample ``` +Two platforms are currently supported. `linux64` and `androidarm64-v8a` + 3. Import ```dart @@ -85,49 +92,6 @@ Copy the cronet's binary to the `cronet_binaries/` folder from p From the root of the repo, run ```bash -dart run -dart example/main.dart -``` - -### Output - -You'll get the HTML page of `example.com` along with some other texts (I used them for lazy & easy debugging). - - -![example.com output](/output.png?raw=true "Screenshot") - -## Comparison - -I compared this with `dart:io` library's http client. I ran tests 5 times, repeatitively (same machine, same network, same site - http://info.cern.ch/). The results are given below - - +cd example +flutter run ``` -Round 1: -cronet implemenation took: 385 ms -dart:io implemenation took: 351 ms - -Round 2: -cronet implemenation took: 382 ms -dart:io implemenation took: 345 ms - -Round 3: -cronet implemenation took: 378 ms -dart:io implemenation took: 349 ms - -Round 4: -cronet implemenation took: 385 ms -dart:io implemenation took: 363 ms - -Round 5: -cronet implemenation took: 385 ms -dart:io implemenation took: 359 ms -``` - -Well, now `cronet` based solution is marginally slower than `dart:io`. Making the API similar to `dart:io` surely added some overhead, like - receiving message from C side and then forwarding that data to another stream. - -### Compare Yourself - -*Run from root of the project* - -```bash -./benchmark/benchmark.sh -``` \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 01bbee7..16747c4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -4,6 +4,9 @@ analyzer: strong-mode: implicit-casts: false implicit-dynamic: false + exclude: + - example/** + - lib/cronet_sample.dart # due to method channel linter: rules: diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt new file mode 100644 index 0000000..c8ecd9a --- /dev/null +++ b/android/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.10) # for example +project(flutter_cronet) +add_library(wrapper + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + "../lib/src/native/wrapper/wrapper.cc" + "../lib/src/native/wrapper/wrapper.h" + "../lib/src/native/wrapper/sample_executor.cc" + "../lib/src/native/wrapper/sample_executor.h" + "../lib/src/native/include/dart_api_dl.c" +) + +include_directories(../lib/src/native/include/) + +add_compile_options(-fPIC -ldl -rdynamic -DDART_SHARED_LIB -fpermissive) \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..b10624a --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,52 @@ +group 'com.example.cronet_sample' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 30 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + externalNativeBuild { + // Encapsulates your CMake build configurations. + cmake { + // Provides a relative path to your CMake build script. + path "CMakeLists.txt" + } + } + + defaultConfig { + minSdkVersion 16 + ndk { + abiFilters 'arm64-v8a' + } + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation files(fileTree(dir: 'libs', includes: ['*.jar'])) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c9d085 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..c1ee135 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'cronet_sample' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..dbd9932 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + diff --git a/android/src/main/kotlin/com/example/cronet_sample/CronetSamplePlugin.kt b/android/src/main/kotlin/com/example/cronet_sample/CronetSamplePlugin.kt new file mode 100644 index 0000000..a60219e --- /dev/null +++ b/android/src/main/kotlin/com/example/cronet_sample/CronetSamplePlugin.kt @@ -0,0 +1,41 @@ +package com.example.cronet_sample + +import androidx.annotation.NonNull + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry.Registrar + +import org.chromium.base.ContextUtils + +/** CronetSamplePlugin */ +class CronetSamplePlugin: FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel : MethodChannel + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + System.loadLibrary("cronet.91.0.4456.0") + System.loadLibrary("wrapper") + ContextUtils.initApplicationContext(flutterPluginBinding.applicationContext) + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cronet_sample") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} diff --git a/benchmark/benchmark.sh b/benchmark/benchmark.sh deleted file mode 100755 index 9e75f02..0000000 --- a/benchmark/benchmark.sh +++ /dev/null @@ -1,16 +0,0 @@ -dart run -if [ ! -f "example/main.exe" ]; then - dart compile exe example/main.dart -fi - -if [ ! -f "benchmark/http_based.exe" ]; then - dart compile exe benchmark/http_based.dart -fi - -echo "Let's check execution time of cronet's implementation" - -./example/main.exe - -echo "Let's check execution time of dart:io" - -./benchmark/http_based.exe diff --git a/benchmark/http_based.dart b/benchmark/http_based.dart deleted file mode 100755 index a505cce..0000000 --- a/benchmark/http_based.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -int main(List args) { - final stopwatch = Stopwatch()..start(); - - final client = HttpClient(); - client - .getUrl(Uri.parse('http://info.cern.ch/')) - .then((HttpClientRequest request) { - return request.close(); - }).then((HttpClientResponse response) { - response.transform(utf8.decoder).listen((contents) { - print(contents); - }, - onDone: () => print( - 'dart:io implemenation took: ${stopwatch.elapsedMilliseconds} ms')); - }); - - return 0; -} diff --git a/bin/cronet_sample.dart b/bin/cronet_sample.dart index 2767ce2..23dd230 100644 --- a/bin/cronet_sample.dart +++ b/bin/cronet_sample.dart @@ -1,6 +1,10 @@ import 'package:cronet_sample/src/prepare_cronet.dart'; -void main(List arguments) async { - buildWrapper(); - downloadCronetBinaries(['linux64']); +void main(List arguments) { + arguments.forEach((platform) async { + if (platform.startsWith('linux')) { + buildWrapper(); + } + await downloadCronetBinaries(platform); + }); } diff --git a/cronet_sample.iml b/cronet_sample.iml new file mode 100644 index 0000000..429df7d --- /dev/null +++ b/cronet_sample.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..0fa6b67 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 0000000..c1ee81b --- /dev/null +++ b/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 1d9032c7e1d867f071f2277eb1673e8f9b0274e3 + channel: stable + +project_type: app diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..ad3198c --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# cronet_sample_example + +Demonstrates how to use the cronet_sample plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000..0a741cb --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..fc0405a --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,59 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 30 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.cronet_sample_example" + minSdkVersion 16 + targetSdkVersion 30 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..5eb13f3 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..50bab75 --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/example/cronet_sample_example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/cronet_sample_example/MainActivity.kt new file mode 100644 index 0000000..faa7f2d --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/cronet_sample_example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.cronet_sample_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..449a9f9 --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..d74aa35 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..5eb13f3 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 0000000..c505a86 --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bc6a58a --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..44136b5 --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,85 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:cronet_sample/cronet_sample.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + String data = ''; + bool _fetching = false; + final client = HttpClient(); + final _stopwatch = Stopwatch(); + + @override + void initState() { + request(); + request(); // SEE issue #8 + super.initState(); + } + + void request() { + setState(() { + _fetching = true; + }); + client + .getUrl(Uri.parse('http://info.cern.ch/')) + .then((HttpClientRequest request) { + _stopwatch.start(); + + /* The alternate API introduced. + NOTE: If we register callbacks & listen to the stream at the same time, + the stream will be closed immediately executing the onDone callback */ + + // request.registerCallbacks(onReadData: (contents, size, next) { + // print(utf8.decoder.convert(contents)); + // next(); + // }, onSuccess: () => print("cronet implemenation took: ${stopwatch.elapsedMilliseconds} ms")); + return request.close(); + }).then((Stream> response) { + response.transform(utf8.decoder).listen((contents) { + setState(() { + data += contents; + }); + }, onDone: () { + _stopwatch.stop(); + setState(() { + _fetching = false; + }); + }); + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: Column(children: [ + Text('Cronet Version: ${client.HttpClientVersion}'), + _fetching + ? CircularProgressIndicator() + : Expanded(child: SingleChildScrollView(child: Text('$data'))), + Text('Time taken: ${_stopwatch.elapsedMilliseconds} ms') + ])), + floatingActionButton: FloatingActionButton( + tooltip: 'Fetch Data', + child: Icon(Icons.arrow_right_alt_outlined), + onPressed: () => request(), + ), + ), + ); + } +} diff --git a/example/linux/.gitignore b/example/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt new file mode 100644 index 0000000..15740a1 --- /dev/null +++ b/example/linux/CMakeLists.txt @@ -0,0 +1,106 @@ +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +set(BINARY_NAME "cronet_sample_example") +set(APPLICATION_ID "com.example.cronet_sample") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Application build +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) +apply_standard_settings(${BINARY_NAME}) +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) +add_dependencies(${BINARY_NAME} flutter_assemble) +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/example/linux/flutter/CMakeLists.txt b/example/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..a1da1b9 --- /dev/null +++ b/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) +pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) +pkg_check_modules(LZMA REQUIRED IMPORTED_TARGET liblzma) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO + PkgConfig::BLKID + PkgConfig::LZMA +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + linux-x64 ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..fce059b --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,13 @@ +// +// Generated file. Do not edit. +// + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) cronet_sample_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "CronetSamplePlugin"); + cronet_sample_plugin_register_with_registrar(cronet_sample_registrar); +} diff --git a/example/linux/flutter/generated_plugin_registrant.h b/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..9bf7478 --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,13 @@ +// +// Generated file. Do not edit. +// + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..10cfb29 --- /dev/null +++ b/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,16 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + cronet_sample +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/example/linux/main.cc b/example/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/example/linux/my_application.cc b/example/linux/my_application.cc new file mode 100644 index 0000000..52a7c19 --- /dev/null +++ b/example/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen *screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "cronet_sample_example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } + else { + gtk_window_set_title(window, "cronet_sample_example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject *object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + nullptr)); +} diff --git a/example/linux/my_application.h b/example/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/example/main.dart b/example/main.dart deleted file mode 100755 index 537a4de..0000000 --- a/example/main.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'dart:convert'; -import 'package:cronet_sample/cronet_sample.dart'; - -/* Trying to re-impliment: https://chromium.googlesource.com/chromium/src/+/master/components/cronet/native/sample/main.cc */ - -void main(List args) { - final stopwatch = Stopwatch()..start(); - - final client = HttpClient(); - client - .getUrl(Uri.parse('http://info.cern.ch/')) - .then((HttpClientRequest request) { - /* The alternate API introduced. - NOTE: If we register callbacks & listen to the stream at the same time, - the stream will be closed immediately executing the onDone callback */ - - // request.registerCallbacks(onReadData: (contents, size, next) { - // print(utf8.decoder.convert(contents)); - // next(); - // }, onSuccess: () => print("cronet implemenation took: ${stopwatch.elapsedMilliseconds} ms")); - return request.close(); - }).then((Stream> response) { - response.transform(utf8.decoder).listen((contents) { - print(contents); - }, - onDone: () => print( - 'cronet implemenation took: ${stopwatch.elapsedMilliseconds} ms')); - }); -} diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..ae40a8f --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,71 @@ +name: cronet_sample_example +description: Demonstrates how to use the cronet_sample plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + cronet_sample: + # When depending on this package from a real application you should use: + # cronet_sample: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, 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 from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..b5e553a --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cronet_sample_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => + widget is Text && widget.data.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/lib/cronet_sample.dart b/lib/cronet_sample.dart old mode 100755 new mode 100644 index 94dbda8..cb1d619 --- a/lib/cronet_sample.dart +++ b/lib/cronet_sample.dart @@ -1,2 +1,15 @@ -export 'src/http_client.dart'; -export 'src/http_client_request.dart'; +import 'dart:async'; + +import 'package:flutter/services.dart'; + +export 'standalone.dart'; + +class CronetSample { + static const MethodChannel _channel = MethodChannel('cronet_sample'); + + static Future get platformVersion async { + // ignore: omit_local_variable_types + final String version = await _channel.invokeMethod('getPlatformVersion'); + return version; + } +} diff --git a/lib/src/find_resource.dart b/lib/src/find_resource.dart index 5a42c80..d290ee0 100644 --- a/lib/src/find_resource.dart +++ b/lib/src/find_resource.dart @@ -5,7 +5,7 @@ import 'dart:convert'; import 'dart:io' show File, Directory; /// Finds the root [Uri] of our package -Uri? _findPackageRoot() { +Uri? findPackageRoot() { var root = Directory.current.uri; do { // Traverse up till .dart_tool/package_config.json is found @@ -38,7 +38,7 @@ Uri? _findPackageRoot() { /// Throws [Exception] if not found String wrapperSourcePath() { // Finds this package's location - final packagePath = _findPackageRoot(); + final packagePath = findPackageRoot(); if (packagePath == null) { throw Exception('Cannot resolve package:cronet_sample\'s rootUri'); } diff --git a/lib/src/http_client.dart b/lib/src/http_client.dart index cbbb562..0a78c86 100755 --- a/lib/src/http_client.dart +++ b/lib/src/http_client.dart @@ -23,8 +23,8 @@ final _cronet = Cronet(loadWrapper()); /// /// /// TODO: Implement other functions -/// -/// +/// +/// /// Example Usage: /// ```dart /// final client = HttpClient(); diff --git a/lib/src/http_client_request.dart b/lib/src/http_client_request.dart index 0f04328..1a64f4a 100755 --- a/lib/src/http_client_request.dart +++ b/lib/src/http_client_request.dart @@ -24,8 +24,8 @@ typedef SuccessCallabck = void Function(); /// [registerCallbacks] or a [Stream] of [List] of [int] like [HttpClientResponse]. /// /// Either of them can be used at a time. -/// -/// +/// +/// /// Example Usage: /// ```dart /// final client = HttpClient(); @@ -37,8 +37,8 @@ typedef SuccessCallabck = void Function(); /// // Use it as you like. /// }); /// ``` -/// -/// +/// +/// /// TODO: Implement other functions class HttpClientRequest { final Uri _uri; diff --git a/lib/src/prepare_cronet.dart b/lib/src/prepare_cronet.dart index 7bfce81..01b617e 100644 --- a/lib/src/prepare_cronet.dart +++ b/lib/src/prepare_cronet.dart @@ -9,6 +9,7 @@ final _cronetBinaryUrl = 'https://github.com/unsuitable001/dart_cronet_sample/releases/download/$_release/'; final _cBinExtMap = { 'linux64': '.tar.xz', + 'androidarm64-v8a': '.tar.xz', }; /// Builds the [wrapper] shared library @@ -28,31 +29,55 @@ void buildWrapper() { /// Download [cronet] library /// from Github Releases -void downloadCronetBinaries(List platforms) { - platforms.forEach((platform) async { - if (!isCronetAvailable(platform)) { - final fileName = platform + (_cBinExtMap[platform] ?? ''); - print('Downloading Cronet for $platform'); - final downloadUrl = _cronetBinaryUrl + fileName; - final dProcess = await Process.start('wget', - ['-c', '-q', '--show-progress', '--progress=bar:force', downloadUrl], - mode: ProcessStartMode.inheritStdio); - if (await dProcess.exitCode != 0) { - throw Exception('Can\'t download. Check your network connection!'); - } - print('Extracting Cronet for $platform'); - Process.runSync('mkdir', ['-p', 'cronet_binaries']); - final res = - Process.runSync('tar', ['-xvf', fileName, '-C', 'cronet_binaries']); - if (res.exitCode != 0) { - throw Exception( - 'Can\'t unzip. Check if the downloaded file isn\'t corrupted'); - } - print('Done! Cleaning up...'); - Process.runSync('rm', ['-f', fileName]); - print('Done! Cronet support for $platform is now available!'); - } else { - print('Cronet $platform is already available. No need to download.'); +Future downloadCronetBinaries(String platform) async { + if (!isCronetAvailable(platform)) { + final fileName = platform + (_cBinExtMap[platform] ?? ''); + print('Downloading Cronet for $platform'); + final downloadUrl = _cronetBinaryUrl + fileName; + final dProcess = await Process.start('wget', + ['-c', '-q', '--show-progress', '--progress=bar:force', downloadUrl], + mode: ProcessStartMode.inheritStdio); + if (await dProcess.exitCode != 0) { + throw Exception('Can\'t download. Check your network connection!'); } - }); + print('Extracting Cronet for $platform'); + Process.runSync('mkdir', ['-p', 'cronet_binaries']); + final res = + Process.runSync('tar', ['-xvf', fileName, '-C', 'cronet_binaries']); + if (res.exitCode != 0) { + throw Exception( + 'Can\'t unzip. Check if the downloaded file isn\'t corrupted'); + } + print('Done! Cleaning up...'); + Process.runSync('rm', ['-f', fileName]); + if (platform.startsWith('android')) { + print(platform); + copyMobileBinaries(platform); + } + print('Done! Cronet support for $platform is now available!'); + } else { + print('Cronet $platform is already available. No need to download.'); + } +} + +void copyMobileBinaries(String platform) { + if (platform.startsWith('android')) { + final android = findPackageRoot()!.toFilePath() + 'android'; + + Process.runSync('mkdir', ['-p', android + '/libs']); + print('Copying to ' + android + '/libs'); + Process.runSync('cp', [ + '-R', + 'cronet_binaries/' + platform + '/libs', + android + ]); // copy jar files + + Process.runSync('mkdir', ['-p', android + '/src/main/jniLibs']); + Process.runSync('cp', [ + '-R', + 'cronet_binaries/' + platform + '/' + platform.split('android')[1], + android + '/src/main/jniLibs' + ]); // copy cronet + + } } diff --git a/lib/standalone.dart b/lib/standalone.dart new file mode 100755 index 0000000..f0fa30d --- /dev/null +++ b/lib/standalone.dart @@ -0,0 +1,4 @@ +// Keeping it in a hope that when Flutter and Dart Standalone +// can be merged, we can directly import this file +export 'src/http_client.dart'; +export 'src/http_client_request.dart'; diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..ae33076 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) +set(PROJECT_NAME "cronet_sample") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "cronet_sample_plugin") + +add_library(${PLUGIN_NAME} SHARED + "cronet_sample_plugin.cc" +) +apply_standard_settings(${PLUGIN_NAME}) +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) + +# List of absolute paths to libraries that should be bundled with the plugin +set(cronet_sample_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/linux/cronet_sample_plugin.cc b/linux/cronet_sample_plugin.cc new file mode 100644 index 0000000..36926b8 --- /dev/null +++ b/linux/cronet_sample_plugin.cc @@ -0,0 +1,70 @@ +#include "include/cronet_sample/cronet_sample_plugin.h" + +#include +#include +#include + +#include + +#define CRONET_SAMPLE_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), cronet_sample_plugin_get_type(), \ + CronetSamplePlugin)) + +struct _CronetSamplePlugin { + GObject parent_instance; +}; + +G_DEFINE_TYPE(CronetSamplePlugin, cronet_sample_plugin, g_object_get_type()) + +// Called when a method call is received from Flutter. +static void cronet_sample_plugin_handle_method_call( + CronetSamplePlugin* self, + FlMethodCall* method_call) { + g_autoptr(FlMethodResponse) response = nullptr; + + const gchar* method = fl_method_call_get_name(method_call); + + if (strcmp(method, "getPlatformVersion") == 0) { + struct utsname uname_data = {}; + uname(&uname_data); + g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version); + g_autoptr(FlValue) result = fl_value_new_string(version); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + fl_method_call_respond(method_call, response, nullptr); +} + +static void cronet_sample_plugin_dispose(GObject* object) { + G_OBJECT_CLASS(cronet_sample_plugin_parent_class)->dispose(object); +} + +static void cronet_sample_plugin_class_init(CronetSamplePluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = cronet_sample_plugin_dispose; +} + +static void cronet_sample_plugin_init(CronetSamplePlugin* self) {} + +static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + CronetSamplePlugin* plugin = CRONET_SAMPLE_PLUGIN(user_data); + cronet_sample_plugin_handle_method_call(plugin, method_call); +} + +void cronet_sample_plugin_register_with_registrar(FlPluginRegistrar* registrar) { + CronetSamplePlugin* plugin = CRONET_SAMPLE_PLUGIN( + g_object_new(cronet_sample_plugin_get_type(), nullptr)); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = + fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), + "cronet_sample", + FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(channel, method_call_cb, + g_object_ref(plugin), + g_object_unref); + + g_object_unref(plugin); +} diff --git a/linux/include/cronet_sample/cronet_sample_plugin.h b/linux/include/cronet_sample/cronet_sample_plugin.h new file mode 100644 index 0000000..07cc618 --- /dev/null +++ b/linux/include/cronet_sample/cronet_sample_plugin.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_PLUGIN_CRONET_SAMPLE_PLUGIN_H_ +#define FLUTTER_PLUGIN_CRONET_SAMPLE_PLUGIN_H_ + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +typedef struct _CronetSamplePlugin CronetSamplePlugin; +typedef struct { + GObjectClass parent_class; +} CronetSamplePluginClass; + +FLUTTER_PLUGIN_EXPORT GType cronet_sample_plugin_get_type(); + +FLUTTER_PLUGIN_EXPORT void cronet_sample_plugin_register_with_registrar( + FlPluginRegistrar* registrar); + +G_END_DECLS + +#endif // FLUTTER_PLUGIN_CRONET_SAMPLE_PLUGIN_H_ diff --git a/output.png b/output.png deleted file mode 100644 index 787c4d95a9f580194529231e2366b6d2319edbd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134874 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%0D)W3iHr1|%L|1B^SJ|*1`-q~ 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.12.0 <3.0.0' + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.20.0" dependencies: + flutter: + sdk: flutter ffi: ^1.0.0 path: any dev_dependencies: - pedantic: ^1.10.0 + flutter_test: + sdk: flutter + pedantic: ^1.11.0 ffigen: ^2.4.0 +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' and Android 'package' identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: com.example.cronet_sample + pluginClass: CronetSamplePlugin + linux: + pluginClass: CronetSamplePlugin + + ffigen: name: 'Cronet' description: 'Bindings to Cronet' @@ -29,3 +54,33 @@ ffigen: - 'lib/src/native/wrapper/wrapper_export.h' compiler-opts: - '-Ilib/src/native/include/ -DDART_SHARED_LIB' + # 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/test/cronet_sample_test.dart b/test/cronet_sample_test.dart new file mode 100644 index 0000000..4c1188b --- /dev/null +++ b/test/cronet_sample_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:cronet_sample/cronet_sample.dart'; + +void main() { + const channel = MethodChannel('cronet_sample'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return '42'; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('getPlatformVersion', () async { + expect(await CronetSample.platformVersion, '42'); + }); +} From 60a8c7efaf59ce7d3ed09f083c8120f852a0ea89 Mon Sep 17 00:00:00 2001 From: Soumyadip Mondal Date: Sun, 16 May 2021 17:52:47 +0530 Subject: [PATCH 2/7] Dirtify file/directory movements --- lib/src/prepare_cronet.dart | 76 ++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/lib/src/prepare_cronet.dart b/lib/src/prepare_cronet.dart index 01b617e..77fe55f 100644 --- a/lib/src/prepare_cronet.dart +++ b/lib/src/prepare_cronet.dart @@ -1,6 +1,8 @@ // Contains the nessesary setup code only. Not meant to be exposed. -import 'dart:io' show Process, ProcessStartMode; +import 'dart:io' show Directory, File, Process, ProcessStartMode; + +import 'package:path/path.dart'; import 'find_resource.dart'; @@ -27,6 +29,44 @@ void buildWrapper() { print(result.stderr); } +/// Move binary files for mobiles. Currenly just [Android] is implemented +void moveMobileBinaries(String platform) { + if (platform.startsWith('android')) { + final android = findPackageRoot()!.toFilePath() + 'android'; + + Directory(android + '/libs').createSync(); + + Directory('cronet_binaries/' + platform + '/libs') + .listSync() + .forEach((jar) { + if (jar is File) { + jar.renameSync(android + '/libs/' + basename(jar.path)); + } + }); // move the extracted jars + + Directory(android + '/src/main/jniLibs').createSync(); + + Directory( + 'cronet_binaries/' + platform + '/' + platform.split('android')[1]) + .listSync() + .forEach((cronet) { + if (cronet is File) { + Directory(android + '/src/main/jniLibs/' + platform.split('android')[1]) + .createSync(); + + if (cronet is File) { + cronet.renameSync(android + + '/src/main/jniLibs/' + + platform.split('android')[1] + + '/' + + basename(cronet.path)); + } + } + }); // move cronet binaries + Directory('cronet_binaries/$platform').deleteSync(recursive: true); + } +} + /// Download [cronet] library /// from Github Releases Future downloadCronetBinaries(String platform) async { @@ -41,7 +81,12 @@ Future downloadCronetBinaries(String platform) async { throw Exception('Can\'t download. Check your network connection!'); } print('Extracting Cronet for $platform'); - Process.runSync('mkdir', ['-p', 'cronet_binaries']); + + // Process.runSync('mkdir', ['-p', 'cronet_binaries']); + Directory('cronet_binaries').createSync(); + + // Do we have tar extraction capability + // in dart's built-in libraries? final res = Process.runSync('tar', ['-xvf', fileName, '-C', 'cronet_binaries']); if (res.exitCode != 0) { @@ -49,35 +94,14 @@ Future downloadCronetBinaries(String platform) async { 'Can\'t unzip. Check if the downloaded file isn\'t corrupted'); } print('Done! Cleaning up...'); - Process.runSync('rm', ['-f', fileName]); + + File(fileName).deleteSync(); if (platform.startsWith('android')) { print(platform); - copyMobileBinaries(platform); + moveMobileBinaries(platform); } print('Done! Cronet support for $platform is now available!'); } else { print('Cronet $platform is already available. No need to download.'); } } - -void copyMobileBinaries(String platform) { - if (platform.startsWith('android')) { - final android = findPackageRoot()!.toFilePath() + 'android'; - - Process.runSync('mkdir', ['-p', android + '/libs']); - print('Copying to ' + android + '/libs'); - Process.runSync('cp', [ - '-R', - 'cronet_binaries/' + platform + '/libs', - android - ]); // copy jar files - - Process.runSync('mkdir', ['-p', android + '/src/main/jniLibs']); - Process.runSync('cp', [ - '-R', - 'cronet_binaries/' + platform + '/' + platform.split('android')[1], - android + '/src/main/jniLibs' - ]); // copy cronet - - } -} From 3c4ddf3e46e57a7d9479d17d6974d1e0f3a1aee6 Mon Sep 17 00:00:00 2001 From: Soumyadip Mondal Date: Sun, 16 May 2021 18:19:14 +0530 Subject: [PATCH 3/7] Wrong branch name fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c06d29..fddad54 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ dependencies: cronet_sample: git: url: https://github.com/unsuitable001/dart_cronet_sample.git - ref: flutter_support + ref: feature_flutter ``` From ae7ae7957cbd610107d179f81e05b81e65e68643 Mon Sep 17 00:00:00 2001 From: Soumyadip Mondal Date: Mon, 17 May 2021 16:50:02 +0530 Subject: [PATCH 4/7] Removing flutter dependency Added .pubignore Android binary resources aren't checked into git. But, included via .pubignore Removed binary file copy for mobile devices Ref: #7 Updated readme --- .github/workflows/test-package.yml | 6 +-- .pubignore | 59 ++++++++++++++++++++ README.md | 23 ++++++-- example_dart/bin/example_dart.dart | 29 ++++++++++ example_dart/pubspec.yaml | 15 ++++++ lib/cronet_sample.dart | 17 +----- lib/src/prepare_cronet.dart | 86 +++++++++++++++--------------- lib/standalone.dart | 4 -- pubspec.yaml | 12 ++--- test/cronet_sample_test.dart | 23 -------- 10 files changed, 178 insertions(+), 96 deletions(-) create mode 100644 .pubignore create mode 100644 example_dart/bin/example_dart.dart create mode 100644 example_dart/pubspec.yaml delete mode 100755 lib/standalone.dart diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 158a848..90713c2 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -23,9 +23,9 @@ jobs: - uses: dart-lang/setup-dart@v1.0 with: sdk: ${{ matrix.sdk }} - # - id: install - # name: Install dependencies - # run: dart pub get + - id: install + name: Install dependencies + run: dart pub get - name: Check formatting run: dart format --output=none --set-exit-if-changed . if: always() && steps.install.outcome == 'success' diff --git a/.pubignore b/.pubignore new file mode 100644 index 0000000..24e181a --- /dev/null +++ b/.pubignore @@ -0,0 +1,59 @@ +# See https://dart.dev/guides/libraries/private-files + +# Files and directories created by pub. +.dart_tool/ +.packages +pubspec.lock + +# IDE and debugger files. +.clangd +.gdb_history +.history +.vscode +compile_commands.json + +# Directory created by dartdoc. +# If you don't generate documentation locally you can remove this line. +doc/api/ + +# Avoid committing generated Javascript files: +*.dart.js +*.info.json # Produced by the --dump-info flag. +*.js # When generated by dart2js. Don't specify *.js if your + # project includes source files written in JavaScript. +*.js_ +*.js.deps +*.js.map + +# Cronet pre-built binaries +cronet_binaries/ + +# Generated shared libraries. +*.so +*.so.* +*.dylib +*.dll + +# AOT compiled files. +*.exe + +*.jar +*.cxx + +# Jar dependencies should be published to pub +!android/libs/* + +# Cronet binaries for android should be published to pub +!android/src/main/jniLibs/** + +*.flutter-plugins +*.flutter-plugins-dependencies + +*.idea + +# Directory for quick experiments. +experiments/ + +# Files generated by tests for debugging purposes. +test/debug_generated/* +!test/debug_generated/readme.md diff --git a/README.md b/README.md index fddad54..3f54f5f 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,22 @@ dependencies: 2. Run this from the `root` of your project +Desktop Platforms + ```bash -flutter pub get -flutter pub run cronet_sample +pub get +pub run cronet_sample ``` +Supported platforms: `linux64` + -Two platforms are currently supported. `linux64` and `androidarm64-v8a` +Mobile Platforms (Flutter) + +```bash +flutter pub get +``` +Binaries aren't checked into git. They will be published on pub though. Meanwhile, +they can be downloaded from `Releases` section. 3. Import @@ -95,3 +105,10 @@ From the root of the repo, run cd example flutter run ``` + +or + +```bash +cd example_dart +dart run +``` diff --git a/example_dart/bin/example_dart.dart b/example_dart/bin/example_dart.dart new file mode 100644 index 0000000..28db296 --- /dev/null +++ b/example_dart/bin/example_dart.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; +import 'package:cronet_sample/cronet_sample.dart'; + +/* Trying to re-impliment: https://chromium.googlesource.com/chromium/src/+/master/components/cronet/native/sample/main.cc */ + +void main(List args) { + final stopwatch = Stopwatch()..start(); + + final client = HttpClient(); + client + .getUrl(Uri.parse('http://info.cern.ch/')) + .then((HttpClientRequest request) { + /* The alternate API introduced. + NOTE: If we register callbacks & listen to the stream at the same time, + the stream will be closed immediately executing the onDone callback */ + + // request.registerCallbacks(onReadData: (contents, size, next) { + // print(utf8.decoder.convert(contents)); + // next(); + // }, onSuccess: () => print("cronet implemenation took: ${stopwatch.elapsedMilliseconds} ms")); + return request.close(); + }).then((Stream> response) { + response.transform(utf8.decoder).listen((contents) { + print(contents); + }, + onDone: () => print( + 'cronet implemenation took: ${stopwatch.elapsedMilliseconds} ms')); + }); +} \ No newline at end of file diff --git a/example_dart/pubspec.yaml b/example_dart/pubspec.yaml new file mode 100644 index 0000000..8c500e7 --- /dev/null +++ b/example_dart/pubspec.yaml @@ -0,0 +1,15 @@ +name: example_dart +description: A simple command-line application. +# version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.10.0 <3.0.0' + +dependencies: + cronet_sample: + path: ../ +# path: ^1.7.0 + +dev_dependencies: + pedantic: ^1.9.0 diff --git a/lib/cronet_sample.dart b/lib/cronet_sample.dart index cb1d619..94dbda8 100644 --- a/lib/cronet_sample.dart +++ b/lib/cronet_sample.dart @@ -1,15 +1,2 @@ -import 'dart:async'; - -import 'package:flutter/services.dart'; - -export 'standalone.dart'; - -class CronetSample { - static const MethodChannel _channel = MethodChannel('cronet_sample'); - - static Future get platformVersion async { - // ignore: omit_local_variable_types - final String version = await _channel.invokeMethod('getPlatformVersion'); - return version; - } -} +export 'src/http_client.dart'; +export 'src/http_client_request.dart'; diff --git a/lib/src/prepare_cronet.dart b/lib/src/prepare_cronet.dart index 77fe55f..382b69f 100644 --- a/lib/src/prepare_cronet.dart +++ b/lib/src/prepare_cronet.dart @@ -2,7 +2,7 @@ import 'dart:io' show Directory, File, Process, ProcessStartMode; -import 'package:path/path.dart'; +// import 'package:path/path.dart'; import 'find_resource.dart'; @@ -11,7 +11,7 @@ final _cronetBinaryUrl = 'https://github.com/unsuitable001/dart_cronet_sample/releases/download/$_release/'; final _cBinExtMap = { 'linux64': '.tar.xz', - 'androidarm64-v8a': '.tar.xz', + // 'androidarm64-v8a': '.tar.xz', }; /// Builds the [wrapper] shared library @@ -29,43 +29,45 @@ void buildWrapper() { print(result.stderr); } +// Disabled - files included via .pubignore + /// Move binary files for mobiles. Currenly just [Android] is implemented -void moveMobileBinaries(String platform) { - if (platform.startsWith('android')) { - final android = findPackageRoot()!.toFilePath() + 'android'; - - Directory(android + '/libs').createSync(); - - Directory('cronet_binaries/' + platform + '/libs') - .listSync() - .forEach((jar) { - if (jar is File) { - jar.renameSync(android + '/libs/' + basename(jar.path)); - } - }); // move the extracted jars - - Directory(android + '/src/main/jniLibs').createSync(); - - Directory( - 'cronet_binaries/' + platform + '/' + platform.split('android')[1]) - .listSync() - .forEach((cronet) { - if (cronet is File) { - Directory(android + '/src/main/jniLibs/' + platform.split('android')[1]) - .createSync(); - - if (cronet is File) { - cronet.renameSync(android + - '/src/main/jniLibs/' + - platform.split('android')[1] + - '/' + - basename(cronet.path)); - } - } - }); // move cronet binaries - Directory('cronet_binaries/$platform').deleteSync(recursive: true); - } -} +// void moveMobileBinaries(String platform) { +// if (platform.startsWith('android')) { +// final android = findPackageRoot()!.toFilePath() + 'android'; + +// Directory(android + '/libs').createSync(); + +// Directory('cronet_binaries/' + platform + '/libs') +// .listSync() +// .forEach((jar) { +// if (jar is File) { +// jar.renameSync(android + '/libs/' + basename(jar.path)); +// } +// }); // move the extracted jars + +// Directory(android + '/src/main/jniLibs').createSync(); + +// Directory( +// 'cronet_binaries/' + platform + '/' + platform.split('android')[1]) +// .listSync() +// .forEach((cronet) { +// if (cronet is File) { +// Directory(android + '/src/main/jniLibs/' + platform.split('android')[1]) +// .createSync(); + +// if (cronet is File) { +// cronet.renameSync(android + +// '/src/main/jniLibs/' + +// platform.split('android')[1] + +// '/' + +// basename(cronet.path)); +// } +// } +// }); // move cronet binaries +// Directory('cronet_binaries/$platform').deleteSync(recursive: true); +// } +// } /// Download [cronet] library /// from Github Releases @@ -96,10 +98,10 @@ Future downloadCronetBinaries(String platform) async { print('Done! Cleaning up...'); File(fileName).deleteSync(); - if (platform.startsWith('android')) { - print(platform); - moveMobileBinaries(platform); - } + // if (platform.startsWith('android')) { + // print(platform); + // moveMobileBinaries(platform); + // } print('Done! Cronet support for $platform is now available!'); } else { print('Cronet $platform is already available. No need to download.'); diff --git a/lib/standalone.dart b/lib/standalone.dart deleted file mode 100755 index f0fa30d..0000000 --- a/lib/standalone.dart +++ /dev/null @@ -1,4 +0,0 @@ -// Keeping it in a hope that when Flutter and Dart Standalone -// can be merged, we can directly import this file -export 'src/http_client.dart'; -export 'src/http_client_request.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 306d2d3..368c127 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,22 +1,22 @@ name: cronet_sample -description: A new flutter plugin project. +description: A cronet based HTTP Library sample. version: 0.0.1 author: homepage: environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=1.20.0" + # flutter: ">=1.20.0" dependencies: - flutter: - sdk: flutter + # flutter: + # sdk: flutter ffi: ^1.0.0 path: any dev_dependencies: - flutter_test: - sdk: flutter + # flutter_test: + # sdk: flutter pedantic: ^1.11.0 ffigen: ^2.4.0 diff --git a/test/cronet_sample_test.dart b/test/cronet_sample_test.dart index 4c1188b..e69de29 100644 --- a/test/cronet_sample_test.dart +++ b/test/cronet_sample_test.dart @@ -1,23 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:cronet_sample/cronet_sample.dart'; - -void main() { - const channel = MethodChannel('cronet_sample'); - - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return '42'; - }); - }); - - tearDown(() { - channel.setMockMethodCallHandler(null); - }); - - test('getPlatformVersion', () async { - expect(await CronetSample.platformVersion, '42'); - }); -} From 56d0b87b20dd88fece7c8c9f84a6375079b83b8a Mon Sep 17 00:00:00 2001 From: Soumyadip Mondal Date: Tue, 18 May 2021 12:49:05 +0530 Subject: [PATCH 5/7] Remove Linux Flutter plugin Linux support can be achieved without any flutter specific config like pluginClass. Dart standalone code will work on Linux Flutter also. Reference #7 --- example/lib/main.dart | 9 ++- .../flutter/generated_plugin_registrant.cc | 4 -- example/linux/flutter/generated_plugins.cmake | 1 - example_dart/bin/example_dart.dart | 2 +- linux/CMakeLists.txt | 25 ------- linux/cronet_sample_plugin.cc | 70 ------------------- .../cronet_sample/cronet_sample_plugin.h | 26 ------- pubspec.yaml | 4 +- test/cronet_sample_test.dart | 1 + 9 files changed, 11 insertions(+), 131 deletions(-) delete mode 100644 linux/CMakeLists.txt delete mode 100644 linux/cronet_sample_plugin.cc delete mode 100644 linux/include/cronet_sample/cronet_sample_plugin.h diff --git a/example/lib/main.dart b/example/lib/main.dart index 44136b5..8c21e57 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io' show Platform; import 'package:flutter/material.dart'; import 'dart:async'; @@ -22,9 +23,8 @@ class _MyAppState extends State { @override void initState() { - request(); - request(); // SEE issue #8 super.initState(); + request(); } void request() { @@ -55,6 +55,11 @@ class _MyAppState extends State { setState(() { _fetching = false; }); + }, onError: (e) { + if (Platform.isLinux) { + print('See issue #8'); + request(); + } }); }); } diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc index fce059b..d38195a 100644 --- a/example/linux/flutter/generated_plugin_registrant.cc +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -4,10 +4,6 @@ #include "generated_plugin_registrant.h" -#include void fl_register_plugins(FlPluginRegistry* registry) { - g_autoptr(FlPluginRegistrar) cronet_sample_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "CronetSamplePlugin"); - cronet_sample_plugin_register_with_registrar(cronet_sample_registrar); } diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake index 10cfb29..51436ae 100644 --- a/example/linux/flutter/generated_plugins.cmake +++ b/example/linux/flutter/generated_plugins.cmake @@ -3,7 +3,6 @@ # list(APPEND FLUTTER_PLUGIN_LIST - cronet_sample ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/example_dart/bin/example_dart.dart b/example_dart/bin/example_dart.dart index 28db296..537a4de 100644 --- a/example_dart/bin/example_dart.dart +++ b/example_dart/bin/example_dart.dart @@ -26,4 +26,4 @@ void main(List args) { onDone: () => print( 'cronet implemenation took: ${stopwatch.elapsedMilliseconds} ms')); }); -} \ No newline at end of file +} diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt deleted file mode 100644 index ae33076..0000000 --- a/linux/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -set(PROJECT_NAME "cronet_sample") -project(${PROJECT_NAME} LANGUAGES CXX) - -# This value is used when generating builds using this plugin, so it must -# not be changed -set(PLUGIN_NAME "cronet_sample_plugin") - -add_library(${PLUGIN_NAME} SHARED - "cronet_sample_plugin.cc" -) -apply_standard_settings(${PLUGIN_NAME}) -set_target_properties(${PLUGIN_NAME} PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) -target_include_directories(${PLUGIN_NAME} INTERFACE - "${CMAKE_CURRENT_SOURCE_DIR}/include") -target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) -target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) - -# List of absolute paths to libraries that should be bundled with the plugin -set(cronet_sample_bundled_libraries - "" - PARENT_SCOPE -) diff --git a/linux/cronet_sample_plugin.cc b/linux/cronet_sample_plugin.cc deleted file mode 100644 index 36926b8..0000000 --- a/linux/cronet_sample_plugin.cc +++ /dev/null @@ -1,70 +0,0 @@ -#include "include/cronet_sample/cronet_sample_plugin.h" - -#include -#include -#include - -#include - -#define CRONET_SAMPLE_PLUGIN(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), cronet_sample_plugin_get_type(), \ - CronetSamplePlugin)) - -struct _CronetSamplePlugin { - GObject parent_instance; -}; - -G_DEFINE_TYPE(CronetSamplePlugin, cronet_sample_plugin, g_object_get_type()) - -// Called when a method call is received from Flutter. -static void cronet_sample_plugin_handle_method_call( - CronetSamplePlugin* self, - FlMethodCall* method_call) { - g_autoptr(FlMethodResponse) response = nullptr; - - const gchar* method = fl_method_call_get_name(method_call); - - if (strcmp(method, "getPlatformVersion") == 0) { - struct utsname uname_data = {}; - uname(&uname_data); - g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version); - g_autoptr(FlValue) result = fl_value_new_string(version); - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } else { - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - } - - fl_method_call_respond(method_call, response, nullptr); -} - -static void cronet_sample_plugin_dispose(GObject* object) { - G_OBJECT_CLASS(cronet_sample_plugin_parent_class)->dispose(object); -} - -static void cronet_sample_plugin_class_init(CronetSamplePluginClass* klass) { - G_OBJECT_CLASS(klass)->dispose = cronet_sample_plugin_dispose; -} - -static void cronet_sample_plugin_init(CronetSamplePlugin* self) {} - -static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, - gpointer user_data) { - CronetSamplePlugin* plugin = CRONET_SAMPLE_PLUGIN(user_data); - cronet_sample_plugin_handle_method_call(plugin, method_call); -} - -void cronet_sample_plugin_register_with_registrar(FlPluginRegistrar* registrar) { - CronetSamplePlugin* plugin = CRONET_SAMPLE_PLUGIN( - g_object_new(cronet_sample_plugin_get_type(), nullptr)); - - g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); - g_autoptr(FlMethodChannel) channel = - fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), - "cronet_sample", - FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(channel, method_call_cb, - g_object_ref(plugin), - g_object_unref); - - g_object_unref(plugin); -} diff --git a/linux/include/cronet_sample/cronet_sample_plugin.h b/linux/include/cronet_sample/cronet_sample_plugin.h deleted file mode 100644 index 07cc618..0000000 --- a/linux/include/cronet_sample/cronet_sample_plugin.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef FLUTTER_PLUGIN_CRONET_SAMPLE_PLUGIN_H_ -#define FLUTTER_PLUGIN_CRONET_SAMPLE_PLUGIN_H_ - -#include - -G_BEGIN_DECLS - -#ifdef FLUTTER_PLUGIN_IMPL -#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) -#else -#define FLUTTER_PLUGIN_EXPORT -#endif - -typedef struct _CronetSamplePlugin CronetSamplePlugin; -typedef struct { - GObjectClass parent_class; -} CronetSamplePluginClass; - -FLUTTER_PLUGIN_EXPORT GType cronet_sample_plugin_get_type(); - -FLUTTER_PLUGIN_EXPORT void cronet_sample_plugin_register_with_registrar( - FlPluginRegistrar* registrar); - -G_END_DECLS - -#endif // FLUTTER_PLUGIN_CRONET_SAMPLE_PLUGIN_H_ diff --git a/pubspec.yaml b/pubspec.yaml index 368c127..b8ae8e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,8 +34,8 @@ flutter: android: package: com.example.cronet_sample pluginClass: CronetSamplePlugin - linux: - pluginClass: CronetSamplePlugin + # linux: + # pluginClass: CronetSamplePlugin ffigen: diff --git a/test/cronet_sample_test.dart b/test/cronet_sample_test.dart index e69de29..8b13789 100644 --- a/test/cronet_sample_test.dart +++ b/test/cronet_sample_test.dart @@ -0,0 +1 @@ + From b6e801046e1a07cfe73bdfd83ee3aafbdb7a7c58 Mon Sep 17 00:00:00 2001 From: Soumyadip Mondal Date: Wed, 19 May 2021 16:10:26 +0530 Subject: [PATCH 6/7] tool added: download mobile binaries CRONET_VERSION is now set from build scripts --- README.md | 1 - bin/cronet_sample.dart | 4 +- lib/src/native/wrapper/build.sh | 8 +-- lib/src/native/wrapper/wrapper.cc | 11 +++-- lib/src/prepare_cronet.dart | 81 +++++++++---------------------- tool/getMobileBinaries.dart | 58 ++++++++++++++++++++++ 6 files changed, 94 insertions(+), 69 deletions(-) create mode 100644 tool/getMobileBinaries.dart diff --git a/README.md b/README.md index 3f54f5f..4e794e1 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ dependencies: cronet_sample: git: url: https://github.com/unsuitable001/dart_cronet_sample.git - ref: feature_flutter ``` diff --git a/bin/cronet_sample.dart b/bin/cronet_sample.dart index 23dd230..c9cd05f 100644 --- a/bin/cronet_sample.dart +++ b/bin/cronet_sample.dart @@ -1,7 +1,7 @@ import 'package:cronet_sample/src/prepare_cronet.dart'; -void main(List arguments) { - arguments.forEach((platform) async { +void main(List platforms) { + platforms.forEach((platform) async { if (platform.startsWith('linux')) { buildWrapper(); } diff --git a/lib/src/native/wrapper/build.sh b/lib/src/native/wrapper/build.sh index d2f669b..cae3c43 100755 --- a/lib/src/native/wrapper/build.sh +++ b/lib/src/native/wrapper/build.sh @@ -1,8 +1,8 @@ -# Takes the path to wrapper source code as parameter -if [ $# -eq 0 ] +# Takes the path to wrapper source code path & cronet version as parameter +if [ $# -le 1 ] then - echo "No arguments supplied" + echo "Provide

'\"\"'" exit 2 fi cd $1 -g++ -fPIC -rdynamic -shared -W -o wrapper.so wrapper.cc sample_executor.cc ../include/dart_api_dl.c -ldl -I../include/ -DDART_SHARED_LIB -fpermissive -Wl,-z,origin -Wl,-rpath,'$ORIGIN' -Wl,-rpath,'$ORIGIN/cronet_binaries/linux64/' +g++ -DCRONET_VERSION=$2 -fPIC -rdynamic -shared -W -o wrapper.so wrapper.cc sample_executor.cc ../include/dart_api_dl.c -ldl -I../include/ -DDART_SHARED_LIB -fpermissive -Wl,-z,origin -Wl,-rpath,'$ORIGIN' -Wl,-rpath,'$ORIGIN/cronet_binaries/linux64/' diff --git a/lib/src/native/wrapper/wrapper.cc b/lib/src/native/wrapper/wrapper.cc index 0799c6d..653cbbe 100755 --- a/lib/src/native/wrapper/wrapper.cc +++ b/lib/src/native/wrapper/wrapper.cc @@ -7,11 +7,16 @@ #include #include -#define CRONET_VERSION "91.0.4456.0" -#define CRONET_LIB_PREFIX "libcronet." // NOTE: extra . (dot) is also a part of the prefix +#define CRONET_LIB_PREFIX "libcronet" #define CRONET_LIB_EXTENSION ".so" -#define CRONET_LIB_NAME CRONET_LIB_PREFIX CRONET_VERSION CRONET_LIB_EXTENSION +// Set CRONET_VERSION from build script + +#ifdef CRONET_VERSION + #define CRONET_LIB_NAME CRONET_LIB_PREFIX "." CRONET_VERSION CRONET_LIB_EXTENSION +#else + #define CRONET_LIB_NAME CRONET_LIB_PREFIX CRONET_LIB_EXTENSION +#endif //////////////////////////////////////////////////////////////////////////////// // Initialize `dart_api_dl.h` intptr_t InitDartApiDL(void* data) { diff --git a/lib/src/prepare_cronet.dart b/lib/src/prepare_cronet.dart index 382b69f..469da03 100644 --- a/lib/src/prepare_cronet.dart +++ b/lib/src/prepare_cronet.dart @@ -11,16 +11,18 @@ final _cronetBinaryUrl = 'https://github.com/unsuitable001/dart_cronet_sample/releases/download/$_release/'; final _cBinExtMap = { 'linux64': '.tar.xz', - // 'androidarm64-v8a': '.tar.xz', + 'androidarm64-v8a': '.tar.xz', }; +final _cronet_version = '"91.0.4456.0"'; + /// Builds the [wrapper] shared library /// according to [build.sh] file void buildWrapper() { final wrapperPath = wrapperSourcePath(); print('Building Wrapper...'); - var result = Process.runSync(wrapperPath + '/build.sh', [wrapperPath]); + var result = Process.runSync(wrapperPath + '/build.sh', [wrapperPath, _cronet_version]); print(result.stdout); print(result.stderr); print('Copying wrapper to project root...'); @@ -29,45 +31,26 @@ void buildWrapper() { print(result.stderr); } -// Disabled - files included via .pubignore - -/// Move binary files for mobiles. Currenly just [Android] is implemented -// void moveMobileBinaries(String platform) { -// if (platform.startsWith('android')) { -// final android = findPackageRoot()!.toFilePath() + 'android'; - -// Directory(android + '/libs').createSync(); +/// Place downloaded binaries to proper location +void placeBinaries(String platform, String fileName) { + print('Extracting Cronet for $platform'); -// Directory('cronet_binaries/' + platform + '/libs') -// .listSync() -// .forEach((jar) { -// if (jar is File) { -// jar.renameSync(android + '/libs/' + basename(jar.path)); -// } -// }); // move the extracted jars - -// Directory(android + '/src/main/jniLibs').createSync(); + // Process.runSync('mkdir', ['-p', 'cronet_binaries']); + Directory('cronet_binaries').createSync(); -// Directory( -// 'cronet_binaries/' + platform + '/' + platform.split('android')[1]) -// .listSync() -// .forEach((cronet) { -// if (cronet is File) { -// Directory(android + '/src/main/jniLibs/' + platform.split('android')[1]) -// .createSync(); + // Do we have tar extraction capability + // in dart's built-in libraries? + final res = + Process.runSync('tar', ['-xvf', fileName, '-C', 'cronet_binaries']); + if (res.exitCode != 0) { + throw Exception( + 'Can\'t unzip. Check if the downloaded file isn\'t corrupted'); + } + print('Done! Cleaning up...'); -// if (cronet is File) { -// cronet.renameSync(android + -// '/src/main/jniLibs/' + -// platform.split('android')[1] + -// '/' + -// basename(cronet.path)); -// } -// } -// }); // move cronet binaries -// Directory('cronet_binaries/$platform').deleteSync(recursive: true); -// } -// } + File(fileName).deleteSync(); + print('Done! Cronet support for $platform is now available!'); +} /// Download [cronet] library /// from Github Releases @@ -82,27 +65,7 @@ Future downloadCronetBinaries(String platform) async { if (await dProcess.exitCode != 0) { throw Exception('Can\'t download. Check your network connection!'); } - print('Extracting Cronet for $platform'); - - // Process.runSync('mkdir', ['-p', 'cronet_binaries']); - Directory('cronet_binaries').createSync(); - - // Do we have tar extraction capability - // in dart's built-in libraries? - final res = - Process.runSync('tar', ['-xvf', fileName, '-C', 'cronet_binaries']); - if (res.exitCode != 0) { - throw Exception( - 'Can\'t unzip. Check if the downloaded file isn\'t corrupted'); - } - print('Done! Cleaning up...'); - - File(fileName).deleteSync(); - // if (platform.startsWith('android')) { - // print(platform); - // moveMobileBinaries(platform); - // } - print('Done! Cronet support for $platform is now available!'); + placeBinaries(platform, fileName); } else { print('Cronet $platform is already available. No need to download.'); } diff --git a/tool/getMobileBinaries.dart b/tool/getMobileBinaries.dart new file mode 100644 index 0000000..6bdf353 --- /dev/null +++ b/tool/getMobileBinaries.dart @@ -0,0 +1,58 @@ + +import 'dart:io' show Directory, File; + +import 'package:cronet_sample/src/prepare_cronet.dart'; +import 'package:path/path.dart'; + +import 'package:cronet_sample/src/find_resource.dart'; + +/// Move binary files for mobiles. Currenly just [Android] is implemented +void placeMobileBinaries(String platform) { + if (platform.startsWith('android')) { + final android = findPackageRoot()!.toFilePath() + 'android'; + + Directory(android + '/libs').createSync(); + + Directory('cronet_binaries/' + platform + '/libs') + .listSync() + .forEach((jar) { + if (jar is File) { + jar.renameSync(android + '/libs/' + basename(jar.path)); + } + }); // move the extracted jars + + Directory(android + '/src/main/jniLibs').createSync(); + + Directory( + 'cronet_binaries/' + platform + '/' + platform.split('android')[1]) + .listSync() + .forEach((cronet) { + if (cronet is File) { + Directory(android + '/src/main/jniLibs/' + platform.split('android')[1]) + .createSync(); + + if (cronet is File) { + cronet.renameSync(android + + '/src/main/jniLibs/' + + platform.split('android')[1] + + '/' + + basename(cronet.path)); + } + } + }); // move cronet binaries + Directory('cronet_binaries/$platform').deleteSync(recursive: true); + } +} + + +void main(List platforms) { + if(platforms.isEmpty) { + print('Please provide list of platforms'); + return; + } + platforms.forEach((platform) async { + await downloadCronetBinaries(platform); + placeMobileBinaries(platform); + }); + +} \ No newline at end of file From ba9e196eae6d6f047cb3cac0e58aa5e965c618bb Mon Sep 17 00:00:00 2001 From: Soumyadip Mondal Date: Thu, 20 May 2021 08:31:08 +0530 Subject: [PATCH 7/7] Adding cronet header files --- .../include/cronet/bidirectional_stream_c.h | 245 ++++ lib/src/native/include/cronet/cronet.idl_c.h | 1284 +++++++++++++++++ lib/src/native/include/cronet/cronet_c.h | 38 + lib/src/native/include/cronet/cronet_export.h | 14 + lib/src/native/include/{ => dart}/dart_api.h | 0 .../native/include/{ => dart}/dart_api_dl.c | 0 .../native/include/{ => dart}/dart_api_dl.h | 0 .../include/{ => dart}/dart_native_api.h | 0 .../include/{ => dart}/dart_tools_api.h | 0 .../native/include/{ => dart}/dart_version.h | 0 .../{ => dart}/runtime/dart_api_dl_impl.h | 0 lib/src/native/wrapper/build.sh | 2 +- lib/src/prepare_cronet.dart | 29 +- pubspec.yaml | 2 +- tool/getMobileBinaries.dart | 9 +- 15 files changed, 1601 insertions(+), 22 deletions(-) create mode 100755 lib/src/native/include/cronet/bidirectional_stream_c.h create mode 100755 lib/src/native/include/cronet/cronet.idl_c.h create mode 100755 lib/src/native/include/cronet/cronet_c.h create mode 100755 lib/src/native/include/cronet/cronet_export.h rename lib/src/native/include/{ => dart}/dart_api.h (100%) rename lib/src/native/include/{ => dart}/dart_api_dl.c (100%) rename lib/src/native/include/{ => dart}/dart_api_dl.h (100%) rename lib/src/native/include/{ => dart}/dart_native_api.h (100%) rename lib/src/native/include/{ => dart}/dart_tools_api.h (100%) rename lib/src/native/include/{ => dart}/dart_version.h (100%) rename lib/src/native/include/{ => dart}/runtime/dart_api_dl_impl.h (100%) diff --git a/lib/src/native/include/cronet/bidirectional_stream_c.h b/lib/src/native/include/cronet/bidirectional_stream_c.h new file mode 100755 index 0000000..3a6fae8 --- /dev/null +++ b/lib/src/native/include/cronet/bidirectional_stream_c.h @@ -0,0 +1,245 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_GRPC_SUPPORT_INCLUDE_BIDIRECTIONAL_STREAM_C_H_ +#define COMPONENTS_GRPC_SUPPORT_INCLUDE_BIDIRECTIONAL_STREAM_C_H_ + +#if defined(WIN32) +#define GRPC_SUPPORT_EXPORT __declspec(dllexport) +#else +#define GRPC_SUPPORT_EXPORT __attribute__((visibility("default"))) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Engine API. */ + +/* Opaque object representing a Bidirectional stream creating engine. Created + * and configured outside of this API to facilitate sharing with other + * components */ +typedef struct stream_engine { + void* obj; + void* annotation; +} stream_engine; + +/* Bidirectional Stream API */ + +/* Opaque object representing Bidirectional Stream. */ +typedef struct bidirectional_stream { + void* obj; + void* annotation; +} bidirectional_stream; + +/* A single request or response header element. */ +typedef struct bidirectional_stream_header { + const char* key; + const char* value; +} bidirectional_stream_header; + +/* Array of request or response headers or trailers. */ +typedef struct bidirectional_stream_header_array { + size_t count; + size_t capacity; + bidirectional_stream_header* headers; +} bidirectional_stream_header_array; + +/* Set of callbacks used to receive callbacks from bidirectional stream. */ +typedef struct bidirectional_stream_callback { + /* Invoked when the stream is ready for reading and writing. + * Consumer may call bidirectional_stream_read() to start reading data. + * Consumer may call bidirectional_stream_write() to start writing + * data. + */ + void (*on_stream_ready)(bidirectional_stream* stream); + + /* Invoked when initial response headers are received. + * Consumer must call bidirectional_stream_read() to start reading. + * Consumer may call bidirectional_stream_write() to start writing or + * close the stream. Contents of |headers| is valid for duration of the call. + */ + void (*on_response_headers_received)( + bidirectional_stream* stream, + const bidirectional_stream_header_array* headers, + const char* negotiated_protocol); + + /* Invoked when data is read into the buffer passed to + * bidirectional_stream_read(). Only part of the buffer may be + * populated. To continue reading, call bidirectional_stream_read(). + * It may be invoked after on_response_trailers_received()}, if there was + * pending read data before trailers were received. + * + * If |bytes_read| is 0, it means the remote side has signaled that it will + * send no more data; future calls to bidirectional_stream_read() + * will result in the on_data_read() callback or on_succeded() callback if + * bidirectional_stream_write() was invoked with end_of_stream set to + * true. + */ + void (*on_read_completed)(bidirectional_stream* stream, + char* data, + int bytes_read); + + /** + * Invoked when all data passed to bidirectional_stream_write() is + * sent. To continue writing, call bidirectional_stream_write(). + */ + void (*on_write_completed)(bidirectional_stream* stream, const char* data); + + /* Invoked when trailers are received before closing the stream. Only invoked + * when server sends trailers, which it may not. May be invoked while there is + * read data remaining in local buffer. Contents of |trailers| is valid for + * duration of the call. + */ + void (*on_response_trailers_received)( + bidirectional_stream* stream, + const bidirectional_stream_header_array* trailers); + + /** + * Invoked when there is no data to be read or written and the stream is + * closed successfully remotely and locally. Once invoked, no further callback + * methods will be invoked. + */ + void (*on_succeded)(bidirectional_stream* stream); + + /** + * Invoked if the stream failed for any reason after + * bidirectional_stream_start(). HTTP/2 error codes are + * mapped to chrome net error codes. Once invoked, no further callback methods + * will be invoked. + */ + void (*on_failed)(bidirectional_stream* stream, int net_error); + + /** + * Invoked if the stream was canceled via + * bidirectional_stream_cancel(). Once invoked, no further callback + * methods will be invoked. + */ + void (*on_canceled)(bidirectional_stream* stream); +} bidirectional_stream_callback; + +/* Creates a new stream object that uses |engine| and |callback|. All stream + * tasks are performed asynchronously on the |engine| network thread. |callback| + * methods are invoked synchronously on the |engine| network thread, but must + * not run tasks on the current thread to prevent blocking networking operations + * and causing exceptions during shutdown. The |annotation| is stored in + * bidirectional stream for arbitrary use by application. + * + * Returned |bidirectional_stream*| is owned by the caller, and must be + * destroyed using |bidirectional_stream_destroy|. + * + * Both |calback| and |engine| must remain valid until stream is destroyed. + */ +GRPC_SUPPORT_EXPORT +bidirectional_stream* bidirectional_stream_create( + stream_engine* engine, + void* annotation, + bidirectional_stream_callback* callback); + +/* TBD: The following methods return int. Should it be a custom type? */ + +/* Destroys stream object. Destroy could be called from any thread, including + * network thread, but is posted, so |stream| is valid until calling task is + * complete. + */ +GRPC_SUPPORT_EXPORT +int bidirectional_stream_destroy(bidirectional_stream* stream); + +/** + * Disables or enables auto flush. By default, data is flushed after + * every bidirectional_stream_write(). If the auto flush is disabled, + * the client should explicitly call bidirectional_stream_flush to flush + * the data. + */ +GRPC_SUPPORT_EXPORT void bidirectional_stream_disable_auto_flush( + bidirectional_stream* stream, + bool disable_auto_flush); + +/** + * Delays sending request headers until bidirectional_stream_flush() + * is called. This flag is currently only respected when QUIC is negotiated. + * When true, QUIC will send request header frame along with data frame(s) + * as a single packet when possible. + */ +GRPC_SUPPORT_EXPORT +void bidirectional_stream_delay_request_headers_until_flush( + bidirectional_stream* stream, + bool delay_headers_until_flush); + +/* Starts the stream by sending request to |url| using |method| and |headers|. + * If |end_of_stream| is true, then no data is expected to be written. The + * |method| is HTTP verb. + */ +GRPC_SUPPORT_EXPORT +int bidirectional_stream_start(bidirectional_stream* stream, + const char* url, + int priority, + const char* method, + const bidirectional_stream_header_array* headers, + bool end_of_stream); + +/* Reads response data into |buffer| of |capacity| length. Must only be called + * at most once in response to each invocation of the + * on_stream_ready()/on_response_headers_received() and on_read_completed() + * methods of the bidirectional_stream_callback. + * Each call will result in an invocation of the callback's + * on_read_completed() method if data is read, or its on_failed() method if + * there's an error. The callback's on_succeeded() method is also invoked if + * there is no more data to read and |end_of_stream| was previously sent. + */ +GRPC_SUPPORT_EXPORT +int bidirectional_stream_read(bidirectional_stream* stream, + char* buffer, + int capacity); + +/* Writes request data from |buffer| of |buffer_length| length. If auto flush is + * disabled, data will be sent only after bidirectional_stream_flush() is + * called. + * Each call will result in an invocation the callback's on_write_completed() + * method if data is sent, or its on_failed() method if there's an error. + * The callback's on_succeeded() method is also invoked if |end_of_stream| is + * set and all response data has been read. + */ +GRPC_SUPPORT_EXPORT +int bidirectional_stream_write(bidirectional_stream* stream, + const char* buffer, + int buffer_length, + bool end_of_stream); + +/** + * Flushes pending writes. This method should not be called before invocation of + * on_stream_ready() method of the bidirectional_stream_callback. + * For each previously called bidirectional_stream_write() + * a corresponding on_write_completed() callback will be invoked when the buffer + * is sent. + */ +GRPC_SUPPORT_EXPORT +void bidirectional_stream_flush(bidirectional_stream* stream); + +/* Cancels the stream. Can be called at any time after + * bidirectional_stream_start(). The on_canceled() method of + * bidirectional_stream_callback will be invoked when cancelation + * is complete and no further callback methods will be invoked. If the + * stream has completed or has not started, calling + * bidirectional_stream_cancel() has no effect and on_canceled() will not + * be invoked. At most one callback method may be invoked after + * bidirectional_stream_cancel() has completed. + */ +GRPC_SUPPORT_EXPORT +void bidirectional_stream_cancel(bidirectional_stream* stream); + +/* Returns true if the |stream| was successfully started and is now done + * (succeeded, canceled, or failed). + * Returns false if the |stream| stream is not yet started or is in progress. + */ +GRPC_SUPPORT_EXPORT +bool bidirectional_stream_is_done(bidirectional_stream* stream); + +#ifdef __cplusplus +} +#endif + +#endif // COMPONENTS_GRPC_SUPPORT_INCLUDE_BIDIRECTIONAL_STREAM_C_H_ diff --git a/lib/src/native/include/cronet/cronet.idl_c.h b/lib/src/native/include/cronet/cronet.idl_c.h new file mode 100755 index 0000000..041fd8f --- /dev/null +++ b/lib/src/native/include/cronet/cronet.idl_c.h @@ -0,0 +1,1284 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* DO NOT EDIT. Generated from components/cronet/native/generated/cronet.idl */ + +#ifndef COMPONENTS_CRONET_NATIVE_GENERATED_CRONET_IDL_C_H_ +#define COMPONENTS_CRONET_NATIVE_GENERATED_CRONET_IDL_C_H_ +#include "cronet_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef const char* Cronet_String; +typedef void* Cronet_RawDataPtr; +typedef void* Cronet_ClientContext; + +// Forward declare interfaces. +typedef struct Cronet_Buffer Cronet_Buffer; +typedef struct Cronet_Buffer* Cronet_BufferPtr; +typedef struct Cronet_BufferCallback Cronet_BufferCallback; +typedef struct Cronet_BufferCallback* Cronet_BufferCallbackPtr; +typedef struct Cronet_Runnable Cronet_Runnable; +typedef struct Cronet_Runnable* Cronet_RunnablePtr; +typedef struct Cronet_Executor Cronet_Executor; +typedef struct Cronet_Executor* Cronet_ExecutorPtr; +typedef struct Cronet_Engine Cronet_Engine; +typedef struct Cronet_Engine* Cronet_EnginePtr; +typedef struct Cronet_UrlRequestStatusListener Cronet_UrlRequestStatusListener; +typedef struct Cronet_UrlRequestStatusListener* + Cronet_UrlRequestStatusListenerPtr; +typedef struct Cronet_UrlRequestCallback Cronet_UrlRequestCallback; +typedef struct Cronet_UrlRequestCallback* Cronet_UrlRequestCallbackPtr; +typedef struct Cronet_UploadDataSink Cronet_UploadDataSink; +typedef struct Cronet_UploadDataSink* Cronet_UploadDataSinkPtr; +typedef struct Cronet_UploadDataProvider Cronet_UploadDataProvider; +typedef struct Cronet_UploadDataProvider* Cronet_UploadDataProviderPtr; +typedef struct Cronet_UrlRequest Cronet_UrlRequest; +typedef struct Cronet_UrlRequest* Cronet_UrlRequestPtr; +typedef struct Cronet_RequestFinishedInfoListener + Cronet_RequestFinishedInfoListener; +typedef struct Cronet_RequestFinishedInfoListener* + Cronet_RequestFinishedInfoListenerPtr; + +// Forward declare structs. +typedef struct Cronet_Error Cronet_Error; +typedef struct Cronet_Error* Cronet_ErrorPtr; +typedef struct Cronet_QuicHint Cronet_QuicHint; +typedef struct Cronet_QuicHint* Cronet_QuicHintPtr; +typedef struct Cronet_PublicKeyPins Cronet_PublicKeyPins; +typedef struct Cronet_PublicKeyPins* Cronet_PublicKeyPinsPtr; +typedef struct Cronet_EngineParams Cronet_EngineParams; +typedef struct Cronet_EngineParams* Cronet_EngineParamsPtr; +typedef struct Cronet_HttpHeader Cronet_HttpHeader; +typedef struct Cronet_HttpHeader* Cronet_HttpHeaderPtr; +typedef struct Cronet_UrlResponseInfo Cronet_UrlResponseInfo; +typedef struct Cronet_UrlResponseInfo* Cronet_UrlResponseInfoPtr; +typedef struct Cronet_UrlRequestParams Cronet_UrlRequestParams; +typedef struct Cronet_UrlRequestParams* Cronet_UrlRequestParamsPtr; +typedef struct Cronet_DateTime Cronet_DateTime; +typedef struct Cronet_DateTime* Cronet_DateTimePtr; +typedef struct Cronet_Metrics Cronet_Metrics; +typedef struct Cronet_Metrics* Cronet_MetricsPtr; +typedef struct Cronet_RequestFinishedInfo Cronet_RequestFinishedInfo; +typedef struct Cronet_RequestFinishedInfo* Cronet_RequestFinishedInfoPtr; + +// Declare enums +typedef enum Cronet_RESULT { + Cronet_RESULT_SUCCESS = 0, + Cronet_RESULT_ILLEGAL_ARGUMENT = -100, + Cronet_RESULT_ILLEGAL_ARGUMENT_STORAGE_PATH_MUST_EXIST = -101, + Cronet_RESULT_ILLEGAL_ARGUMENT_INVALID_PIN = -102, + Cronet_RESULT_ILLEGAL_ARGUMENT_INVALID_HOSTNAME = -103, + Cronet_RESULT_ILLEGAL_ARGUMENT_INVALID_HTTP_METHOD = -104, + Cronet_RESULT_ILLEGAL_ARGUMENT_INVALID_HTTP_HEADER = -105, + Cronet_RESULT_ILLEGAL_STATE = -200, + Cronet_RESULT_ILLEGAL_STATE_STORAGE_PATH_IN_USE = -201, + Cronet_RESULT_ILLEGAL_STATE_CANNOT_SHUTDOWN_ENGINE_FROM_NETWORK_THREAD = -202, + Cronet_RESULT_ILLEGAL_STATE_ENGINE_ALREADY_STARTED = -203, + Cronet_RESULT_ILLEGAL_STATE_REQUEST_ALREADY_STARTED = -204, + Cronet_RESULT_ILLEGAL_STATE_REQUEST_NOT_INITIALIZED = -205, + Cronet_RESULT_ILLEGAL_STATE_REQUEST_ALREADY_INITIALIZED = -206, + Cronet_RESULT_ILLEGAL_STATE_REQUEST_NOT_STARTED = -207, + Cronet_RESULT_ILLEGAL_STATE_UNEXPECTED_REDIRECT = -208, + Cronet_RESULT_ILLEGAL_STATE_UNEXPECTED_READ = -209, + Cronet_RESULT_ILLEGAL_STATE_READ_FAILED = -210, + Cronet_RESULT_NULL_POINTER = -300, + Cronet_RESULT_NULL_POINTER_HOSTNAME = -301, + Cronet_RESULT_NULL_POINTER_SHA256_PINS = -302, + Cronet_RESULT_NULL_POINTER_EXPIRATION_DATE = -303, + Cronet_RESULT_NULL_POINTER_ENGINE = -304, + Cronet_RESULT_NULL_POINTER_URL = -305, + Cronet_RESULT_NULL_POINTER_CALLBACK = -306, + Cronet_RESULT_NULL_POINTER_EXECUTOR = -307, + Cronet_RESULT_NULL_POINTER_METHOD = -308, + Cronet_RESULT_NULL_POINTER_HEADER_NAME = -309, + Cronet_RESULT_NULL_POINTER_HEADER_VALUE = -310, + Cronet_RESULT_NULL_POINTER_PARAMS = -311, + Cronet_RESULT_NULL_POINTER_REQUEST_FINISHED_INFO_LISTENER_EXECUTOR = -312, +} Cronet_RESULT; + +typedef enum Cronet_Error_ERROR_CODE { + Cronet_Error_ERROR_CODE_ERROR_CALLBACK = 0, + Cronet_Error_ERROR_CODE_ERROR_HOSTNAME_NOT_RESOLVED = 1, + Cronet_Error_ERROR_CODE_ERROR_INTERNET_DISCONNECTED = 2, + Cronet_Error_ERROR_CODE_ERROR_NETWORK_CHANGED = 3, + Cronet_Error_ERROR_CODE_ERROR_TIMED_OUT = 4, + Cronet_Error_ERROR_CODE_ERROR_CONNECTION_CLOSED = 5, + Cronet_Error_ERROR_CODE_ERROR_CONNECTION_TIMED_OUT = 6, + Cronet_Error_ERROR_CODE_ERROR_CONNECTION_REFUSED = 7, + Cronet_Error_ERROR_CODE_ERROR_CONNECTION_RESET = 8, + Cronet_Error_ERROR_CODE_ERROR_ADDRESS_UNREACHABLE = 9, + Cronet_Error_ERROR_CODE_ERROR_QUIC_PROTOCOL_FAILED = 10, + Cronet_Error_ERROR_CODE_ERROR_OTHER = 11, +} Cronet_Error_ERROR_CODE; + +typedef enum Cronet_EngineParams_HTTP_CACHE_MODE { + Cronet_EngineParams_HTTP_CACHE_MODE_DISABLED = 0, + Cronet_EngineParams_HTTP_CACHE_MODE_IN_MEMORY = 1, + Cronet_EngineParams_HTTP_CACHE_MODE_DISK_NO_HTTP = 2, + Cronet_EngineParams_HTTP_CACHE_MODE_DISK = 3, +} Cronet_EngineParams_HTTP_CACHE_MODE; + +typedef enum Cronet_UrlRequestParams_REQUEST_PRIORITY { + Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_IDLE = 0, + Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_LOWEST = 1, + Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_LOW = 2, + Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_MEDIUM = 3, + Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_HIGHEST = 4, +} Cronet_UrlRequestParams_REQUEST_PRIORITY; + +typedef enum Cronet_UrlRequestParams_IDEMPOTENCY { + Cronet_UrlRequestParams_IDEMPOTENCY_DEFAULT_IDEMPOTENCY = 0, + Cronet_UrlRequestParams_IDEMPOTENCY_IDEMPOTENT = 1, + Cronet_UrlRequestParams_IDEMPOTENCY_NOT_IDEMPOTENT = 2, +} Cronet_UrlRequestParams_IDEMPOTENCY; + +typedef enum Cronet_RequestFinishedInfo_FINISHED_REASON { + Cronet_RequestFinishedInfo_FINISHED_REASON_SUCCEEDED = 0, + Cronet_RequestFinishedInfo_FINISHED_REASON_FAILED = 1, + Cronet_RequestFinishedInfo_FINISHED_REASON_CANCELED = 2, +} Cronet_RequestFinishedInfo_FINISHED_REASON; + +typedef enum Cronet_UrlRequestStatusListener_Status { + Cronet_UrlRequestStatusListener_Status_INVALID = -1, + Cronet_UrlRequestStatusListener_Status_IDLE = 0, + Cronet_UrlRequestStatusListener_Status_WAITING_FOR_STALLED_SOCKET_POOL = 1, + Cronet_UrlRequestStatusListener_Status_WAITING_FOR_AVAILABLE_SOCKET = 2, + Cronet_UrlRequestStatusListener_Status_WAITING_FOR_DELEGATE = 3, + Cronet_UrlRequestStatusListener_Status_WAITING_FOR_CACHE = 4, + Cronet_UrlRequestStatusListener_Status_DOWNLOADING_PAC_FILE = 5, + Cronet_UrlRequestStatusListener_Status_RESOLVING_PROXY_FOR_URL = 6, + Cronet_UrlRequestStatusListener_Status_RESOLVING_HOST_IN_PAC_FILE = 7, + Cronet_UrlRequestStatusListener_Status_ESTABLISHING_PROXY_TUNNEL = 8, + Cronet_UrlRequestStatusListener_Status_RESOLVING_HOST = 9, + Cronet_UrlRequestStatusListener_Status_CONNECTING = 10, + Cronet_UrlRequestStatusListener_Status_SSL_HANDSHAKE = 11, + Cronet_UrlRequestStatusListener_Status_SENDING_REQUEST = 12, + Cronet_UrlRequestStatusListener_Status_WAITING_FOR_RESPONSE = 13, + Cronet_UrlRequestStatusListener_Status_READING_RESPONSE = 14, +} Cronet_UrlRequestStatusListener_Status; + +// Declare constants + +/////////////////////// +// Concrete interface Cronet_Buffer. + +// Create an instance of Cronet_Buffer. +CRONET_EXPORT Cronet_BufferPtr Cronet_Buffer_Create(void); +// Destroy an instance of Cronet_Buffer. +CRONET_EXPORT void Cronet_Buffer_Destroy(Cronet_BufferPtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_Buffer_SetClientContext( + Cronet_BufferPtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_Buffer_GetClientContext(Cronet_BufferPtr self); +// Concrete methods of Cronet_Buffer implemented by Cronet. +// The app calls them to manipulate Cronet_Buffer. +CRONET_EXPORT +void Cronet_Buffer_InitWithDataAndCallback(Cronet_BufferPtr self, + Cronet_RawDataPtr data, + uint64_t size, + Cronet_BufferCallbackPtr callback); +CRONET_EXPORT +void Cronet_Buffer_InitWithAlloc(Cronet_BufferPtr self, uint64_t size); +CRONET_EXPORT +uint64_t Cronet_Buffer_GetSize(Cronet_BufferPtr self); +CRONET_EXPORT +Cronet_RawDataPtr Cronet_Buffer_GetData(Cronet_BufferPtr self); +// Concrete interface Cronet_Buffer is implemented by Cronet. +// The app can implement these for testing / mocking. +typedef void (*Cronet_Buffer_InitWithDataAndCallbackFunc)( + Cronet_BufferPtr self, + Cronet_RawDataPtr data, + uint64_t size, + Cronet_BufferCallbackPtr callback); +typedef void (*Cronet_Buffer_InitWithAllocFunc)(Cronet_BufferPtr self, + uint64_t size); +typedef uint64_t (*Cronet_Buffer_GetSizeFunc)(Cronet_BufferPtr self); +typedef Cronet_RawDataPtr (*Cronet_Buffer_GetDataFunc)(Cronet_BufferPtr self); +// Concrete interface Cronet_Buffer is implemented by Cronet. +// The app can use this for testing / mocking. +CRONET_EXPORT Cronet_BufferPtr Cronet_Buffer_CreateWith( + Cronet_Buffer_InitWithDataAndCallbackFunc InitWithDataAndCallbackFunc, + Cronet_Buffer_InitWithAllocFunc InitWithAllocFunc, + Cronet_Buffer_GetSizeFunc GetSizeFunc, + Cronet_Buffer_GetDataFunc GetDataFunc); + +/////////////////////// +// Abstract interface Cronet_BufferCallback is implemented by the app. + +// There is no method to create a concrete implementation. + +// Destroy an instance of Cronet_BufferCallback. +CRONET_EXPORT void Cronet_BufferCallback_Destroy(Cronet_BufferCallbackPtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_BufferCallback_SetClientContext( + Cronet_BufferCallbackPtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_BufferCallback_GetClientContext(Cronet_BufferCallbackPtr self); +// Abstract interface Cronet_BufferCallback is implemented by the app. +// The following concrete methods forward call to app implementation. +// The app doesn't normally call them. +CRONET_EXPORT +void Cronet_BufferCallback_OnDestroy(Cronet_BufferCallbackPtr self, + Cronet_BufferPtr buffer); +// The app implements abstract interface Cronet_BufferCallback by defining +// custom functions for each method. +typedef void (*Cronet_BufferCallback_OnDestroyFunc)( + Cronet_BufferCallbackPtr self, + Cronet_BufferPtr buffer); +// The app creates an instance of Cronet_BufferCallback by providing custom +// functions for each method. +CRONET_EXPORT Cronet_BufferCallbackPtr Cronet_BufferCallback_CreateWith( + Cronet_BufferCallback_OnDestroyFunc OnDestroyFunc); + +/////////////////////// +// Abstract interface Cronet_Runnable is implemented by the app. + +// There is no method to create a concrete implementation. + +// Destroy an instance of Cronet_Runnable. +CRONET_EXPORT void Cronet_Runnable_Destroy(Cronet_RunnablePtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_Runnable_SetClientContext( + Cronet_RunnablePtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_Runnable_GetClientContext(Cronet_RunnablePtr self); +// Abstract interface Cronet_Runnable is implemented by the app. +// The following concrete methods forward call to app implementation. +// The app doesn't normally call them. +CRONET_EXPORT +void Cronet_Runnable_Run(Cronet_RunnablePtr self); +// The app implements abstract interface Cronet_Runnable by defining custom +// functions for each method. +typedef void (*Cronet_Runnable_RunFunc)(Cronet_RunnablePtr self); +// The app creates an instance of Cronet_Runnable by providing custom functions +// for each method. +CRONET_EXPORT Cronet_RunnablePtr +Cronet_Runnable_CreateWith(Cronet_Runnable_RunFunc RunFunc); + +/////////////////////// +// Abstract interface Cronet_Executor is implemented by the app. + +// There is no method to create a concrete implementation. + +// Destroy an instance of Cronet_Executor. +CRONET_EXPORT void Cronet_Executor_Destroy(Cronet_ExecutorPtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_Executor_SetClientContext( + Cronet_ExecutorPtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_Executor_GetClientContext(Cronet_ExecutorPtr self); +// Abstract interface Cronet_Executor is implemented by the app. +// The following concrete methods forward call to app implementation. +// The app doesn't normally call them. +CRONET_EXPORT +void Cronet_Executor_Execute(Cronet_ExecutorPtr self, + Cronet_RunnablePtr command); +// The app implements abstract interface Cronet_Executor by defining custom +// functions for each method. +typedef void (*Cronet_Executor_ExecuteFunc)(Cronet_ExecutorPtr self, + Cronet_RunnablePtr command); +// The app creates an instance of Cronet_Executor by providing custom functions +// for each method. +CRONET_EXPORT Cronet_ExecutorPtr +Cronet_Executor_CreateWith(Cronet_Executor_ExecuteFunc ExecuteFunc); + +/////////////////////// +// Concrete interface Cronet_Engine. + +// Create an instance of Cronet_Engine. +CRONET_EXPORT Cronet_EnginePtr Cronet_Engine_Create(void); +// Destroy an instance of Cronet_Engine. +CRONET_EXPORT void Cronet_Engine_Destroy(Cronet_EnginePtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_Engine_SetClientContext( + Cronet_EnginePtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_Engine_GetClientContext(Cronet_EnginePtr self); +// Concrete methods of Cronet_Engine implemented by Cronet. +// The app calls them to manipulate Cronet_Engine. +CRONET_EXPORT +Cronet_RESULT Cronet_Engine_StartWithParams(Cronet_EnginePtr self, + Cronet_EngineParamsPtr params); +CRONET_EXPORT +bool Cronet_Engine_StartNetLogToFile(Cronet_EnginePtr self, + Cronet_String file_name, + bool log_all); +CRONET_EXPORT +void Cronet_Engine_StopNetLog(Cronet_EnginePtr self); +CRONET_EXPORT +Cronet_RESULT Cronet_Engine_Shutdown(Cronet_EnginePtr self); +CRONET_EXPORT +Cronet_String Cronet_Engine_GetVersionString(Cronet_EnginePtr self); +CRONET_EXPORT +Cronet_String Cronet_Engine_GetDefaultUserAgent(Cronet_EnginePtr self); +CRONET_EXPORT +void Cronet_Engine_AddRequestFinishedListener( + Cronet_EnginePtr self, + Cronet_RequestFinishedInfoListenerPtr listener, + Cronet_ExecutorPtr executor); +CRONET_EXPORT +void Cronet_Engine_RemoveRequestFinishedListener( + Cronet_EnginePtr self, + Cronet_RequestFinishedInfoListenerPtr listener); +// Concrete interface Cronet_Engine is implemented by Cronet. +// The app can implement these for testing / mocking. +typedef Cronet_RESULT (*Cronet_Engine_StartWithParamsFunc)( + Cronet_EnginePtr self, + Cronet_EngineParamsPtr params); +typedef bool (*Cronet_Engine_StartNetLogToFileFunc)(Cronet_EnginePtr self, + Cronet_String file_name, + bool log_all); +typedef void (*Cronet_Engine_StopNetLogFunc)(Cronet_EnginePtr self); +typedef Cronet_RESULT (*Cronet_Engine_ShutdownFunc)(Cronet_EnginePtr self); +typedef Cronet_String (*Cronet_Engine_GetVersionStringFunc)( + Cronet_EnginePtr self); +typedef Cronet_String (*Cronet_Engine_GetDefaultUserAgentFunc)( + Cronet_EnginePtr self); +typedef void (*Cronet_Engine_AddRequestFinishedListenerFunc)( + Cronet_EnginePtr self, + Cronet_RequestFinishedInfoListenerPtr listener, + Cronet_ExecutorPtr executor); +typedef void (*Cronet_Engine_RemoveRequestFinishedListenerFunc)( + Cronet_EnginePtr self, + Cronet_RequestFinishedInfoListenerPtr listener); +// Concrete interface Cronet_Engine is implemented by Cronet. +// The app can use this for testing / mocking. +CRONET_EXPORT Cronet_EnginePtr Cronet_Engine_CreateWith( + Cronet_Engine_StartWithParamsFunc StartWithParamsFunc, + Cronet_Engine_StartNetLogToFileFunc StartNetLogToFileFunc, + Cronet_Engine_StopNetLogFunc StopNetLogFunc, + Cronet_Engine_ShutdownFunc ShutdownFunc, + Cronet_Engine_GetVersionStringFunc GetVersionStringFunc, + Cronet_Engine_GetDefaultUserAgentFunc GetDefaultUserAgentFunc, + Cronet_Engine_AddRequestFinishedListenerFunc AddRequestFinishedListenerFunc, + Cronet_Engine_RemoveRequestFinishedListenerFunc + RemoveRequestFinishedListenerFunc); + +/////////////////////// +// Abstract interface Cronet_UrlRequestStatusListener is implemented by the app. + +// There is no method to create a concrete implementation. + +// Destroy an instance of Cronet_UrlRequestStatusListener. +CRONET_EXPORT void Cronet_UrlRequestStatusListener_Destroy( + Cronet_UrlRequestStatusListenerPtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_UrlRequestStatusListener_SetClientContext( + Cronet_UrlRequestStatusListenerPtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_UrlRequestStatusListener_GetClientContext( + Cronet_UrlRequestStatusListenerPtr self); +// Abstract interface Cronet_UrlRequestStatusListener is implemented by the app. +// The following concrete methods forward call to app implementation. +// The app doesn't normally call them. +CRONET_EXPORT +void Cronet_UrlRequestStatusListener_OnStatus( + Cronet_UrlRequestStatusListenerPtr self, + Cronet_UrlRequestStatusListener_Status status); +// The app implements abstract interface Cronet_UrlRequestStatusListener by +// defining custom functions for each method. +typedef void (*Cronet_UrlRequestStatusListener_OnStatusFunc)( + Cronet_UrlRequestStatusListenerPtr self, + Cronet_UrlRequestStatusListener_Status status); +// The app creates an instance of Cronet_UrlRequestStatusListener by providing +// custom functions for each method. +CRONET_EXPORT Cronet_UrlRequestStatusListenerPtr +Cronet_UrlRequestStatusListener_CreateWith( + Cronet_UrlRequestStatusListener_OnStatusFunc OnStatusFunc); + +/////////////////////// +// Abstract interface Cronet_UrlRequestCallback is implemented by the app. + +// There is no method to create a concrete implementation. + +// Destroy an instance of Cronet_UrlRequestCallback. +CRONET_EXPORT void Cronet_UrlRequestCallback_Destroy( + Cronet_UrlRequestCallbackPtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_UrlRequestCallback_SetClientContext( + Cronet_UrlRequestCallbackPtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_UrlRequestCallback_GetClientContext(Cronet_UrlRequestCallbackPtr self); +// Abstract interface Cronet_UrlRequestCallback is implemented by the app. +// The following concrete methods forward call to app implementation. +// The app doesn't normally call them. +CRONET_EXPORT +void Cronet_UrlRequestCallback_OnRedirectReceived( + Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info, + Cronet_String new_location_url); +CRONET_EXPORT +void Cronet_UrlRequestCallback_OnResponseStarted( + Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info); +CRONET_EXPORT +void Cronet_UrlRequestCallback_OnReadCompleted( + Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info, + Cronet_BufferPtr buffer, + uint64_t bytes_read); +CRONET_EXPORT +void Cronet_UrlRequestCallback_OnSucceeded(Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info); +CRONET_EXPORT +void Cronet_UrlRequestCallback_OnFailed(Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info, + Cronet_ErrorPtr error); +CRONET_EXPORT +void Cronet_UrlRequestCallback_OnCanceled(Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info); +// The app implements abstract interface Cronet_UrlRequestCallback by defining +// custom functions for each method. +typedef void (*Cronet_UrlRequestCallback_OnRedirectReceivedFunc)( + Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info, + Cronet_String new_location_url); +typedef void (*Cronet_UrlRequestCallback_OnResponseStartedFunc)( + Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info); +typedef void (*Cronet_UrlRequestCallback_OnReadCompletedFunc)( + Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info, + Cronet_BufferPtr buffer, + uint64_t bytes_read); +typedef void (*Cronet_UrlRequestCallback_OnSucceededFunc)( + Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info); +typedef void (*Cronet_UrlRequestCallback_OnFailedFunc)( + Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info, + Cronet_ErrorPtr error); +typedef void (*Cronet_UrlRequestCallback_OnCanceledFunc)( + Cronet_UrlRequestCallbackPtr self, + Cronet_UrlRequestPtr request, + Cronet_UrlResponseInfoPtr info); +// The app creates an instance of Cronet_UrlRequestCallback by providing custom +// functions for each method. +CRONET_EXPORT Cronet_UrlRequestCallbackPtr Cronet_UrlRequestCallback_CreateWith( + Cronet_UrlRequestCallback_OnRedirectReceivedFunc OnRedirectReceivedFunc, + Cronet_UrlRequestCallback_OnResponseStartedFunc OnResponseStartedFunc, + Cronet_UrlRequestCallback_OnReadCompletedFunc OnReadCompletedFunc, + Cronet_UrlRequestCallback_OnSucceededFunc OnSucceededFunc, + Cronet_UrlRequestCallback_OnFailedFunc OnFailedFunc, + Cronet_UrlRequestCallback_OnCanceledFunc OnCanceledFunc); + +/////////////////////// +// Concrete interface Cronet_UploadDataSink. + +// Create an instance of Cronet_UploadDataSink. +CRONET_EXPORT Cronet_UploadDataSinkPtr Cronet_UploadDataSink_Create(void); +// Destroy an instance of Cronet_UploadDataSink. +CRONET_EXPORT void Cronet_UploadDataSink_Destroy(Cronet_UploadDataSinkPtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_UploadDataSink_SetClientContext( + Cronet_UploadDataSinkPtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_UploadDataSink_GetClientContext(Cronet_UploadDataSinkPtr self); +// Concrete methods of Cronet_UploadDataSink implemented by Cronet. +// The app calls them to manipulate Cronet_UploadDataSink. +CRONET_EXPORT +void Cronet_UploadDataSink_OnReadSucceeded(Cronet_UploadDataSinkPtr self, + uint64_t bytes_read, + bool final_chunk); +CRONET_EXPORT +void Cronet_UploadDataSink_OnReadError(Cronet_UploadDataSinkPtr self, + Cronet_String error_message); +CRONET_EXPORT +void Cronet_UploadDataSink_OnRewindSucceeded(Cronet_UploadDataSinkPtr self); +CRONET_EXPORT +void Cronet_UploadDataSink_OnRewindError(Cronet_UploadDataSinkPtr self, + Cronet_String error_message); +// Concrete interface Cronet_UploadDataSink is implemented by Cronet. +// The app can implement these for testing / mocking. +typedef void (*Cronet_UploadDataSink_OnReadSucceededFunc)( + Cronet_UploadDataSinkPtr self, + uint64_t bytes_read, + bool final_chunk); +typedef void (*Cronet_UploadDataSink_OnReadErrorFunc)( + Cronet_UploadDataSinkPtr self, + Cronet_String error_message); +typedef void (*Cronet_UploadDataSink_OnRewindSucceededFunc)( + Cronet_UploadDataSinkPtr self); +typedef void (*Cronet_UploadDataSink_OnRewindErrorFunc)( + Cronet_UploadDataSinkPtr self, + Cronet_String error_message); +// Concrete interface Cronet_UploadDataSink is implemented by Cronet. +// The app can use this for testing / mocking. +CRONET_EXPORT Cronet_UploadDataSinkPtr Cronet_UploadDataSink_CreateWith( + Cronet_UploadDataSink_OnReadSucceededFunc OnReadSucceededFunc, + Cronet_UploadDataSink_OnReadErrorFunc OnReadErrorFunc, + Cronet_UploadDataSink_OnRewindSucceededFunc OnRewindSucceededFunc, + Cronet_UploadDataSink_OnRewindErrorFunc OnRewindErrorFunc); + +/////////////////////// +// Abstract interface Cronet_UploadDataProvider is implemented by the app. + +// There is no method to create a concrete implementation. + +// Destroy an instance of Cronet_UploadDataProvider. +CRONET_EXPORT void Cronet_UploadDataProvider_Destroy( + Cronet_UploadDataProviderPtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_UploadDataProvider_SetClientContext( + Cronet_UploadDataProviderPtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_UploadDataProvider_GetClientContext(Cronet_UploadDataProviderPtr self); +// Abstract interface Cronet_UploadDataProvider is implemented by the app. +// The following concrete methods forward call to app implementation. +// The app doesn't normally call them. +CRONET_EXPORT +int64_t Cronet_UploadDataProvider_GetLength(Cronet_UploadDataProviderPtr self); +CRONET_EXPORT +void Cronet_UploadDataProvider_Read(Cronet_UploadDataProviderPtr self, + Cronet_UploadDataSinkPtr upload_data_sink, + Cronet_BufferPtr buffer); +CRONET_EXPORT +void Cronet_UploadDataProvider_Rewind( + Cronet_UploadDataProviderPtr self, + Cronet_UploadDataSinkPtr upload_data_sink); +CRONET_EXPORT +void Cronet_UploadDataProvider_Close(Cronet_UploadDataProviderPtr self); +// The app implements abstract interface Cronet_UploadDataProvider by defining +// custom functions for each method. +typedef int64_t (*Cronet_UploadDataProvider_GetLengthFunc)( + Cronet_UploadDataProviderPtr self); +typedef void (*Cronet_UploadDataProvider_ReadFunc)( + Cronet_UploadDataProviderPtr self, + Cronet_UploadDataSinkPtr upload_data_sink, + Cronet_BufferPtr buffer); +typedef void (*Cronet_UploadDataProvider_RewindFunc)( + Cronet_UploadDataProviderPtr self, + Cronet_UploadDataSinkPtr upload_data_sink); +typedef void (*Cronet_UploadDataProvider_CloseFunc)( + Cronet_UploadDataProviderPtr self); +// The app creates an instance of Cronet_UploadDataProvider by providing custom +// functions for each method. +CRONET_EXPORT Cronet_UploadDataProviderPtr Cronet_UploadDataProvider_CreateWith( + Cronet_UploadDataProvider_GetLengthFunc GetLengthFunc, + Cronet_UploadDataProvider_ReadFunc ReadFunc, + Cronet_UploadDataProvider_RewindFunc RewindFunc, + Cronet_UploadDataProvider_CloseFunc CloseFunc); + +/////////////////////// +// Concrete interface Cronet_UrlRequest. + +// Create an instance of Cronet_UrlRequest. +CRONET_EXPORT Cronet_UrlRequestPtr Cronet_UrlRequest_Create(void); +// Destroy an instance of Cronet_UrlRequest. +CRONET_EXPORT void Cronet_UrlRequest_Destroy(Cronet_UrlRequestPtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_UrlRequest_SetClientContext( + Cronet_UrlRequestPtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_UrlRequest_GetClientContext(Cronet_UrlRequestPtr self); +// Concrete methods of Cronet_UrlRequest implemented by Cronet. +// The app calls them to manipulate Cronet_UrlRequest. +CRONET_EXPORT +Cronet_RESULT Cronet_UrlRequest_InitWithParams( + Cronet_UrlRequestPtr self, + Cronet_EnginePtr engine, + Cronet_String url, + Cronet_UrlRequestParamsPtr params, + Cronet_UrlRequestCallbackPtr callback, + Cronet_ExecutorPtr executor); +CRONET_EXPORT +Cronet_RESULT Cronet_UrlRequest_Start(Cronet_UrlRequestPtr self); +CRONET_EXPORT +Cronet_RESULT Cronet_UrlRequest_FollowRedirect(Cronet_UrlRequestPtr self); +CRONET_EXPORT +Cronet_RESULT Cronet_UrlRequest_Read(Cronet_UrlRequestPtr self, + Cronet_BufferPtr buffer); +CRONET_EXPORT +void Cronet_UrlRequest_Cancel(Cronet_UrlRequestPtr self); +CRONET_EXPORT +bool Cronet_UrlRequest_IsDone(Cronet_UrlRequestPtr self); +CRONET_EXPORT +void Cronet_UrlRequest_GetStatus(Cronet_UrlRequestPtr self, + Cronet_UrlRequestStatusListenerPtr listener); +// Concrete interface Cronet_UrlRequest is implemented by Cronet. +// The app can implement these for testing / mocking. +typedef Cronet_RESULT (*Cronet_UrlRequest_InitWithParamsFunc)( + Cronet_UrlRequestPtr self, + Cronet_EnginePtr engine, + Cronet_String url, + Cronet_UrlRequestParamsPtr params, + Cronet_UrlRequestCallbackPtr callback, + Cronet_ExecutorPtr executor); +typedef Cronet_RESULT (*Cronet_UrlRequest_StartFunc)(Cronet_UrlRequestPtr self); +typedef Cronet_RESULT (*Cronet_UrlRequest_FollowRedirectFunc)( + Cronet_UrlRequestPtr self); +typedef Cronet_RESULT (*Cronet_UrlRequest_ReadFunc)(Cronet_UrlRequestPtr self, + Cronet_BufferPtr buffer); +typedef void (*Cronet_UrlRequest_CancelFunc)(Cronet_UrlRequestPtr self); +typedef bool (*Cronet_UrlRequest_IsDoneFunc)(Cronet_UrlRequestPtr self); +typedef void (*Cronet_UrlRequest_GetStatusFunc)( + Cronet_UrlRequestPtr self, + Cronet_UrlRequestStatusListenerPtr listener); +// Concrete interface Cronet_UrlRequest is implemented by Cronet. +// The app can use this for testing / mocking. +CRONET_EXPORT Cronet_UrlRequestPtr Cronet_UrlRequest_CreateWith( + Cronet_UrlRequest_InitWithParamsFunc InitWithParamsFunc, + Cronet_UrlRequest_StartFunc StartFunc, + Cronet_UrlRequest_FollowRedirectFunc FollowRedirectFunc, + Cronet_UrlRequest_ReadFunc ReadFunc, + Cronet_UrlRequest_CancelFunc CancelFunc, + Cronet_UrlRequest_IsDoneFunc IsDoneFunc, + Cronet_UrlRequest_GetStatusFunc GetStatusFunc); + +/////////////////////// +// Abstract interface Cronet_RequestFinishedInfoListener is implemented by the +// app. + +// There is no method to create a concrete implementation. + +// Destroy an instance of Cronet_RequestFinishedInfoListener. +CRONET_EXPORT void Cronet_RequestFinishedInfoListener_Destroy( + Cronet_RequestFinishedInfoListenerPtr self); +// Set and get app-specific Cronet_ClientContext. +CRONET_EXPORT void Cronet_RequestFinishedInfoListener_SetClientContext( + Cronet_RequestFinishedInfoListenerPtr self, + Cronet_ClientContext client_context); +CRONET_EXPORT Cronet_ClientContext +Cronet_RequestFinishedInfoListener_GetClientContext( + Cronet_RequestFinishedInfoListenerPtr self); +// Abstract interface Cronet_RequestFinishedInfoListener is implemented by the +// app. The following concrete methods forward call to app implementation. The +// app doesn't normally call them. +CRONET_EXPORT +void Cronet_RequestFinishedInfoListener_OnRequestFinished( + Cronet_RequestFinishedInfoListenerPtr self, + Cronet_RequestFinishedInfoPtr request_info, + Cronet_UrlResponseInfoPtr response_info, + Cronet_ErrorPtr error); +// The app implements abstract interface Cronet_RequestFinishedInfoListener by +// defining custom functions for each method. +typedef void (*Cronet_RequestFinishedInfoListener_OnRequestFinishedFunc)( + Cronet_RequestFinishedInfoListenerPtr self, + Cronet_RequestFinishedInfoPtr request_info, + Cronet_UrlResponseInfoPtr response_info, + Cronet_ErrorPtr error); +// The app creates an instance of Cronet_RequestFinishedInfoListener by +// providing custom functions for each method. +CRONET_EXPORT Cronet_RequestFinishedInfoListenerPtr +Cronet_RequestFinishedInfoListener_CreateWith( + Cronet_RequestFinishedInfoListener_OnRequestFinishedFunc + OnRequestFinishedFunc); + +/////////////////////// +// Struct Cronet_Error. +CRONET_EXPORT Cronet_ErrorPtr Cronet_Error_Create(void); +CRONET_EXPORT void Cronet_Error_Destroy(Cronet_ErrorPtr self); +// Cronet_Error setters. +CRONET_EXPORT +void Cronet_Error_error_code_set(Cronet_ErrorPtr self, + const Cronet_Error_ERROR_CODE error_code); +CRONET_EXPORT +void Cronet_Error_message_set(Cronet_ErrorPtr self, + const Cronet_String message); +CRONET_EXPORT +void Cronet_Error_internal_error_code_set(Cronet_ErrorPtr self, + const int32_t internal_error_code); +CRONET_EXPORT +void Cronet_Error_immediately_retryable_set(Cronet_ErrorPtr self, + const bool immediately_retryable); +CRONET_EXPORT +void Cronet_Error_quic_detailed_error_code_set( + Cronet_ErrorPtr self, + const int32_t quic_detailed_error_code); +// Cronet_Error getters. +CRONET_EXPORT +Cronet_Error_ERROR_CODE Cronet_Error_error_code_get(const Cronet_ErrorPtr self); +CRONET_EXPORT +Cronet_String Cronet_Error_message_get(const Cronet_ErrorPtr self); +CRONET_EXPORT +int32_t Cronet_Error_internal_error_code_get(const Cronet_ErrorPtr self); +CRONET_EXPORT +bool Cronet_Error_immediately_retryable_get(const Cronet_ErrorPtr self); +CRONET_EXPORT +int32_t Cronet_Error_quic_detailed_error_code_get(const Cronet_ErrorPtr self); + +/////////////////////// +// Struct Cronet_QuicHint. +CRONET_EXPORT Cronet_QuicHintPtr Cronet_QuicHint_Create(void); +CRONET_EXPORT void Cronet_QuicHint_Destroy(Cronet_QuicHintPtr self); +// Cronet_QuicHint setters. +CRONET_EXPORT +void Cronet_QuicHint_host_set(Cronet_QuicHintPtr self, + const Cronet_String host); +CRONET_EXPORT +void Cronet_QuicHint_port_set(Cronet_QuicHintPtr self, const int32_t port); +CRONET_EXPORT +void Cronet_QuicHint_alternate_port_set(Cronet_QuicHintPtr self, + const int32_t alternate_port); +// Cronet_QuicHint getters. +CRONET_EXPORT +Cronet_String Cronet_QuicHint_host_get(const Cronet_QuicHintPtr self); +CRONET_EXPORT +int32_t Cronet_QuicHint_port_get(const Cronet_QuicHintPtr self); +CRONET_EXPORT +int32_t Cronet_QuicHint_alternate_port_get(const Cronet_QuicHintPtr self); + +/////////////////////// +// Struct Cronet_PublicKeyPins. +CRONET_EXPORT Cronet_PublicKeyPinsPtr Cronet_PublicKeyPins_Create(void); +CRONET_EXPORT void Cronet_PublicKeyPins_Destroy(Cronet_PublicKeyPinsPtr self); +// Cronet_PublicKeyPins setters. +CRONET_EXPORT +void Cronet_PublicKeyPins_host_set(Cronet_PublicKeyPinsPtr self, + const Cronet_String host); +CRONET_EXPORT +void Cronet_PublicKeyPins_pins_sha256_add(Cronet_PublicKeyPinsPtr self, + const Cronet_String element); +CRONET_EXPORT +void Cronet_PublicKeyPins_include_subdomains_set(Cronet_PublicKeyPinsPtr self, + const bool include_subdomains); +CRONET_EXPORT +void Cronet_PublicKeyPins_expiration_date_set(Cronet_PublicKeyPinsPtr self, + const int64_t expiration_date); +// Cronet_PublicKeyPins getters. +CRONET_EXPORT +Cronet_String Cronet_PublicKeyPins_host_get(const Cronet_PublicKeyPinsPtr self); +CRONET_EXPORT +uint32_t Cronet_PublicKeyPins_pins_sha256_size( + const Cronet_PublicKeyPinsPtr self); +CRONET_EXPORT +Cronet_String Cronet_PublicKeyPins_pins_sha256_at( + const Cronet_PublicKeyPinsPtr self, + uint32_t index); +CRONET_EXPORT +void Cronet_PublicKeyPins_pins_sha256_clear(Cronet_PublicKeyPinsPtr self); +CRONET_EXPORT +bool Cronet_PublicKeyPins_include_subdomains_get( + const Cronet_PublicKeyPinsPtr self); +CRONET_EXPORT +int64_t Cronet_PublicKeyPins_expiration_date_get( + const Cronet_PublicKeyPinsPtr self); + +/////////////////////// +// Struct Cronet_EngineParams. +CRONET_EXPORT Cronet_EngineParamsPtr Cronet_EngineParams_Create(void); +CRONET_EXPORT void Cronet_EngineParams_Destroy(Cronet_EngineParamsPtr self); +// Cronet_EngineParams setters. +CRONET_EXPORT +void Cronet_EngineParams_enable_check_result_set( + Cronet_EngineParamsPtr self, + const bool enable_check_result); +CRONET_EXPORT +void Cronet_EngineParams_user_agent_set(Cronet_EngineParamsPtr self, + const Cronet_String user_agent); +CRONET_EXPORT +void Cronet_EngineParams_accept_language_set( + Cronet_EngineParamsPtr self, + const Cronet_String accept_language); +CRONET_EXPORT +void Cronet_EngineParams_storage_path_set(Cronet_EngineParamsPtr self, + const Cronet_String storage_path); +CRONET_EXPORT +void Cronet_EngineParams_enable_quic_set(Cronet_EngineParamsPtr self, + const bool enable_quic); +CRONET_EXPORT +void Cronet_EngineParams_enable_http2_set(Cronet_EngineParamsPtr self, + const bool enable_http2); +CRONET_EXPORT +void Cronet_EngineParams_enable_brotli_set(Cronet_EngineParamsPtr self, + const bool enable_brotli); +CRONET_EXPORT +void Cronet_EngineParams_http_cache_mode_set( + Cronet_EngineParamsPtr self, + const Cronet_EngineParams_HTTP_CACHE_MODE http_cache_mode); +CRONET_EXPORT +void Cronet_EngineParams_http_cache_max_size_set( + Cronet_EngineParamsPtr self, + const int64_t http_cache_max_size); +CRONET_EXPORT +void Cronet_EngineParams_quic_hints_add(Cronet_EngineParamsPtr self, + const Cronet_QuicHintPtr element); +CRONET_EXPORT +void Cronet_EngineParams_public_key_pins_add( + Cronet_EngineParamsPtr self, + const Cronet_PublicKeyPinsPtr element); +CRONET_EXPORT +void Cronet_EngineParams_enable_public_key_pinning_bypass_for_local_trust_anchors_set( + Cronet_EngineParamsPtr self, + const bool enable_public_key_pinning_bypass_for_local_trust_anchors); +CRONET_EXPORT +void Cronet_EngineParams_network_thread_priority_set( + Cronet_EngineParamsPtr self, + const double network_thread_priority); +CRONET_EXPORT +void Cronet_EngineParams_experimental_options_set( + Cronet_EngineParamsPtr self, + const Cronet_String experimental_options); +// Cronet_EngineParams getters. +CRONET_EXPORT +bool Cronet_EngineParams_enable_check_result_get( + const Cronet_EngineParamsPtr self); +CRONET_EXPORT +Cronet_String Cronet_EngineParams_user_agent_get( + const Cronet_EngineParamsPtr self); +CRONET_EXPORT +Cronet_String Cronet_EngineParams_accept_language_get( + const Cronet_EngineParamsPtr self); +CRONET_EXPORT +Cronet_String Cronet_EngineParams_storage_path_get( + const Cronet_EngineParamsPtr self); +CRONET_EXPORT +bool Cronet_EngineParams_enable_quic_get(const Cronet_EngineParamsPtr self); +CRONET_EXPORT +bool Cronet_EngineParams_enable_http2_get(const Cronet_EngineParamsPtr self); +CRONET_EXPORT +bool Cronet_EngineParams_enable_brotli_get(const Cronet_EngineParamsPtr self); +CRONET_EXPORT +Cronet_EngineParams_HTTP_CACHE_MODE Cronet_EngineParams_http_cache_mode_get( + const Cronet_EngineParamsPtr self); +CRONET_EXPORT +int64_t Cronet_EngineParams_http_cache_max_size_get( + const Cronet_EngineParamsPtr self); +CRONET_EXPORT +uint32_t Cronet_EngineParams_quic_hints_size(const Cronet_EngineParamsPtr self); +CRONET_EXPORT +Cronet_QuicHintPtr Cronet_EngineParams_quic_hints_at( + const Cronet_EngineParamsPtr self, + uint32_t index); +CRONET_EXPORT +void Cronet_EngineParams_quic_hints_clear(Cronet_EngineParamsPtr self); +CRONET_EXPORT +uint32_t Cronet_EngineParams_public_key_pins_size( + const Cronet_EngineParamsPtr self); +CRONET_EXPORT +Cronet_PublicKeyPinsPtr Cronet_EngineParams_public_key_pins_at( + const Cronet_EngineParamsPtr self, + uint32_t index); +CRONET_EXPORT +void Cronet_EngineParams_public_key_pins_clear(Cronet_EngineParamsPtr self); +CRONET_EXPORT +bool Cronet_EngineParams_enable_public_key_pinning_bypass_for_local_trust_anchors_get( + const Cronet_EngineParamsPtr self); +CRONET_EXPORT +double Cronet_EngineParams_network_thread_priority_get( + const Cronet_EngineParamsPtr self); +CRONET_EXPORT +Cronet_String Cronet_EngineParams_experimental_options_get( + const Cronet_EngineParamsPtr self); + +/////////////////////// +// Struct Cronet_HttpHeader. +CRONET_EXPORT Cronet_HttpHeaderPtr Cronet_HttpHeader_Create(void); +CRONET_EXPORT void Cronet_HttpHeader_Destroy(Cronet_HttpHeaderPtr self); +// Cronet_HttpHeader setters. +CRONET_EXPORT +void Cronet_HttpHeader_name_set(Cronet_HttpHeaderPtr self, + const Cronet_String name); +CRONET_EXPORT +void Cronet_HttpHeader_value_set(Cronet_HttpHeaderPtr self, + const Cronet_String value); +// Cronet_HttpHeader getters. +CRONET_EXPORT +Cronet_String Cronet_HttpHeader_name_get(const Cronet_HttpHeaderPtr self); +CRONET_EXPORT +Cronet_String Cronet_HttpHeader_value_get(const Cronet_HttpHeaderPtr self); + +/////////////////////// +// Struct Cronet_UrlResponseInfo. +CRONET_EXPORT Cronet_UrlResponseInfoPtr Cronet_UrlResponseInfo_Create(void); +CRONET_EXPORT void Cronet_UrlResponseInfo_Destroy( + Cronet_UrlResponseInfoPtr self); +// Cronet_UrlResponseInfo setters. +CRONET_EXPORT +void Cronet_UrlResponseInfo_url_set(Cronet_UrlResponseInfoPtr self, + const Cronet_String url); +CRONET_EXPORT +void Cronet_UrlResponseInfo_url_chain_add(Cronet_UrlResponseInfoPtr self, + const Cronet_String element); +CRONET_EXPORT +void Cronet_UrlResponseInfo_http_status_code_set( + Cronet_UrlResponseInfoPtr self, + const int32_t http_status_code); +CRONET_EXPORT +void Cronet_UrlResponseInfo_http_status_text_set( + Cronet_UrlResponseInfoPtr self, + const Cronet_String http_status_text); +CRONET_EXPORT +void Cronet_UrlResponseInfo_all_headers_list_add( + Cronet_UrlResponseInfoPtr self, + const Cronet_HttpHeaderPtr element); +CRONET_EXPORT +void Cronet_UrlResponseInfo_was_cached_set(Cronet_UrlResponseInfoPtr self, + const bool was_cached); +CRONET_EXPORT +void Cronet_UrlResponseInfo_negotiated_protocol_set( + Cronet_UrlResponseInfoPtr self, + const Cronet_String negotiated_protocol); +CRONET_EXPORT +void Cronet_UrlResponseInfo_proxy_server_set(Cronet_UrlResponseInfoPtr self, + const Cronet_String proxy_server); +CRONET_EXPORT +void Cronet_UrlResponseInfo_received_byte_count_set( + Cronet_UrlResponseInfoPtr self, + const int64_t received_byte_count); +// Cronet_UrlResponseInfo getters. +CRONET_EXPORT +Cronet_String Cronet_UrlResponseInfo_url_get( + const Cronet_UrlResponseInfoPtr self); +CRONET_EXPORT +uint32_t Cronet_UrlResponseInfo_url_chain_size( + const Cronet_UrlResponseInfoPtr self); +CRONET_EXPORT +Cronet_String Cronet_UrlResponseInfo_url_chain_at( + const Cronet_UrlResponseInfoPtr self, + uint32_t index); +CRONET_EXPORT +void Cronet_UrlResponseInfo_url_chain_clear(Cronet_UrlResponseInfoPtr self); +CRONET_EXPORT +int32_t Cronet_UrlResponseInfo_http_status_code_get( + const Cronet_UrlResponseInfoPtr self); +CRONET_EXPORT +Cronet_String Cronet_UrlResponseInfo_http_status_text_get( + const Cronet_UrlResponseInfoPtr self); +CRONET_EXPORT +uint32_t Cronet_UrlResponseInfo_all_headers_list_size( + const Cronet_UrlResponseInfoPtr self); +CRONET_EXPORT +Cronet_HttpHeaderPtr Cronet_UrlResponseInfo_all_headers_list_at( + const Cronet_UrlResponseInfoPtr self, + uint32_t index); +CRONET_EXPORT +void Cronet_UrlResponseInfo_all_headers_list_clear( + Cronet_UrlResponseInfoPtr self); +CRONET_EXPORT +bool Cronet_UrlResponseInfo_was_cached_get( + const Cronet_UrlResponseInfoPtr self); +CRONET_EXPORT +Cronet_String Cronet_UrlResponseInfo_negotiated_protocol_get( + const Cronet_UrlResponseInfoPtr self); +CRONET_EXPORT +Cronet_String Cronet_UrlResponseInfo_proxy_server_get( + const Cronet_UrlResponseInfoPtr self); +CRONET_EXPORT +int64_t Cronet_UrlResponseInfo_received_byte_count_get( + const Cronet_UrlResponseInfoPtr self); + +/////////////////////// +// Struct Cronet_UrlRequestParams. +CRONET_EXPORT Cronet_UrlRequestParamsPtr Cronet_UrlRequestParams_Create(void); +CRONET_EXPORT void Cronet_UrlRequestParams_Destroy( + Cronet_UrlRequestParamsPtr self); +// Cronet_UrlRequestParams setters. +CRONET_EXPORT +void Cronet_UrlRequestParams_http_method_set(Cronet_UrlRequestParamsPtr self, + const Cronet_String http_method); +CRONET_EXPORT +void Cronet_UrlRequestParams_request_headers_add( + Cronet_UrlRequestParamsPtr self, + const Cronet_HttpHeaderPtr element); +CRONET_EXPORT +void Cronet_UrlRequestParams_disable_cache_set(Cronet_UrlRequestParamsPtr self, + const bool disable_cache); +CRONET_EXPORT +void Cronet_UrlRequestParams_priority_set( + Cronet_UrlRequestParamsPtr self, + const Cronet_UrlRequestParams_REQUEST_PRIORITY priority); +CRONET_EXPORT +void Cronet_UrlRequestParams_upload_data_provider_set( + Cronet_UrlRequestParamsPtr self, + const Cronet_UploadDataProviderPtr upload_data_provider); +CRONET_EXPORT +void Cronet_UrlRequestParams_upload_data_provider_executor_set( + Cronet_UrlRequestParamsPtr self, + const Cronet_ExecutorPtr upload_data_provider_executor); +CRONET_EXPORT +void Cronet_UrlRequestParams_allow_direct_executor_set( + Cronet_UrlRequestParamsPtr self, + const bool allow_direct_executor); +CRONET_EXPORT +void Cronet_UrlRequestParams_annotations_add(Cronet_UrlRequestParamsPtr self, + const Cronet_RawDataPtr element); +CRONET_EXPORT +void Cronet_UrlRequestParams_request_finished_listener_set( + Cronet_UrlRequestParamsPtr self, + const Cronet_RequestFinishedInfoListenerPtr request_finished_listener); +CRONET_EXPORT +void Cronet_UrlRequestParams_request_finished_executor_set( + Cronet_UrlRequestParamsPtr self, + const Cronet_ExecutorPtr request_finished_executor); +CRONET_EXPORT +void Cronet_UrlRequestParams_idempotency_set( + Cronet_UrlRequestParamsPtr self, + const Cronet_UrlRequestParams_IDEMPOTENCY idempotency); +// Cronet_UrlRequestParams getters. +CRONET_EXPORT +Cronet_String Cronet_UrlRequestParams_http_method_get( + const Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +uint32_t Cronet_UrlRequestParams_request_headers_size( + const Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +Cronet_HttpHeaderPtr Cronet_UrlRequestParams_request_headers_at( + const Cronet_UrlRequestParamsPtr self, + uint32_t index); +CRONET_EXPORT +void Cronet_UrlRequestParams_request_headers_clear( + Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +bool Cronet_UrlRequestParams_disable_cache_get( + const Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +Cronet_UrlRequestParams_REQUEST_PRIORITY Cronet_UrlRequestParams_priority_get( + const Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +Cronet_UploadDataProviderPtr Cronet_UrlRequestParams_upload_data_provider_get( + const Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +Cronet_ExecutorPtr Cronet_UrlRequestParams_upload_data_provider_executor_get( + const Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +bool Cronet_UrlRequestParams_allow_direct_executor_get( + const Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +uint32_t Cronet_UrlRequestParams_annotations_size( + const Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +Cronet_RawDataPtr Cronet_UrlRequestParams_annotations_at( + const Cronet_UrlRequestParamsPtr self, + uint32_t index); +CRONET_EXPORT +void Cronet_UrlRequestParams_annotations_clear(Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +Cronet_RequestFinishedInfoListenerPtr +Cronet_UrlRequestParams_request_finished_listener_get( + const Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +Cronet_ExecutorPtr Cronet_UrlRequestParams_request_finished_executor_get( + const Cronet_UrlRequestParamsPtr self); +CRONET_EXPORT +Cronet_UrlRequestParams_IDEMPOTENCY Cronet_UrlRequestParams_idempotency_get( + const Cronet_UrlRequestParamsPtr self); + +/////////////////////// +// Struct Cronet_DateTime. +CRONET_EXPORT Cronet_DateTimePtr Cronet_DateTime_Create(void); +CRONET_EXPORT void Cronet_DateTime_Destroy(Cronet_DateTimePtr self); +// Cronet_DateTime setters. +CRONET_EXPORT +void Cronet_DateTime_value_set(Cronet_DateTimePtr self, const int64_t value); +// Cronet_DateTime getters. +CRONET_EXPORT +int64_t Cronet_DateTime_value_get(const Cronet_DateTimePtr self); + +/////////////////////// +// Struct Cronet_Metrics. +CRONET_EXPORT Cronet_MetricsPtr Cronet_Metrics_Create(void); +CRONET_EXPORT void Cronet_Metrics_Destroy(Cronet_MetricsPtr self); +// Cronet_Metrics setters. +CRONET_EXPORT +void Cronet_Metrics_request_start_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr request_start); +// Move data from |request_start|. The caller retains ownership of +// |request_start| and must destroy it. +void Cronet_Metrics_request_start_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr request_start); +CRONET_EXPORT +void Cronet_Metrics_dns_start_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr dns_start); +// Move data from |dns_start|. The caller retains ownership of |dns_start| and +// must destroy it. +void Cronet_Metrics_dns_start_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr dns_start); +CRONET_EXPORT +void Cronet_Metrics_dns_end_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr dns_end); +// Move data from |dns_end|. The caller retains ownership of |dns_end| and must +// destroy it. +void Cronet_Metrics_dns_end_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr dns_end); +CRONET_EXPORT +void Cronet_Metrics_connect_start_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr connect_start); +// Move data from |connect_start|. The caller retains ownership of +// |connect_start| and must destroy it. +void Cronet_Metrics_connect_start_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr connect_start); +CRONET_EXPORT +void Cronet_Metrics_connect_end_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr connect_end); +// Move data from |connect_end|. The caller retains ownership of |connect_end| +// and must destroy it. +void Cronet_Metrics_connect_end_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr connect_end); +CRONET_EXPORT +void Cronet_Metrics_ssl_start_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr ssl_start); +// Move data from |ssl_start|. The caller retains ownership of |ssl_start| and +// must destroy it. +void Cronet_Metrics_ssl_start_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr ssl_start); +CRONET_EXPORT +void Cronet_Metrics_ssl_end_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr ssl_end); +// Move data from |ssl_end|. The caller retains ownership of |ssl_end| and must +// destroy it. +void Cronet_Metrics_ssl_end_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr ssl_end); +CRONET_EXPORT +void Cronet_Metrics_sending_start_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr sending_start); +// Move data from |sending_start|. The caller retains ownership of +// |sending_start| and must destroy it. +void Cronet_Metrics_sending_start_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr sending_start); +CRONET_EXPORT +void Cronet_Metrics_sending_end_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr sending_end); +// Move data from |sending_end|. The caller retains ownership of |sending_end| +// and must destroy it. +void Cronet_Metrics_sending_end_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr sending_end); +CRONET_EXPORT +void Cronet_Metrics_push_start_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr push_start); +// Move data from |push_start|. The caller retains ownership of |push_start| and +// must destroy it. +void Cronet_Metrics_push_start_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr push_start); +CRONET_EXPORT +void Cronet_Metrics_push_end_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr push_end); +// Move data from |push_end|. The caller retains ownership of |push_end| and +// must destroy it. +void Cronet_Metrics_push_end_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr push_end); +CRONET_EXPORT +void Cronet_Metrics_response_start_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr response_start); +// Move data from |response_start|. The caller retains ownership of +// |response_start| and must destroy it. +void Cronet_Metrics_response_start_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr response_start); +CRONET_EXPORT +void Cronet_Metrics_request_end_set(Cronet_MetricsPtr self, + const Cronet_DateTimePtr request_end); +// Move data from |request_end|. The caller retains ownership of |request_end| +// and must destroy it. +void Cronet_Metrics_request_end_move(Cronet_MetricsPtr self, + Cronet_DateTimePtr request_end); +CRONET_EXPORT +void Cronet_Metrics_socket_reused_set(Cronet_MetricsPtr self, + const bool socket_reused); +CRONET_EXPORT +void Cronet_Metrics_sent_byte_count_set(Cronet_MetricsPtr self, + const int64_t sent_byte_count); +CRONET_EXPORT +void Cronet_Metrics_received_byte_count_set(Cronet_MetricsPtr self, + const int64_t received_byte_count); +// Cronet_Metrics getters. +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_request_start_get( + const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_dns_start_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_dns_end_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_connect_start_get( + const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_connect_end_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_ssl_start_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_ssl_end_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_sending_start_get( + const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_sending_end_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_push_start_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_push_end_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_response_start_get( + const Cronet_MetricsPtr self); +CRONET_EXPORT +Cronet_DateTimePtr Cronet_Metrics_request_end_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +bool Cronet_Metrics_socket_reused_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +int64_t Cronet_Metrics_sent_byte_count_get(const Cronet_MetricsPtr self); +CRONET_EXPORT +int64_t Cronet_Metrics_received_byte_count_get(const Cronet_MetricsPtr self); + +/////////////////////// +// Struct Cronet_RequestFinishedInfo. +CRONET_EXPORT Cronet_RequestFinishedInfoPtr +Cronet_RequestFinishedInfo_Create(void); +CRONET_EXPORT void Cronet_RequestFinishedInfo_Destroy( + Cronet_RequestFinishedInfoPtr self); +// Cronet_RequestFinishedInfo setters. +CRONET_EXPORT +void Cronet_RequestFinishedInfo_metrics_set(Cronet_RequestFinishedInfoPtr self, + const Cronet_MetricsPtr metrics); +// Move data from |metrics|. The caller retains ownership of |metrics| and must +// destroy it. +void Cronet_RequestFinishedInfo_metrics_move(Cronet_RequestFinishedInfoPtr self, + Cronet_MetricsPtr metrics); +CRONET_EXPORT +void Cronet_RequestFinishedInfo_annotations_add( + Cronet_RequestFinishedInfoPtr self, + const Cronet_RawDataPtr element); +CRONET_EXPORT +void Cronet_RequestFinishedInfo_finished_reason_set( + Cronet_RequestFinishedInfoPtr self, + const Cronet_RequestFinishedInfo_FINISHED_REASON finished_reason); +// Cronet_RequestFinishedInfo getters. +CRONET_EXPORT +Cronet_MetricsPtr Cronet_RequestFinishedInfo_metrics_get( + const Cronet_RequestFinishedInfoPtr self); +CRONET_EXPORT +uint32_t Cronet_RequestFinishedInfo_annotations_size( + const Cronet_RequestFinishedInfoPtr self); +CRONET_EXPORT +Cronet_RawDataPtr Cronet_RequestFinishedInfo_annotations_at( + const Cronet_RequestFinishedInfoPtr self, + uint32_t index); +CRONET_EXPORT +void Cronet_RequestFinishedInfo_annotations_clear( + Cronet_RequestFinishedInfoPtr self); +CRONET_EXPORT +Cronet_RequestFinishedInfo_FINISHED_REASON +Cronet_RequestFinishedInfo_finished_reason_get( + const Cronet_RequestFinishedInfoPtr self); + +#ifdef __cplusplus +} +#endif + +#endif // COMPONENTS_CRONET_NATIVE_GENERATED_CRONET_IDL_C_H_ diff --git a/lib/src/native/include/cronet/cronet_c.h b/lib/src/native/include/cronet/cronet_c.h new file mode 100755 index 0000000..d9cd111 --- /dev/null +++ b/lib/src/native/include/cronet/cronet_c.h @@ -0,0 +1,38 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_CRONET_NATIVE_INCLUDE_CRONET_C_H_ +#define COMPONENTS_CRONET_NATIVE_INCLUDE_CRONET_C_H_ + +#include "cronet_export.h" + +// Cronet public C API is generated from cronet.idl +#include "cronet.idl_c.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Stream Engine used by Bidirectional Stream C API for GRPC. +typedef struct stream_engine stream_engine; + +// Additional Cronet C API not generated from cronet.idl. + +// Sets net::CertVerifier* raw_mock_cert_verifier for testing of Cronet_Engine. +// Must be called before Cronet_Engine_InitWithParams(). +CRONET_EXPORT void Cronet_Engine_SetMockCertVerifierForTesting( + Cronet_EnginePtr engine, + /* net::CertVerifier* */ void* raw_mock_cert_verifier); + +// Returns "stream_engine" interface for bidirectionsl stream support for GRPC. +// Returned stream engine is owned by Cronet Engine and is only valid until +// Cronet_Engine_Shutdown(). +CRONET_EXPORT stream_engine* Cronet_Engine_GetStreamEngine( + Cronet_EnginePtr engine); + +#ifdef __cplusplus +} +#endif + +#endif // COMPONENTS_CRONET_NATIVE_INCLUDE_CRONET_C_H_ diff --git a/lib/src/native/include/cronet/cronet_export.h b/lib/src/native/include/cronet/cronet_export.h new file mode 100755 index 0000000..68379ae --- /dev/null +++ b/lib/src/native/include/cronet/cronet_export.h @@ -0,0 +1,14 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_CRONET_NATIVE_INCLUDE_CRONET_EXPORT_H_ +#define COMPONENTS_CRONET_NATIVE_INCLUDE_CRONET_EXPORT_H_ + +#if defined(WIN32) +#define CRONET_EXPORT __declspec(dllexport) +#else +#define CRONET_EXPORT __attribute__((visibility("default"))) +#endif + +#endif // COMPONENTS_CRONET_NATIVE_INCLUDE_CRONET_EXPORT_H_ diff --git a/lib/src/native/include/dart_api.h b/lib/src/native/include/dart/dart_api.h similarity index 100% rename from lib/src/native/include/dart_api.h rename to lib/src/native/include/dart/dart_api.h diff --git a/lib/src/native/include/dart_api_dl.c b/lib/src/native/include/dart/dart_api_dl.c similarity index 100% rename from lib/src/native/include/dart_api_dl.c rename to lib/src/native/include/dart/dart_api_dl.c diff --git a/lib/src/native/include/dart_api_dl.h b/lib/src/native/include/dart/dart_api_dl.h similarity index 100% rename from lib/src/native/include/dart_api_dl.h rename to lib/src/native/include/dart/dart_api_dl.h diff --git a/lib/src/native/include/dart_native_api.h b/lib/src/native/include/dart/dart_native_api.h similarity index 100% rename from lib/src/native/include/dart_native_api.h rename to lib/src/native/include/dart/dart_native_api.h diff --git a/lib/src/native/include/dart_tools_api.h b/lib/src/native/include/dart/dart_tools_api.h similarity index 100% rename from lib/src/native/include/dart_tools_api.h rename to lib/src/native/include/dart/dart_tools_api.h diff --git a/lib/src/native/include/dart_version.h b/lib/src/native/include/dart/dart_version.h similarity index 100% rename from lib/src/native/include/dart_version.h rename to lib/src/native/include/dart/dart_version.h diff --git a/lib/src/native/include/runtime/dart_api_dl_impl.h b/lib/src/native/include/dart/runtime/dart_api_dl_impl.h similarity index 100% rename from lib/src/native/include/runtime/dart_api_dl_impl.h rename to lib/src/native/include/dart/runtime/dart_api_dl_impl.h diff --git a/lib/src/native/wrapper/build.sh b/lib/src/native/wrapper/build.sh index cae3c43..a27cf51 100755 --- a/lib/src/native/wrapper/build.sh +++ b/lib/src/native/wrapper/build.sh @@ -5,4 +5,4 @@ if [ $# -le 1 ] exit 2 fi cd $1 -g++ -DCRONET_VERSION=$2 -fPIC -rdynamic -shared -W -o wrapper.so wrapper.cc sample_executor.cc ../include/dart_api_dl.c -ldl -I../include/ -DDART_SHARED_LIB -fpermissive -Wl,-z,origin -Wl,-rpath,'$ORIGIN' -Wl,-rpath,'$ORIGIN/cronet_binaries/linux64/' +g++ -DCRONET_VERSION=$2 -fPIC -rdynamic -shared -W -o wrapper.so wrapper.cc sample_executor.cc ../include/dart/dart_api_dl.c -ldl -I../include/dart/ -DDART_SHARED_LIB -fpermissive -Wl,-z,origin -Wl,-rpath,'$ORIGIN' -Wl,-rpath,'$ORIGIN/cronet_binaries/linux64/' diff --git a/lib/src/prepare_cronet.dart b/lib/src/prepare_cronet.dart index 469da03..d8e59cb 100644 --- a/lib/src/prepare_cronet.dart +++ b/lib/src/prepare_cronet.dart @@ -22,7 +22,8 @@ void buildWrapper() { final wrapperPath = wrapperSourcePath(); print('Building Wrapper...'); - var result = Process.runSync(wrapperPath + '/build.sh', [wrapperPath, _cronet_version]); + var result = Process.runSync( + wrapperPath + '/build.sh', [wrapperPath, _cronet_version]); print(result.stdout); print(result.stderr); print('Copying wrapper to project root...'); @@ -35,21 +36,21 @@ void buildWrapper() { void placeBinaries(String platform, String fileName) { print('Extracting Cronet for $platform'); - // Process.runSync('mkdir', ['-p', 'cronet_binaries']); - Directory('cronet_binaries').createSync(); + // Process.runSync('mkdir', ['-p', 'cronet_binaries']); + Directory('cronet_binaries').createSync(); - // Do we have tar extraction capability - // in dart's built-in libraries? - final res = - Process.runSync('tar', ['-xvf', fileName, '-C', 'cronet_binaries']); - if (res.exitCode != 0) { - throw Exception( - 'Can\'t unzip. Check if the downloaded file isn\'t corrupted'); - } - print('Done! Cleaning up...'); + // Do we have tar extraction capability + // in dart's built-in libraries? + final res = + Process.runSync('tar', ['-xvf', fileName, '-C', 'cronet_binaries']); + if (res.exitCode != 0) { + throw Exception( + 'Can\'t unzip. Check if the downloaded file isn\'t corrupted'); + } + print('Done! Cleaning up...'); - File(fileName).deleteSync(); - print('Done! Cronet support for $platform is now available!'); + File(fileName).deleteSync(); + print('Done! Cronet support for $platform is now available!'); } /// Download [cronet] library diff --git a/pubspec.yaml b/pubspec.yaml index b8ae8e3..7c8e683 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,7 +53,7 @@ ffigen: - 'dart_api_dl.h' - 'lib/src/native/wrapper/wrapper_export.h' compiler-opts: - - '-Ilib/src/native/include/ -DDART_SHARED_LIB' + - '-Ilib/src/native/include/dart/ -DDART_SHARED_LIB' # To add assets to your plugin package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg diff --git a/tool/getMobileBinaries.dart b/tool/getMobileBinaries.dart index 6bdf353..3bbdecd 100644 --- a/tool/getMobileBinaries.dart +++ b/tool/getMobileBinaries.dart @@ -1,4 +1,3 @@ - import 'dart:io' show Directory, File; import 'package:cronet_sample/src/prepare_cronet.dart'; @@ -44,15 +43,13 @@ void placeMobileBinaries(String platform) { } } - void main(List platforms) { - if(platforms.isEmpty) { + if (platforms.isEmpty) { print('Please provide list of platforms'); return; } - platforms.forEach((platform) async { + platforms.forEach((platform) async { await downloadCronetBinaries(platform); placeMobileBinaries(platform); }); - -} \ No newline at end of file +}