import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:alice/core/alice_utils.dart'; import 'package:alice/helper/alice_alert_helper.dart'; import 'package:alice/helper/alice_conversion_helper.dart'; import 'package:alice/model/alice_http_call.dart'; import 'package:alice/utils/alice_parser.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; class AliceSaveHelper { static const JsonEncoder _encoder = JsonEncoder.withIndent(' '); /// Top level method used to save calls to file static void saveCalls(BuildContext context, List calls, Brightness brightness) { _checkPermissions(context, calls, brightness); } static void _checkPermissions(BuildContext context, List calls, Brightness brightness) async { final status = await Permission.storage.status; if (status.isGranted) { _saveToFile(context, calls, brightness); } else { final status = await Permission.storage.request(); if (status.isGranted) { _saveToFile(context, calls, brightness); } else { AliceAlertHelper.showAlert(context, "Permission error", "Permission not granted. Couldn't save logs.", brightness: brightness); } } } static Future _saveToFile(BuildContext context, List calls, Brightness brightness) async { try { if (calls.isEmpty) { AliceAlertHelper.showAlert(context, "Error", "There are no logs to save", brightness: brightness); return ""; } final bool isAndroid = Platform.isAndroid; final Directory externalDir = await (isAndroid ? getExternalStorageDirectory() as FutureOr : getApplicationDocumentsDirectory()); final String fileName = "alice_log_${DateTime.now().millisecondsSinceEpoch}.txt"; final File file = File("${externalDir.path}/$fileName"); file.createSync(); final IOSink sink = file.openWrite(mode: FileMode.append); sink.write(await _buildAliceLog()); calls.forEach((AliceHttpCall call) { sink.write(_buildCallLog(call)); }); await sink.flush(); await sink.close(); AliceAlertHelper.showAlert(context, "Success", "Successfully saved logs in ${file.path}", secondButtonTitle: isAndroid ? "View file" : null, //secondButtonAction: () => isAndroid ? OpenFile.open(file.path) : null, brightness: brightness); return file.path; } catch (exception) { AliceAlertHelper.showAlert(context, "Error", "Failed to save http calls to file", brightness: brightness); AliceUtils.log(exception.toString()); } return ""; } static Future _buildAliceLog() async { final StringBuffer stringBuffer = StringBuffer(); final packageInfo = await PackageInfo.fromPlatform(); stringBuffer.write("Alice - HTTP Inspector\n"); stringBuffer.write("App name: ${packageInfo.appName}\n"); stringBuffer.write("Package: ${packageInfo.packageName}\n"); stringBuffer.write("Version: ${packageInfo.version}\n"); stringBuffer.write("Build number: ${packageInfo.buildNumber}\n"); stringBuffer.write("Generated: ${DateTime.now().toIso8601String()}\n"); stringBuffer.write("\n"); return stringBuffer.toString(); } static String _buildCallLog(AliceHttpCall call) { final StringBuffer stringBuffer = StringBuffer(); stringBuffer.write("===========================================\n"); stringBuffer.write("Id: ${call.id}\n"); stringBuffer.write("============================================\n"); stringBuffer.write("--------------------------------------------\n"); stringBuffer.write("General data\n"); stringBuffer.write("--------------------------------------------\n"); stringBuffer.write("Server: ${call.server} \n"); stringBuffer.write("Method: ${call.method} \n"); stringBuffer.write("Endpoint: ${call.endpoint} \n"); stringBuffer.write("Client: ${call.client} \n"); stringBuffer.write("Duration ${AliceConversionHelper.formatTime(call.duration)}\n"); stringBuffer.write("Secured connection: ${call.secure}\n"); stringBuffer.write("Completed: ${!call.loading} \n"); stringBuffer.write("--------------------------------------------\n"); stringBuffer.write("Request\n"); stringBuffer.write("--------------------------------------------\n"); stringBuffer.write("Request time: ${call.request!.time}\n"); stringBuffer.write("Request content type: ${call.request!.contentType}\n"); stringBuffer.write("Request cookies: ${_encoder.convert(call.request!.cookies)}\n"); stringBuffer.write("Request headers: ${_encoder.convert(call.request!.headers)}\n"); if (call.request!.queryParameters.isNotEmpty) { stringBuffer.write("Request query params: ${_encoder.convert(call.request!.queryParameters)}\n"); } stringBuffer.write("Request size: ${AliceConversionHelper.formatBytes(call.request!.size)}\n"); stringBuffer.write( "Request body: ${AliceParser.formatBody(call.request!.body, AliceParser.getContentType(call.request!.headers))}\n"); stringBuffer.write("--------------------------------------------\n"); stringBuffer.write("Response\n"); stringBuffer.write("--------------------------------------------\n"); stringBuffer.write("Response time: ${call.response!.time}\n"); stringBuffer.write("Response status: ${call.response!.status}\n"); stringBuffer.write("Response size: ${AliceConversionHelper.formatBytes(call.response!.size)}\n"); stringBuffer.write("Response headers: ${_encoder.convert(call.response!.headers)}\n"); stringBuffer.write( "Response body: ${AliceParser.formatBody(call.response!.body, AliceParser.getContentType(call.response!.headers))}\n"); if (call.error != null) { stringBuffer.write("--------------------------------------------\n"); stringBuffer.write("Error\n"); stringBuffer.write("--------------------------------------------\n"); stringBuffer.write("Error: ${call.error!.error}\n"); if (call.error!.stackTrace != null) { stringBuffer.write("Error stacktrace: ${call.error!.stackTrace}\n"); } } stringBuffer.write("--------------------------------------------\n"); stringBuffer.write("Curl\n"); stringBuffer.write("--------------------------------------------\n"); stringBuffer.write(call.getCurlCommand()); stringBuffer.write("\n"); stringBuffer.write("==============================================\n"); stringBuffer.write("\n"); return stringBuffer.toString(); } static Future buildCallLog(AliceHttpCall call) async { try { return await _buildAliceLog() + _buildCallLog(call); } catch (exception) { return "Failed to generate call log"; } } }