ronantakizawa/codereview-bench huggingface dataset:
Repository: ggerganov/whisper.cpp
File: examples/stb_vorbis.c (C)

Code before:
   #endif
}

static void skip(vorb *z, int n)
{
   if (USE_MEMORY(z)) {
      z->stream += n;
      if (z->stream >= z->stream_end) z->eof = 1;
      return;
   }
   #ifndef STB_VORBIS_NO_STDIO
   {
      long x = ftell(z->f);
      fseek(z->f, x+n, SEEK_SET);
   }
   #endif
}

static int set_file_offset(stb_vorbis *f, unsigned int loc)
{
   #ifndef STB_VORBIS_NO_PUSHDATA_API
   if (f->push_mode) return 0;
   #endif
   f->eof = 0;
   if (USE_MEMORY(f)) {
      if (loc >= f->stream_len) {
         f->stream = f->stream_end;
         f->eof = 1;
         return 0;
      } else {
         f->stream = f->stream_start + loc;
         return 1;
      }
   }
   #ifndef STB_VORBIS_NO_STDIO
   if (loc + f->f_start < loc || loc >= 0x80000000) {
      loc = 0x7fffffff;
      f->eof = 1;
   } else {
      loc += f->f_start;
   }
   if (!fseek(f->f, loc, SEEK_SET))
      return 1;
   f->eof = 1;
   fseek(f->f, f->f_start, SEEK_END);
   return 0;
   #endif
}


static uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 };

Review comment (suggestion):
I assume this fixes some warning?

Unfortunately, we should avoid changing the 3rd party code here, since during the next sync with the upstream, the changes will be gone. The alternative option is to propose this change upstream.

Code after:
   #endif
}

static void skip(vorb *z, int n)
{
   if (USE_MEMORY(z)) {
      z->stream += n;
      if (z->stream >= z->stream_end) z->eof = 1;
      return;
   }
   #ifndef STB_VORBIS_NO_STDIO
   {
      long x = ftell(z->f);
      fseek(z->f, x+n, SEEK_SET);
   }
   #endif
}

static int set_file_offset(stb_vorbis *f, unsigned int loc)
{
   #ifndef STB_VORBIS_NO_PUSHDATA_API
   if (f->push_mode) return 0;
   #endif
   f->eof = 0;
   if (USE_MEMORY(f)) {
      if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) {
         f->stream = f->stream_end;
         f->eof = 1;
         return 0;
      } else {
         f->stream = f->stream_start + loc;
         return 1;
      }
   }
   #ifndef STB_VORBIS_NO_STDIO
   if (loc + f->f_start < loc || loc >= 0x80000000) {
      loc = 0x7fffffff;
      f->eof = 1;
   } else {
      loc += f->f_start;
   }
   if (!fseek(f->f, loc, SEEK_SET))
      return 1;
   f->eof = 1;
   fseek(f->f, f->f_start, SEEK_END);
   return 0;
   #endif
}


static uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 };
Repository: ggerganov/whisper.cpp
File: src/whisper.cpp (C++)

Code before:
    whisper_token tid_last;

    std::vector<float> energy; // PCM signal energy
    float no_speech_prob = 0.0f;

    // [EXPERIMENTAL] Token-level timestamps with DTW
    whisper_aheads_masks aheads_masks;
    ggml_tensor * aheads_cross_QKs = nullptr;
    std::vector<float> aheads_cross_QKs_data;

    // [EXPERIMENTAL] speed-up techniques
    int32_t exp_n_audio_ctx = 0; // 0 - use default

    whisper_vad_context * vad_context = nullptr;

    struct vad_segment_info {
        int64_t orig_start;
        int64_t orig_end;
        int64_t vad_start;
        int64_t vad_end;
    };
    std::vector<vad_segment_info> vad_segments;
    bool has_vad_segments = false;

    std::vector<vad_time_mapping> vad_mapping_table;
    bool vad_mapping_table_initialized = false;
};

struct whisper_context {
    int64_t t_load_us  = 0;
    int64_t t_start_us = 0;

