writeHostApi method

  1. @override
void writeHostApi(
  1. GObjectOptions generatorOptions,
  2. Root root,
  3. Indent indent,
  4. Api api, {
  5. required String dartPackageName,
})
override

Writes a single Host Api to indent.

Implementation

@override
void writeHostApi(
  GObjectOptions generatorOptions,
  Root root,
  Indent indent,
  Api api, {
  required String dartPackageName,
}) {
  final String module = _getModule(generatorOptions, dartPackageName);
  final String className = _getClassName(module, api.name);

  final String methodPrefix = _getMethodPrefix(module, api.name);
  final String vtableName = _getVTableName(module, api.name);

  final String codecClassName = _getClassName(module, _codecBaseName);
  final String codecMethodPrefix = _getMethodPrefix(module, _codecBaseName);

  final bool hasAsyncMethod =
      api.methods.any((Method method) => method.isAsynchronous);
  if (hasAsyncMethod) {
    indent.newln();
    _writeObjectStruct(indent, module, '${api.name}ResponseHandle', () {
      indent.writeln('FlBasicMessageChannel* channel;');
      indent.writeln('FlBasicMessageChannelResponseHandle* response_handle;');
    });

    indent.newln();
    _writeDefineType(indent, module, '${api.name}ResponseHandle');

    indent.newln();
    _writeDispose(indent, module, '${api.name}ResponseHandle', () {
      _writeCastSelf(indent, module, '${api.name}ResponseHandle', 'object');
      indent.writeln('g_clear_object(&self->channel);');
      indent.writeln('g_clear_object(&self->response_handle);');
    });

    indent.newln();
    _writeInit(indent, module, '${api.name}ResponseHandle', () {});

    indent.newln();
    _writeClassInit(indent, module, '${api.name}ResponseHandle', () {});

    indent.newln();
    indent.writeScoped(
        'static ${className}ResponseHandle* ${methodPrefix}_response_handle_new(FlBasicMessageChannel* channel, FlBasicMessageChannelResponseHandle* response_handle) {',
        '}', () {
      _writeObjectNew(indent, module, '${api.name}ResponseHandle');
      indent.writeln(
          'self->channel = FL_BASIC_MESSAGE_CHANNEL(g_object_ref(channel));');
      indent.writeln(
          'self->response_handle = FL_BASIC_MESSAGE_CHANNEL_RESPONSE_HANDLE(g_object_ref(response_handle));');
      indent.writeln('return self;');
    });
  }

  for (final Method method in api.methods) {
    final String responseName = _getResponseName(api.name, method.name);
    final String responseClassName = _getClassName(module, responseName);
    final String responseMethodPrefix =
        _getMethodPrefix(module, responseName);

    if (method.isAsynchronous) {
      indent.newln();
      _writeDeclareFinalType(indent, module, responseName);
    }

    indent.newln();
    _writeObjectStruct(indent, module, responseName, () {
      indent.writeln('FlValue* value;');
    });

    indent.newln();
    _writeDefineType(indent, module, responseName);

    indent.newln();
    _writeDispose(indent, module, responseName, () {
      _writeCastSelf(indent, module, responseName, 'object');
      indent.writeln('g_clear_pointer(&self->value, fl_value_unref);');
    });

    indent.newln();
    _writeInit(indent, module, responseName, () {});

    indent.newln();
    _writeClassInit(indent, module, responseName, () {});

    final String returnType = _getType(module, method.returnType);
    indent.newln();
    final List<String> constructorArgs = <String>[
      if (returnType != 'void') '$returnType return_value',
      if (_isNumericListType(method.returnType)) 'size_t return_value_length'
    ];
    indent.writeScoped(
        "${method.isAsynchronous ? 'static ' : ''}$responseClassName* ${responseMethodPrefix}_new(${constructorArgs.join(', ')}) {",
        '}', () {
      _writeObjectNew(indent, module, responseName);
      indent.writeln('self->value = fl_value_new_list();');
      indent.writeln(
          "fl_value_append_take(self->value, ${_makeFlValue(root, module, method.returnType, 'return_value', lengthVariableName: 'return_value_length')});");
      indent.writeln('return self;');
    });

    indent.newln();
    indent.writeScoped(
        '${method.isAsynchronous ? 'static ' : ''}$responseClassName* ${responseMethodPrefix}_new_error(const gchar* code, const gchar* message, FlValue* details) {',
        '}', () {
      _writeObjectNew(indent, module, responseName);
      indent.writeln('self->value = fl_value_new_list();');
      indent.writeln(
          'fl_value_append_take(self->value, fl_value_new_string(code));');
      indent.writeln(
          'fl_value_append_take(self->value, fl_value_new_string(message != nullptr ? message : ""));');
      indent.writeln(
          'fl_value_append_take(self->value, details != nullptr ? fl_value_ref(details) : fl_value_new_null());');
      indent.writeln('return self;');
    });
  }

  indent.newln();
  _writeDeclareFinalType(indent, module, api.name);

  indent.newln();
  _writeObjectStruct(indent, module, api.name, () {
    indent.writeln('const ${className}VTable* vtable;');
    indent.writeln('gpointer user_data;');
    indent.writeln('GDestroyNotify user_data_free_func;');
  });

  indent.newln();
  _writeDefineType(indent, module, api.name);

  indent.newln();
  _writeDispose(indent, module, api.name, () {
    _writeCastSelf(indent, module, api.name, 'object');
    indent.writeScoped('if (self->user_data != nullptr) {', '}', () {
      indent.writeln('self->user_data_free_func(self->user_data);');
    });
    indent.writeln('self->user_data = nullptr;');
  });

  indent.newln();
  _writeInit(indent, module, api.name, () {});

  indent.newln();
  _writeClassInit(indent, module, api.name, () {});

  indent.newln();
  indent.writeScoped(
      'static $className* ${methodPrefix}_new(const $vtableName* vtable, gpointer user_data, GDestroyNotify user_data_free_func) {',
      '}', () {
    _writeObjectNew(indent, module, api.name);
    indent.writeln('self->vtable = vtable;');
    indent.writeln('self->user_data = user_data;');
    indent.writeln('self->user_data_free_func = user_data_free_func;');
    indent.writeln('return self;');
  });

  for (final Method method in api.methods) {
    final String methodName = _getMethodName(method.name);
    final String responseName = _getResponseName(api.name, method.name);
    final String responseClassName = _getClassName(module, responseName);

    indent.newln();
    indent.writeScoped(
        'static void ${methodPrefix}_${methodName}_cb(FlBasicMessageChannel* channel, FlValue* message_, FlBasicMessageChannelResponseHandle* response_handle, gpointer user_data) {',
        '}', () {
      _writeCastSelf(indent, module, api.name, 'user_data');

      indent.newln();
      indent.writeScoped(
          'if (self->vtable == nullptr || self->vtable->$methodName == nullptr) {',
          '}', () {
        indent.writeln('return;');
      });

      indent.newln();
      final List<String> methodArgs = <String>[];
      for (int i = 0; i < method.parameters.length; i++) {
        final Parameter param = method.parameters[i];
        final String paramName = _snakeCaseFromCamelCase(param.name);
        final String paramType = _getType(module, param.type);
        indent.writeln(
            'FlValue* value$i = fl_value_get_list_value(message_, $i);');
        if (_isNullablePrimitiveType(param.type)) {
          final String primitiveType =
              _getType(module, param.type, primitive: true);
          indent.writeln('$paramType $paramName = nullptr;');
          indent.writeln('$primitiveType ${paramName}_value;');
          indent.writeScoped(
              'if (fl_value_get_type(value$i) != FL_VALUE_TYPE_NULL) {', '}',
              () {
            final String paramValue =
                _fromFlValue(module, method.parameters[i].type, 'value$i');
            indent.writeln('${paramName}_value = $paramValue;');
            indent.writeln('$paramName = &${paramName}_value;');
          });
        } else {
          final String paramValue =
              _fromFlValue(module, method.parameters[i].type, 'value$i');
          indent.writeln('$paramType $paramName = $paramValue;');
        }
        methodArgs.add(paramName);
        if (_isNumericListType(method.parameters[i].type)) {
          indent.writeln(
              'size_t ${paramName}_length = fl_value_get_length(value$i);');
          methodArgs.add('${paramName}_length');
        }
      }
      if (method.isAsynchronous) {
        final List<String> vfuncArgs = <String>[];
        vfuncArgs.addAll(methodArgs);
        vfuncArgs.addAll(<String>['handle', 'self->user_data']);
        indent.writeln(
            'g_autoptr(${className}ResponseHandle) handle = ${methodPrefix}_response_handle_new(channel, response_handle);');
        indent.writeln("self->vtable->$methodName(${vfuncArgs.join(', ')});");
      } else {
        final List<String> vfuncArgs = <String>[];
        vfuncArgs.addAll(methodArgs);
        vfuncArgs.add('self->user_data');
        indent.writeln(
            "g_autoptr($responseClassName) response = self->vtable->$methodName(${vfuncArgs.join(', ')});");
        indent.writeScoped('if (response == nullptr) {', '}', () {
          indent.writeln(
              'g_warning("No response returned to %s.%s", "${api.name}", "${method.name}");');
          indent.writeln('return;');
        });

        indent.newln();
        indent.writeln('g_autoptr(GError) error = NULL;');
        indent.writeScoped(
            'if (!fl_basic_message_channel_respond(channel, response_handle, response->value, &error)) {',
            '}', () {
          indent.writeln(
              'g_warning("Failed to send response to %s.%s: %s", "${api.name}", "${method.name}", error->message);');
        });
      }
    });
  }

  indent.newln();
  indent.writeScoped(
      'void ${methodPrefix}_set_method_handlers(FlBinaryMessenger* messenger, const gchar* suffix, const $vtableName* vtable, gpointer user_data, GDestroyNotify user_data_free_func) {',
      '}', () {
    indent.writeln(
        'g_autofree gchar* dot_suffix = suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup("");');
    indent.writeln(
        'g_autoptr($className) api_data = ${methodPrefix}_new(vtable, user_data, user_data_free_func);');

    indent.newln();
    indent.writeln(
        'g_autoptr($codecClassName) codec = ${codecMethodPrefix}_new();');
    for (final Method method in api.methods) {
      final String methodName = _getMethodName(method.name);
      final String channelName =
          makeChannelName(api, method, dartPackageName);
      indent.writeln(
          'g_autofree gchar* ${methodName}_channel_name = g_strdup_printf("$channelName%s", dot_suffix);');
      indent.writeln(
          'g_autoptr(FlBasicMessageChannel) ${methodName}_channel = fl_basic_message_channel_new(messenger, ${methodName}_channel_name, FL_MESSAGE_CODEC(codec));');
      indent.writeln(
          'fl_basic_message_channel_set_message_handler(${methodName}_channel, ${methodPrefix}_${methodName}_cb, g_object_ref(api_data), g_object_unref);');
    }
  });

  indent.newln();
  indent.writeScoped(
      'void ${methodPrefix}_clear_method_handlers(FlBinaryMessenger* messenger, const gchar* suffix) {',
      '}', () {
    indent.writeln(
        'g_autofree gchar* dot_suffix = suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup("");');

    indent.newln();
    indent.writeln(
        'g_autoptr($codecClassName) codec = ${codecMethodPrefix}_new();');
    for (final Method method in api.methods) {
      final String methodName = _getMethodName(method.name);
      final String channelName =
          makeChannelName(api, method, dartPackageName);
      indent.writeln(
          'g_autofree gchar* ${methodName}_channel_name = g_strdup_printf("$channelName%s", dot_suffix);');
      indent.writeln(
          'g_autoptr(FlBasicMessageChannel) ${methodName}_channel = fl_basic_message_channel_new(messenger, ${methodName}_channel_name, FL_MESSAGE_CODEC(codec));');
      indent.writeln(
          'fl_basic_message_channel_set_message_handler(${methodName}_channel, nullptr, nullptr, nullptr);');
    }
  });

  for (final Method method
      in api.methods.where((Method method) => method.isAsynchronous)) {
    final String returnType = _getType(module, method.returnType);
    final String methodName = _getMethodName(method.name);
    final String responseName = _getResponseName(api.name, method.name);
    final String responseClassName = _getClassName(module, responseName);
    final String responseMethodPrefix =
        _getMethodPrefix(module, responseName);

    indent.newln();
    final List<String> respondArgs = <String>[
      '${className}ResponseHandle* response_handle',
      if (returnType != 'void') '$returnType return_value',
      if (_isNumericListType(method.returnType)) 'size_t return_value_length'
    ];
    indent.writeScoped(
        "void ${methodPrefix}_respond_$methodName(${respondArgs.join(', ')}) {",
        '}', () {
      final List<String> returnArgs = <String>[
        if (returnType != 'void') 'return_value',
        if (_isNumericListType(method.returnType)) 'return_value_length'
      ];
      indent.writeln(
          'g_autoptr($responseClassName) response = ${responseMethodPrefix}_new(${returnArgs.join(', ')});');
      indent.writeln('g_autoptr(GError) error = nullptr;');
      indent.writeScoped(
          'if (!fl_basic_message_channel_respond(response_handle->channel, response_handle->response_handle, response->value, &error)) {',
          '}', () {
        indent.writeln(
            'g_warning("Failed to send response to %s.%s: %s", "${api.name}", "${method.name}", error->message);');
      });
    });

    indent.newln();
    final List<String> respondErrorArgs = <String>[
      '${className}ResponseHandle* response_handle',
      'const gchar* code',
      'const gchar* message',
      'FlValue* details'
    ];
    indent.writeScoped(
        "void ${methodPrefix}_respond_error_$methodName(${respondErrorArgs.join(', ')}) {",
        '}', () {
      indent.writeln(
          'g_autoptr($responseClassName) response = ${responseMethodPrefix}_new_error(code, message, details);');
      indent.writeln('g_autoptr(GError) error = nullptr;');
      indent.writeScoped(
          'if (!fl_basic_message_channel_respond(response_handle->channel, response_handle->response_handle, response->value, &error)) {',
          '}', () {
        indent.writeln(
            'g_warning("Failed to send response to %s.%s: %s", "${api.name}", "${method.name}", error->message);');
      });
    });
  }
}