    ggml_type wtype = ggml_type::GGML_TYPE_F16; // weight type (FP32 / FP16 / QX)
    ggml_type itype = ggml_type::GGML_TYPE_F16; // intermediate type (FP32 or FP16)

    whisper_context_params params;

    whisper_model model;
    whisper_vocab vocab;

    whisper_state * state = nullptr;

    std::string path_model; // populated by whisper_init_from_file_with_params()
};

struct whisper_global {
    // We save the log callback globally
    ggml_log_callback log_callback = whisper_log_callback_default;
    void * log_callback_user_data = nullptr;
};


Review comment (suggestion):
The `vad_mapping_table_initialized` flag seems equivalent to `has_vad_segments`. We should keep only the `has_vad_segments` flag for simplicity.

Make sure to clear it on line 6641

Code after:
    whisper_token tid_last;

    std::vector<float> energy; // PCM signal energy
    float no_speech_prob = 0.0f;

    // [EXPERIMENTAL] Token-level timestamps with DTW
    whisper_aheads_masks aheads_masks;
    ggml_tensor * aheads_cross_QKs = nullptr;
    std::vector<float> aheads_cross_QKs_data;

    // [EXPERIMENTAL] speed-up techniques
    int32_t exp_n_audio_ctx = 0; // 0 - use default

    whisper_vad_context * vad_context = nullptr;

    struct vad_segment_info {
        int64_t orig_start;
        int64_t orig_end;
        int64_t vad_start;
        int64_t vad_end;
    };
    std::vector<vad_segment_info> vad_segments;
    bool has_vad_segments = false;

    std::vector<vad_time_mapping> vad_mapping_table;
};

struct whisper_context {
    int64_t t_load_us  = 0;
    int64_t t_start_us = 0;

    ggml_type wtype = ggml_type::GGML_TYPE_F16; // weight type (FP32 / FP16 / QX)
    ggml_type itype = ggml_type::GGML_TYPE_F16; // intermediate type (FP32 or FP16)

    whisper_context_params params;

    whisper_model model;
    whisper_vocab vocab;

    whisper_state * state = nullptr;

    std::string path_model; // populated by whisper_init_from_file_with_params()
};

struct whisper_global {
    // We save the log callback globally
    ggml_log_callback log_callback = whisper_log_callback_default;
    void * log_callback_user_data = nullptr;
};

static whisper_global g_state;
Repository: ggerganov/whisper.cpp
File: src/coreml/whisper-compat.m (Objective-C)

Code before:
#import "MLModel+Compat.h"
#import <Foundation/Foundation.h>

@implementation MLModel (Compat)

#if !defined(MAC_OS_X_VERSION_14_00) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_14_00

- (void) predictionFromFeatures:(id<MLFeatureProvider>) input
              completionHandler:(void (^)(id<MLFeatureProvider> output, NSError * error)) completionHandler {
    [NSOperationQueue.new addOperationWithBlock:^{
        NSError *error = nil;
        id<MLFeatureProvider> prediction = [self predictionFromFeatures:input error:&error];

        [NSOperationQueue.mainQueue addOperationWithBlock:^{
            completionHandler(prediction, error);
        }];
    }];
}

- (void) predictionFromFeatures:(id<MLFeatureProvider>) input
                        options:(MLPredictionOptions *) options
              completionHandler:(void (^)(id<MLFeatureProvider> output, NSError * error)) completionHandler {
    [NSOperationQueue.new addOperationWithBlock:^{
        NSError *error = nil;
        id<MLFeatureProvider> prediction = [self predictionFromFeatures:input options:options error:&error];


Review comment (suggestion):
I think this needs to be updated with the changed header name:
```suggestion
#import "whisper-compat.h"
```
This currently generates a compilation error:
```console
/Users/danbev/work/ai/whisper-work/src/coreml/whisper-compat.m:1:9: fatal error: 'MLModel+Compat.h' file not found
    1 | #import "MLModel+Compat.h"
      |         ^~~~~~~~~~~~~~~~~~
1 error generated.
```

Code after:
#import "whisper-compat.h"
#import <Foundation/Foundation.h>

@implementation MLModel (Compat)

#if !defined(MAC_OS_X_VERSION_14_00) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_14_00

- (void) predictionFromFeatures:(id<MLFeatureProvider>) input
              completionHandler:(void (^)(id<MLFeatureProvider> output, NSError * error)) completionHandler {
    [NSOperationQueue.new addOperationWithBlock:^{
        NSError *error = nil;
        id<MLFeatureProvider> prediction = [self predictionFromFeatures:input error:&error];

        [NSOperationQueue.mainQueue addOperationWithBlock:^{
            completionHandler(prediction, error);
        }];
    }];
}

- (void) predictionFromFeatures:(id<MLFeatureProvider>) input
                        options:(MLPredictionOptions *) options
              completionHandler:(void (^)(id<MLFeatureProvider> output, NSError * error)) completionHandler {
    [NSOperationQueue.new addOperationWithBlock:^{
        NSError *error = nil;
        id<MLFeatureProvider> prediction = [self predictionFromFeatures:input options:options error:&error];
Repository: go-gitea/gitea
File: modules/json/json.go (Go)

Code before:
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package json

// Allow "encoding/json" import.
import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"io"

	jsoniter "github.com/json-iterator/go"
)

// Encoder represents an encoder for json
type Encoder interface {
	Encode(v interface{}) error
}

// Decoder represents a decoder for json
type Decoder interface {
	Decode(v interface{}) error
}

// Interface represents an interface to handle json data
type Interface interface {
	Marshal(v interface{}) ([]byte, error)
	Unmarshal(data []byte, v interface{}) error
	NewEncoder(writer io.Writer) Encoder
	NewDecoder(reader io.Reader) Decoder
	Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error
}

var (

Review comment (suggestion):
```suggestion
	"encoding/json" //nolint:depguard
```

Code after:
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package json

// Allow "encoding/json" import.
import (
	"bytes"
	"encoding/binary"
	"encoding/json" //nolint:depguard
	"io"

	jsoniter "github.com/json-iterator/go"
)

// Encoder represents an encoder for json
type Encoder interface {
	Encode(v any) error
}

// Decoder represents a decoder for json
type Decoder interface {
	Decode(v any) error
}

// Interface represents an interface to handle json data
type Interface interface {
	Marshal(v any) ([]byte, error)
	Unmarshal(data []byte, v any) error
	NewEncoder(writer io.Writer) Encoder
	NewDecoder(reader io.Reader) Decoder
	Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error
}

var (
Repository: go-gitea/gitea
File: modules/setting/config_provider.go (Go)

Code before:
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package setting

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/util"

	"gopkg.in/ini.v1"
)

type ConfigKey interface {
	Name() string
	Value() string
	SetValue(v string)

	In(defaultVal string, candidates []string) string
	String() string
	Strings(delim string) []string

	MustString(defaultVal string) string
	MustBool(defaultVal ...bool) bool
	MustInt(defaultVal ...int) int
	MustInt64(defaultVal ...int64) int64
	MustDuration(defaultVal ...time.Duration) time.Duration
}

type ConfigSection interface {
	Name() string
	MapTo(any) error
	HasKey(key string) bool
	NewKey(name, value string) (ConfigKey, error)
	Key(key string) ConfigKey
	Keys() []ConfigKey

Review comment (suggestion):
```suggestion
	"gopkg.in/ini.v1" //nolint:depguard
```

Code after:
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package setting

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/util"

	"gopkg.in/ini.v1" //nolint:depguard
)

type ConfigKey interface {
	Name() string
	Value() string
	SetValue(v string)

	In(defaultVal string, candidates []string) string
	String() string
	Strings(delim string) []string

	MustString(defaultVal string) string
	MustBool(defaultVal ...bool) bool
	MustInt(defaultVal ...int) int
	MustInt64(defaultVal ...int64) int64
	MustDuration(defaultVal ...time.Duration) time.Duration
}

type ConfigSection interface {
	Name() string
	MapTo(any) error
	HasKey(key string) bool
	NewKey(name, value string) (ConfigKey, error)
	Key(key string) ConfigKey
	Keys() []ConfigKey
Repository: go-gitea/gitea
File: services/webtheme/webtheme.go (Go)

Code before:
	"code.gitea.io/gitea/modules/container"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/public"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/util"
)

var (
	availableThemes             []*ThemeMetaInfo
	availableThemeInternalNames container.Set[string]
	themeOnce                   sync.Once
)

const (
	fileNamePrefix = "theme-"
	fileNameSuffix = ".css"
)

type ThemeMetaInfo struct {
	FileName     string
	InternalName string
	DisplayName  string
	IsDarkTheme  bool
}

// extract CSS vars from CSS, taking the last occurence in a file to support combined themes like "auto"
func parseThemeMetaInfoToMap(cssContent string) map[string]string {
	m := map[string]string{}

	for _, v := range []string{"--theme-display-name", "--is-dark-theme"} {
		re := regexp.MustCompile(v + `\s?:\s?["']?([^"';]+)["';]`)
		matches := re.FindAllStringSubmatch(cssContent, -1)
		numMatches := len(matches)
		if numMatches > 0 {
			m[v] = matches[numMatches-1][1]
		}
	}
	return m
}

func defaultThemeMetaInfoByFileName(fileName string) *ThemeMetaInfo {
	themeInfo := &ThemeMetaInfo{
		FileName:     fileName,
		InternalName: strings.TrimSuffix(strings.TrimPrefix(fileName, fileNamePrefix), fileNameSuffix),
	}
	themeInfo.DisplayName = themeInfo.InternalName
	return themeInfo
}

func defaultThemeMetaInfoByInternalName(fileName string) *ThemeMetaInfo {
	return defaultThemeMetaInfoByFileName(fileNamePrefix + fileName + fileNameSuffix)

Review comment (refactor):
```suggestion
// extract CSS vars from CSS, taking the last occurrence in a file to support combined themes like "auto"
```

Code after:
	"code.gitea.io/gitea/modules/container"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/public"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/util"
)

var (
	availableThemes             []*ThemeMetaInfo
	availableThemeInternalNames container.Set[string]
	themeOnce                   sync.Once
)

const (
	fileNamePrefix = "theme-"
	fileNameSuffix = ".css"
)

type ThemeMetaInfo struct {
	FileName     string
	InternalName string
	DisplayName  string
}

func parseThemeMetaInfoToMap(cssContent string) map[string]string {
	/*
		The theme meta info is stored in the CSS file's variables of `gitea-theme-meta-info` element,
		which is a privately defined and is only used by backend to extract the meta info.
		Not using ":root" because it is difficult to parse various ":root" blocks when importing other files,
		it is difficult to control the overriding, and it's difficult to avoid user's customized overridden styles.
	*/
	metaInfoContent := cssContent
	if pos := strings.LastIndex(metaInfoContent, "gitea-theme-meta-info"); pos >= 0 {
		metaInfoContent = metaInfoContent[pos:]
	}

	reMetaInfoItem := `
(
\s*(--[-\w]+)
\s*:
\s*(
("(\\"|[^"])*")
|('(\\'|[^'])*')
|([^'";]+)
)
\s*;
\s*
)
`
	reMetaInfoItem = strings.ReplaceAll(reMetaInfoItem, "\n", "")
	reMetaInfoBlock := `\bgitea-theme-meta-info\s*\{(` + reMetaInfoItem + `+)\}`
Repository: go-gitea/gitea
File: routers/web/repo/issue_suggestions.go (Go)

Code before:
// IssueSuggestions returns a list of issue suggestions
func IssueSuggestions(ctx *context.Context) {
	keyword := ctx.Req.FormValue("q")

	searchOpt := &issue_indexer.SearchOptions{
		Paginator: &db.ListOptions{
			Page:     0,
			PageSize: 5,
		},
		Keyword:  keyword,
		RepoIDs:  []int64{ctx.Repo.Repository.ID},
		IsPull:   nil,
		IsClosed: nil,
		SortBy:   issue_indexer.SortByUpdatedDesc,
	}

	ids, _, err := issue_indexer.SearchIssues(ctx, searchOpt)
	if err != nil {
		ctx.ServerError("SearchIssues", err)
		return
	}
	issues, err := issues_model.GetIssuesByIDs(ctx, ids, true)
	if err != nil {
		ctx.ServerError("FindIssuesByIDs", err)
		return
	}

	suggestions := make([]*issueSuggestion, 0, len(issues))

	for _, issue := range issues {
		suggestion := &issueSuggestion{
			ID:    issue.ID,
			Title: issue.Title,
			State: string(issue.State()),
		}

		if issue.IsPull {
			if err := issue.LoadPullRequest(ctx); err != nil {
				ctx.ServerError("LoadPullRequest", err)
				return
			}
			if issue.PullRequest != nil {
				suggestion.PullRequest = &struct {
					Merged bool `json:"merged"`
					Draft  bool `json:"draft"`
				}{
					Merged: issue.PullRequest.HasMerged,
					Draft:  issue.PullRequest.IsWorkInProgress(ctx),
				}
			}
		}

Review comment (suggestion):
I think we can support both `#123` and `<owner>/<repo>#123` at the same PR. Looks like Github doesn't support `<owner>/<repo>#123` yet.

Code after:
		Merged bool `json:"merged"`
		Draft  bool `json:"draft"`
	} `json:"pull_request,omitempty"`
}

// IssueSuggestions returns a list of issue suggestions
func IssueSuggestions(ctx *context.Context) {
	keyword := ctx.Req.FormValue("q")

	canReadIssues := ctx.Repo.CanRead(unit.TypeIssues)
	canReadPulls := ctx.Repo.CanRead(unit.TypePullRequests)

	var isPull optional.Option[bool]
	if canReadPulls && !canReadIssues {
		isPull = optional.Some(true)
	} else if canReadIssues && !canReadPulls {
		isPull = optional.Some(false)
	}

	searchOpt := &issue_indexer.SearchOptions{
		Paginator: &db.ListOptions{
			Page:     0,
			PageSize: 5,
		},
		Keyword:  keyword,
		RepoIDs:  []int64{ctx.Repo.Repository.ID},
		IsPull:   isPull,
		IsClosed: nil,
		SortBy:   issue_indexer.SortByUpdatedDesc,
	}

	ids, _, err := issue_indexer.SearchIssues(ctx, searchOpt)
	if err != nil {
		ctx.ServerError("SearchIssues", err)
		return
	}
	issues, err := issues_model.GetIssuesByIDs(ctx, ids, true)
	if err != nil {
		ctx.ServerError("FindIssuesByIDs", err)
		return
	}

	suggestions := make([]*issueSuggestion, 0, len(issues))

	for _, issue := range issues {
		suggestion := &issueSuggestion{
			ID:    issue.ID,
			Title: issue.Title,
			State: string(issue.State()),
		}
Repository: go-gitea/gitea
File: routers/web/repo/issue_suggestions.go (Go)

Code before:

package repo

import (
	"net/http"
	"strings"

	"code.gitea.io/gitea/models/db"
	issues_model "code.gitea.io/gitea/models/issues"
	issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
	"code.gitea.io/gitea/services/context"
)

type issueSuggestion struct {
	ID          int64  `json:"id"`
	Title       string `json:"title"`
	State       string `json:"state"`
	PullRequest *struct {
		Merged bool `json:"merged"`
		Draft  bool `json:"draft"`
	} `json:"pull_request,omitempty"`
}

// IssueSuggestions returns a list of issue suggestions
func IssueSuggestions(ctx *context.Context) {
	keyword := strings.ToLower(ctx.Req.FormValue("q"))

	searchOpt := &issue_indexer.SearchOptions{
		Paginator: &db.ListOptions{
			Page:     0,
			PageSize: 5,
		},
		Keyword:  keyword,
		RepoIDs:  []int64{ctx.Repo.Repository.ID},
		IsPull:   nil,
		IsClosed: nil,
		SortBy:   issue_indexer.SortByUpdatedDesc,
	}

	ids, _, err := issue_indexer.SearchIssues(ctx, searchOpt)
	if err != nil {
		ctx.ServerError("SearchIssues", err)
		return
	}
	issues, err := issues_model.GetIssuesByIDs(ctx, ids, true)
	if err != nil {
		ctx.ServerError("FindIssuesByIDs", err)
		return
	}

	suggestions := make([]*issueSuggestion, 0, len(issues))

Review comment (security):
By reading the code, I think it shouldn't do `ToLower`

Because there is a DB indexer, while the content in DB is case-sensitive.

If you do `ToLower` here, then the contents with upper-case would never be correctly fetched.

So, the best approach is to do nothing, do not `ToLower`, and leave the details to the indexers.

Code after:

package repo

import (
	"net/http"

	"code.gitea.io/gitea/models/db"
	issues_model "code.gitea.io/gitea/models/issues"
	"code.gitea.io/gitea/models/unit"
	issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
	"code.gitea.io/gitea/modules/optional"
	"code.gitea.io/gitea/services/context"
)

type issueSuggestion struct {
	ID          int64  `json:"id"`
	Title       string `json:"title"`
	State       string `json:"state"`
	PullRequest *struct {
		Merged bool `json:"merged"`
		Draft  bool `json:"draft"`
	} `json:"pull_request,omitempty"`
}

// IssueSuggestions returns a list of issue suggestions
func IssueSuggestions(ctx *context.Context) {
	keyword := ctx.Req.FormValue("q")

	canReadIssues := ctx.Repo.CanRead(unit.TypeIssues)
	canReadPulls := ctx.Repo.CanRead(unit.TypePullRequests)

	var isPull optional.Option[bool]
	if canReadPulls && !canReadIssues {
		isPull = optional.Some(true)
	} else if canReadIssues && !canReadPulls {
		isPull = optional.Some(false)
	}

	searchOpt := &issue_indexer.SearchOptions{
		Paginator: &db.ListOptions{
			Page:     0,
			PageSize: 5,
		},
		Keyword:  keyword,
		RepoIDs:  []int64{ctx.Repo.Repository.ID},
		IsPull:   isPull,
		IsClosed: nil,
		SortBy:   issue_indexer.SortByUpdatedDesc,
	}

	ids, _, err := issue_indexer.SearchIssues(ctx, searchOpt)
Repository: go-gitea/gitea
File: services/mailer/sender/sender.go (Go)

Code before:
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package sender

import (
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"

	"gopkg.in/gomail.v2"
)

type Sender gomail.Sender

var Send = send

func send(sender Sender, msgs ...*Message) error {
	if setting.MailService == nil {
		log.Error("Mailer: Send is being invoked but mail service hasn't been initialized")
		return nil
	}
	goMsgs := []*gomail.Message{}
	for _, msg := range msgs {
		goMsgs = append(goMsgs, msg.ToMessage())
	}
	return gomail.Send(sender, goMsgs...)
}


Review comment (bug):
It causes panics like "Panic during startup #35768"

I don't know why the `froms` can be empty. Maybe you can figure out the root cause and/or improve " Fix various bugs #36139" / add more comments

Code after:

	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"
)

type Sender interface {
	Send(from string, to []string, msg io.WriterTo) error
}

var Send = send

func send(sender Sender, msgs ...*Message) error {
	if setting.MailService == nil {
		log.Error("Mailer: Send is being invoked but mail service hasn't been initialized")
		return nil
	}
	for _, msg := range msgs {
		m := msg.ToMessage()
		froms := m.GetFrom()
		to, err := m.GetRecipients()
		if err != nil {
			return err
		}

		// TODO: implement sending from multiple addresses
		if err := sender.Send(froms[0].Address, to, m); err != nil {
			return err
		}
	}
	return nil
}