<commit_before>// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package convert

import (
	"net/url"

	"code.gitea.io/gitea/models"
	"code.gitea.io/gitea/models/perm"
	api "code.gitea.io/gitea/modules/structs"
)

// ToNotificationThread convert a Notification to api.NotificationThread
func ToNotificationThread(n *models.Notification) *api.NotificationThread {
	result := &api.NotificationThread{
		ID:        n.ID,
		Unread:    !(n.Status == models.NotificationStatusRead || n.Status == models.NotificationStatusPinned),
		Pinned:    n.Status == models.NotificationStatusPinned,
		UpdatedAt: n.UpdatedUnix.AsTime(),
		URL:       n.APIURL(),
	}

	// since user only get notifications when he has access to use minimal access mode
	if n.Repository != nil {
		result.Repository = ToRepo(n.Repository, perm.AccessModeRead)
	}

	// handle Subject
	switch n.Source {
	case models.NotificationSourceIssue:
		result.Subject = &api.NotificationSubject{Type: api.NotifySubjectIssue}
		if n.Issue != nil {
			result.Subject.Title = n.Issue.Title
			result.Subject.URL = n.Issue.APIURL()
			result.Subject.HTMLURL = n.Issue.HTMLURL()
			result.Subject.State = n.Issue.State()
			comment, err := n.Issue.GetLastComment()
			if err == nil && comment != nil {
				result.Subject.LatestCommentURL = comment.APIURL()
				result.Subject.LatestCommentHTMLURL = comment.HTMLURL()
			}
		}
	case models.NotificationSourcePullRequest:
		result.Subject = &api.NotificationSubject{Type: api.NotifySubjectPull}
		if n.Issue != nil {
			result.Subject.Title = n.Issue.Title
			result.Subject.URL = n.Issue.APIURL()
			result.Subject.HTMLURL = n.Issue.HTMLURL()
			result.Subject.State = n.Issue.State()
			comment, err := n.Issue.GetLastComment()
			if err == nil && comment != nil {
				result.Subject.LatestCommentURL = comment.APIURL()
				result.Subject.LatestCommentHTMLURL = comment.HTMLURL()
			}

			pr, _ := n.Issue.GetPullRequest()
			if pr != nil && pr.HasMerged {
				result.Subject.State = "merged"
			}
		}
	case models.NotificationSourceCommit:
		url := n.Repository.HTMLURL() + "/commit/" + url.PathEscape(n.CommitID)
		result.Subject = &api.NotificationSubject{
			Type:    api.NotifySubjectCommit,
			Title:   n.CommitID,
			URL:     url,
			HTMLURL: url,
		}
	case models.NotificationSourceRepository:
		result.Subject = &api.NotificationSubject{
			Type:  api.NotifySubjectRepository,
			Title: n.Repository.FullName(),
			// FIXME: this is a relative URL, rather useless and inconsistent, but keeping for backwards compat
			URL:     n.Repository.Link(),
			HTMLURL: n.Repository.HTMLURL(),
		}
	}

	return result
}

// ToNotifications convert list of Notification to api.NotificationThread list
func ToNotifications(nl models.NotificationList) []*api.NotificationThread {
	result := make([]*api.NotificationThread, 0, len(nl))
	for _, n := range nl {
		result = append(result, ToNotificationThread(n))
	}
	return result
}
<commit_msg>Nuke the incorrect permission report on /api/v1/notifications (#19761)<commit_after>// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package convert

import (
	"net/url"

	"code.gitea.io/gitea/models"
	"code.gitea.io/gitea/models/perm"
	api "code.gitea.io/gitea/modules/structs"
)

// ToNotificationThread convert a Notification to api.NotificationThread
func ToNotificationThread(n *models.Notification) *api.NotificationThread {
	result := &api.NotificationThread{
		ID:        n.ID,
		Unread:    !(n.Status == models.NotificationStatusRead || n.Status == models.NotificationStatusPinned),
		Pinned:    n.Status == models.NotificationStatusPinned,
		UpdatedAt: n.UpdatedUnix.AsTime(),
		URL:       n.APIURL(),
	}

	// since user only get notifications when he has access to use minimal access mode
	if n.Repository != nil {
		result.Repository = ToRepo(n.Repository, perm.AccessModeRead)

		// This permission is not correct and we should not be reporting it
		for repository := result.Repository; repository != nil; repository = repository.Parent {
			repository.Permissions = nil
		}
	}

	// handle Subject
	switch n.Source {
	case models.NotificationSourceIssue:
		result.Subject = &api.NotificationSubject{Type: api.NotifySubjectIssue}
		if n.Issue != nil {
			result.Subject.Title = n.Issue.Title
			result.Subject.URL = n.Issue.APIURL()
			result.Subject.HTMLURL = n.Issue.HTMLURL()
			result.Subject.State = n.Issue.State()
			comment, err := n.Issue.GetLastComment()
			if err == nil && comment != nil {
				result.Subject.LatestCommentURL = comment.APIURL()
				result.Subject.LatestCommentHTMLURL = comment.HTMLURL()
			}
		}
	case models.NotificationSourcePullRequest:
		result.Subject = &api.NotificationSubject{Type: api.NotifySubjectPull}
		if n.Issue != nil {
			result.Subject.Title = n.Issue.Title
			result.Subject.URL = n.Issue.APIURL()
			result.Subject.HTMLURL = n.Issue.HTMLURL()
			result.Subject.State = n.Issue.State()
			comment, err := n.Issue.GetLastComment()
			if err == nil && comment != nil {
				result.Subject.LatestCommentURL = comment.APIURL()
				result.Subject.LatestCommentHTMLURL = comment.HTMLURL()
			}

			pr, _ := n.Issue.GetPullRequest()
			if pr != nil && pr.HasMerged {
				result.Subject.State = "merged"
			}
		}
	case models.NotificationSourceCommit:
		url := n.Repository.HTMLURL() + "/commit/" + url.PathEscape(n.CommitID)
		result.Subject = &api.NotificationSubject{
			Type:    api.NotifySubjectCommit,
			Title:   n.CommitID,
			URL:     url,
			HTMLURL: url,
		}
	case models.NotificationSourceRepository:
		result.Subject = &api.NotificationSubject{
			Type:  api.NotifySubjectRepository,
			Title: n.Repository.FullName(),
			// FIXME: this is a relative URL, rather useless and inconsistent, but keeping for backwards compat
			URL:     n.Repository.Link(),
			HTMLURL: n.Repository.HTMLURL(),
		}
	}

	return result
}

// ToNotifications convert list of Notification to api.NotificationThread list
func ToNotifications(nl models.NotificationList) []*api.NotificationThread {
	result := make([]*api.NotificationThread, 0, len(nl))
	for _, n := range nl {
		result = append(result, ToNotificationThread(n))
	}
	return result
}
<|endoftext|>


<commit_before>package plugins

import (
	"fmt"
	"strings"
	"time"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/models"
	"github.com/bwmarrin/discordgo"
	rethink "github.com/gorethink/gorethink"
)

type Troublemaker struct{}

func (t *Troublemaker) Commands() []string {
	return []string{
		"troublemaker",
		"troublemakers",
		"tm",
	}
}

type DB_Troublemaker_Entry struct {
	ID                string    `gorethink:"id,omitempty"`
	UserID            string    `gorethink:"userid"`
	Reason            string    `gorethink:"reason"`
	CreatedAt         time.Time `gorethink:"createdat"`
	ReportedByGuildID string    `gorethink:"reportedby_guildid"`
	ReportedByUserID  string    `gorethink:"reportedby_userid"`
}

func (t *Troublemaker) Init(session *discordgo.Session) {

}

func (t *Troublemaker) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
	if !helpers.ModuleIsAllowed(msg.ChannelID, msg.ID, msg.Author.ID, helpers.ModulePermTroublemaker) {
		return
	}

	args := strings.Fields(content)
	if len(args) >= 1 {
		switch args[0] {
		case "participate":
			helpers.RequireAdmin(msg, func() {
				session.ChannelTyping(msg.ChannelID)

				channel, err := helpers.GetChannel(msg.ChannelID)
				helpers.Relax(err)

				settings := helpers.GuildSettingsGetCached(channel.GuildID)

				if len(args) >= 2 {
					// Set new log channel
					targetChannel, err := helpers.GetChannelFromMention(msg, args[1])
					if err != nil || targetChannel.ID == "" {
						helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
						return
					}

					previousChannel := settings.TroublemakerLogChannel

					settings.TroublemakerIsParticipating = true
					settings.TroublemakerLogChannel = targetChannel.ID
					err = helpers.GuildSettingsSet(channel.GuildID, settings)
					helpers.Relax(err)

					changes := make([]models.ElasticEventlogChange, 0)
					if previousChannel != "" {
						changes = []models.ElasticEventlogChange{
							{
								Key:      "troublemaker_participate_channel",
								OldValue: previousChannel,
								NewValue: settings.TroublemakerLogChannel,
							},
						}
					}

					changes = append(changes, models.ElasticEventlogChange{
						Key:      "troublemaker_participating",
						OldValue: helpers.StoreBoolAsString(false),
						NewValue: helpers.StoreBoolAsString(true),
					})

					_, err = helpers.EventlogLog(time.Now(), channel.GuildID, channel.GuildID,
						models.EventlogTargetTypeGuild, msg.Author.ID,
						models.EventlogTypeRobyulTroublemakerParticipate, "",
						changes,
						[]models.ElasticEventlogOption{
							{
								Key:   "troublemaker_participate_channel",
								Value: targetChannel.ID,
							},
						}, false)
					helpers.RelaxLog(err)

					_, err = helpers.SendMessage(msg.ChannelID, helpers.GetText("plugins.troublemaker.participation-enabled"))
					helpers.Relax(err)

					return
				} else {
					// Disable logging
					settings.TroublemakerIsParticipating = false
					err = helpers.GuildSettingsSet(channel.GuildID, settings)
					helpers.Relax(err)

					_, err = helpers.EventlogLog(time.Now(), channel.GuildID, channel.GuildID,
						models.EventlogTargetTypeGuild, msg.Author.ID,
						models.EventlogTypeRobyulTroublemakerParticipate, "",
						[]models.ElasticEventlogChange{
							{
								Key:      "troublemaker_participating",
								OldValue: helpers.StoreBoolAsString(true),
								NewValue: helpers.StoreBoolAsString(false),
							},
						},
						nil, false)
					helpers.RelaxLog(err)

					_, err = helpers.SendMessage(msg.ChannelID, helpers.GetText("plugins.troublemaker.participation-disabled"))
					helpers.Relax(err)

					return
				}
				return
			})
			break
		case "list":
			helpers.RequireMod(msg, func() {
				session.ChannelTyping(msg.ChannelID)

				if len(args) < 2 {
					helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
					return
				}

				targetUser, err := helpers.GetUserFromMention(args[1])
				if err != nil || targetUser.ID == "" {
					helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
					return
				}

				troublemakerReports := t.getTroublemakerReports(targetUser)

				if len(troublemakerReports) <= 0 {
					_, err := helpers.SendMessage(msg.ChannelID, helpers.GetTextF("plugins.troublemaker.list-no-reports", targetUser.Username))
					helpers.Relax(err)
					return
				} else {
					troublemakerText := fmt.Sprintf("I found %d report(s) for `%s#%s` `#%s` <@%s>:\n",
						len(troublemakerReports), targetUser.Username, targetUser.Discriminator, targetUser.ID, targetUser.ID,
					)

					for _, troublemakerReport := range troublemakerReports {
						reportedByUser, err := helpers.GetUser(troublemakerReport.ReportedByUserID)
						if err != nil {
							reportedByUser = new(discordgo.User)
							reportedByUser.ID = troublemakerReport.ReportedByUserID
							reportedByUser.Username = "N/A"
						}
						reportedByGuild, err := helpers.GetGuild(troublemakerReport.ReportedByGuildID)
						if err != nil {
							reportedByGuild = new(discordgo.Guild)
							reportedByGuild.ID = troublemakerReport.ReportedByGuildID
							reportedByGuild.Name = "N/A"
						}
						troublemakerText += fmt.Sprintf("At: `%s`, Reason: `%s`, Reported By: `%s` (`#%s`) On: `%s` (`#%s`)\n",
							troublemakerReport.CreatedAt.Format(time.ANSIC), troublemakerReport.Reason,
							reportedByUser.Username, reportedByUser.ID, reportedByGuild.Name, reportedByGuild.ID,
						)
					}

					for _, page := range helpers.Pagify(troublemakerText, "\n") {
						_, err := helpers.SendMessage(msg.ChannelID, page)
						helpers.Relax(err)
					}
				}
			})
		default:
			helpers.RequireMod(msg, func() {
				session.ChannelTyping(msg.ChannelID)

				channel, err := helpers.GetChannel(msg.ChannelID)
				helpers.Relax(err)
				guild, err := helpers.GetGuild(channel.GuildID)
				helpers.Relax(err)

				if len(args) < 2 {
					helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
					return
				}

				targetUser, err := helpers.GetUserFromMention(args[0])
				if err != nil || targetUser.ID == "" {
					helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
					return
				}

				reasonText := strings.TrimSpace(strings.Replace(content, strings.Join(args[:1], " "), "", 1))

				if helpers.ConfirmEmbed(msg.ChannelID, msg.Author, helpers.GetTextF("plugins.troublemaker.report-confirm",
					targetUser.Username, targetUser.Discriminator, targetUser.ID, targetUser.ID, reasonText,
				), "✅", "🚫") == true {
					// Save to log DB
					troublemakerLogEntry := t.getEntryByOrCreateEmpty("id", "")
					troublemakerLogEntry.UserID = targetUser.ID
					troublemakerLogEntry.Reason = reasonText
					troublemakerLogEntry.CreatedAt = time.Now()
					troublemakerLogEntry.ReportedByGuildID = guild.ID
					troublemakerLogEntry.ReportedByUserID = msg.Author.ID
					t.setEntry(troublemakerLogEntry)

					cache.GetLogger().WithField("module", "troublemaker").Info(fmt.Sprintf("will notify about troublemaker %s (#%s) by %s (#%s) on %s (#%s) reason %s",
						targetUser.Username, targetUser.ID,
						msg.Author.Username, msg.Author.ID,
						guild.Name, guild.ID,
						reasonText,
					))

					guildsToNotify := make([]*discordgo.Guild, 0)

					for _, guildToNotify := range session.State.Guilds {
						if guildToNotify.ID != guild.ID {
							guildToNotifySettings := helpers.GuildSettingsGetCached(guildToNotify.ID)
							if guildToNotifySettings.TroublemakerIsParticipating == true && guildToNotifySettings.TroublemakerLogChannel != "" {
								guildsToNotify = append(guildsToNotify, guildToNotify)
							}
						}
					}
					_, err = helpers.EventlogLog(time.Now(), channel.GuildID, targetUser.ID,
						models.EventlogTargetTypeUser, msg.Author.ID,
						models.EventlogTypeRobyulTroublemakerReport, reasonText,
						nil,
						nil, false)
					helpers.RelaxLog(err)

					successMessages, _ := helpers.SendMessage(msg.ChannelID, helpers.GetTextF("plugins.troublemaker.report-successful", len(guildsToNotify)))

					// Send notifications out
					go func() {
						defer helpers.Recover()

						for _, guildToNotify := range guildsToNotify {
							guildToNotifySettings := helpers.GuildSettingsGetCached(guildToNotify.ID)
							if guildToNotifySettings.TroublemakerIsParticipating == true && guildToNotifySettings.TroublemakerLogChannel != "" {
								targetUserIsOnServer := false
								_, err := helpers.GetGuildMember(guildToNotify.ID, targetUser.ID)
								if err == nil {
									targetUserIsOnServer = true
								}

								reportEmbed := &discordgo.MessageEmbed{
									Title:       helpers.GetTextF("plugins.troublemaker.report-embed-title", targetUser.Username, targetUser.Discriminator),
									Description: helpers.GetTextF("plugins.troublemaker.report-embed-description", targetUser.ID, targetUser.ID),
									URL:         helpers.GetAvatarUrl(targetUser),
									Thumbnail:   &discordgo.MessageEmbedThumbnail{URL: helpers.GetAvatarUrl(targetUser)},
									Footer:      &discordgo.MessageEmbedFooter{Text: helpers.GetTextF("plugins.troublemaker.report-embed-footer", len(guildsToNotify))},
									Color:       0x0FADED,
									Fields: []*discordgo.MessageEmbedField{
										{Name: "Reason stated", Value: reasonText, Inline: false},
									},
								}

								if targetUserIsOnServer == true {
									reportEmbed.Fields = append(reportEmbed.Fields, &discordgo.MessageEmbedField{
										Name: "Member status", Value: ":warning: User is on this server", Inline: false,
									})
								} else {
									reportEmbed.Fields = append(reportEmbed.Fields, &discordgo.MessageEmbedField{
										Name: "Member status", Value: ":white_check_mark: User is not on this server", Inline: false,
									})
								}

								reportEmbed.Fields = append(reportEmbed.Fields, &discordgo.MessageEmbedField{
									Name: "Reported by", Value: fmt.Sprintf("**%s** on **%s**",
										msg.Author.Username, guild.Name,
									), Inline: false})

								_, err = helpers.SendEmbed(guildToNotifySettings.TroublemakerLogChannel, reportEmbed)
								if err != nil {
									cache.GetLogger().WithField("module", "troublemaker").Warnf("Failed to send troublemaker report to channel #%s on guild #%s: %s"+
										guildToNotifySettings.TroublemakerLogChannel, guildToNotifySettings.Guild, err.Error())
								}
							}
						}

						if len(successMessages) > 0 {
							session.MessageReactionAdd(msg.ChannelID, successMessages[0].ID, "👌")
						}
						return
					}()
					return
				}
				return
			})
			break
		}
	}
}

func (t *Troublemaker) getTroublemakerReports(user *discordgo.User) []DB_Troublemaker_Entry {
	var entryBucket []DB_Troublemaker_Entry
	listCursor, err := rethink.Table("troublemakerlog").Filter(
		rethink.Row.Field("userid").Eq(user.ID),
	).Run(helpers.GetDB())
	helpers.Relax(err)
	defer listCursor.Close()
	listCursor.All(&entryBucket)

	return entryBucket
}

func (t *Troublemaker) getEntryByOrCreateEmpty(key string, id string) DB_Troublemaker_Entry {
	var entryBucket DB_Troublemaker_Entry
	listCursor, err := rethink.Table("troublemakerlog").Filter(
		rethink.Row.Field(key).Eq(id),
	).Run(helpers.GetDB())
	if err != nil {
		panic(err)
	}
	defer listCursor.Close()
	err = listCursor.One(&entryBucket)

	// If user has no DB entries create an empty document
	if err == rethink.ErrEmptyResult {
		insert := rethink.Table("troublemakerlog").Insert(DB_Troublemaker_Entry{})
		res, e := insert.RunWrite(helpers.GetDB())
		// If the creation was successful read the document
		if e != nil {
			panic(e)
		} else {
			return t.getEntryByOrCreateEmpty("id", res.GeneratedKeys[0])
		}
	} else if err != nil {
		panic(err)
	}

	return entryBucket
}

func (t *Troublemaker) setEntry(entry DB_Troublemaker_Entry) {
	_, err := rethink.Table("troublemakerlog").Update(entry).Run(helpers.GetDB())
	helpers.Relax(err)
}
<commit_msg>[troublemaker] allows list for basic and extended inspect mods<commit_after>package plugins

import (
	"fmt"
	"strings"
	"time"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/models"
	"github.com/bwmarrin/discordgo"
	rethink "github.com/gorethink/gorethink"
)

type Troublemaker struct{}

func (t *Troublemaker) Commands() []string {
	return []string{
		"troublemaker",
		"troublemakers",
		"tm",
	}
}

type DB_Troublemaker_Entry struct {
	ID                string    `gorethink:"id,omitempty"`
	UserID            string    `gorethink:"userid"`
	Reason            string    `gorethink:"reason"`
	CreatedAt         time.Time `gorethink:"createdat"`
	ReportedByGuildID string    `gorethink:"reportedby_guildid"`
	ReportedByUserID  string    `gorethink:"reportedby_userid"`
}

func (t *Troublemaker) Init(session *discordgo.Session) {

}

func (t *Troublemaker) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
	if !helpers.ModuleIsAllowed(msg.ChannelID, msg.ID, msg.Author.ID, helpers.ModulePermTroublemaker) {
		return
	}

	args := strings.Fields(content)
	if len(args) >= 1 {
		switch args[0] {
		case "participate":
			helpers.RequireAdmin(msg, func() {
				session.ChannelTyping(msg.ChannelID)

				channel, err := helpers.GetChannel(msg.ChannelID)
				helpers.Relax(err)

				settings := helpers.GuildSettingsGetCached(channel.GuildID)

				if len(args) >= 2 {
					// Set new log channel
					targetChannel, err := helpers.GetChannelFromMention(msg, args[1])
					if err != nil || targetChannel.ID == "" {
						helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
						return
					}

					previousChannel := settings.TroublemakerLogChannel

					settings.TroublemakerIsParticipating = true
					settings.TroublemakerLogChannel = targetChannel.ID
					err = helpers.GuildSettingsSet(channel.GuildID, settings)
					helpers.Relax(err)

					changes := make([]models.ElasticEventlogChange, 0)
					if previousChannel != "" {
						changes = []models.ElasticEventlogChange{
							{
								Key:      "troublemaker_participate_channel",
								OldValue: previousChannel,
								NewValue: settings.TroublemakerLogChannel,
							},
						}
					}

					changes = append(changes, models.ElasticEventlogChange{
						Key:      "troublemaker_participating",
						OldValue: helpers.StoreBoolAsString(false),
						NewValue: helpers.StoreBoolAsString(true),
					})

					_, err = helpers.EventlogLog(time.Now(), channel.GuildID, channel.GuildID,
						models.EventlogTargetTypeGuild, msg.Author.ID,
						models.EventlogTypeRobyulTroublemakerParticipate, "",
						changes,
						[]models.ElasticEventlogOption{
							{
								Key:   "troublemaker_participate_channel",
								Value: targetChannel.ID,
							},
						}, false)
					helpers.RelaxLog(err)

					_, err = helpers.SendMessage(msg.ChannelID, helpers.GetText("plugins.troublemaker.participation-enabled"))
					helpers.Relax(err)

					return
				} else {
					// Disable logging
					settings.TroublemakerIsParticipating = false
					err = helpers.GuildSettingsSet(channel.GuildID, settings)
					helpers.Relax(err)

					_, err = helpers.EventlogLog(time.Now(), channel.GuildID, channel.GuildID,
						models.EventlogTargetTypeGuild, msg.Author.ID,
						models.EventlogTypeRobyulTroublemakerParticipate, "",
						[]models.ElasticEventlogChange{
							{
								Key:      "troublemaker_participating",
								OldValue: helpers.StoreBoolAsString(true),
								NewValue: helpers.StoreBoolAsString(false),
							},
						},
						nil, false)
					helpers.RelaxLog(err)

					_, err = helpers.SendMessage(msg.ChannelID, helpers.GetText("plugins.troublemaker.participation-disabled"))
					helpers.Relax(err)

					return
				}
				return
			})
			break
		case "list":
			if !helpers.IsMod(msg) && !helpers.CanInspectExtended(msg) && !helpers.CanInspectBasic(msg) {
				helpers.SendMessage(msg.ChannelID, helpers.GetText("mod.no_permission"))
				return
			}

			session.ChannelTyping(msg.ChannelID)

			if len(args) < 2 {
				helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
				return
			}

			targetUser, err := helpers.GetUserFromMention(args[1])
			if err != nil || targetUser.ID == "" {
				helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
				return
			}

			troublemakerReports := t.getTroublemakerReports(targetUser)

			if len(troublemakerReports) <= 0 {
				_, err := helpers.SendMessage(msg.ChannelID, helpers.GetTextF("plugins.troublemaker.list-no-reports", targetUser.Username))
				helpers.Relax(err)
				return
			} else {
				troublemakerText := fmt.Sprintf("I found %d report(s) for `%s#%s` `#%s` <@%s>:\n",
					len(troublemakerReports), targetUser.Username, targetUser.Discriminator, targetUser.ID, targetUser.ID,
				)

				for _, troublemakerReport := range troublemakerReports {
					reportedByUser, err := helpers.GetUser(troublemakerReport.ReportedByUserID)
					if err != nil {
						reportedByUser = new(discordgo.User)
						reportedByUser.ID = troublemakerReport.ReportedByUserID
						reportedByUser.Username = "N/A"
					}
					reportedByGuild, err := helpers.GetGuild(troublemakerReport.ReportedByGuildID)
					if err != nil {
						reportedByGuild = new(discordgo.Guild)
						reportedByGuild.ID = troublemakerReport.ReportedByGuildID
						reportedByGuild.Name = "N/A"
					}
					troublemakerText += fmt.Sprintf("At: `%s`, Reason: `%s`, Reported By: `%s` (`#%s`) On: `%s` (`#%s`)\n",
						troublemakerReport.CreatedAt.Format(time.ANSIC), troublemakerReport.Reason,
						reportedByUser.Username, reportedByUser.ID, reportedByGuild.Name, reportedByGuild.ID,
					)
				}

				for _, page := range helpers.Pagify(troublemakerText, "\n") {
					_, err := helpers.SendMessage(msg.ChannelID, page)
					helpers.Relax(err)
				}
			}
		default:
			helpers.RequireMod(msg, func() {
				session.ChannelTyping(msg.ChannelID)

				channel, err := helpers.GetChannel(msg.ChannelID)
				helpers.Relax(err)
				guild, err := helpers.GetGuild(channel.GuildID)
				helpers.Relax(err)

				if len(args) < 2 {
					helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
					return
				}

				targetUser, err := helpers.GetUserFromMention(args[0])
				if err != nil || targetUser.ID == "" {
					helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
					return
				}

				reasonText := strings.TrimSpace(strings.Replace(content, strings.Join(args[:1], " "), "", 1))

				if helpers.ConfirmEmbed(msg.ChannelID, msg.Author, helpers.GetTextF("plugins.troublemaker.report-confirm",
					targetUser.Username, targetUser.Discriminator, targetUser.ID, targetUser.ID, reasonText,
				), "✅", "🚫") == true {
					// Save to log DB
					troublemakerLogEntry := t.getEntryByOrCreateEmpty("id", "")
					troublemakerLogEntry.UserID = targetUser.ID
					troublemakerLogEntry.Reason = reasonText
					troublemakerLogEntry.CreatedAt = time.Now()
					troublemakerLogEntry.ReportedByGuildID = guild.ID
					troublemakerLogEntry.ReportedByUserID = msg.Author.ID
					t.setEntry(troublemakerLogEntry)

					cache.GetLogger().WithField("module", "troublemaker").Info(fmt.Sprintf("will notify about troublemaker %s (#%s) by %s (#%s) on %s (#%s) reason %s",
						targetUser.Username, targetUser.ID,
						msg.Author.Username, msg.Author.ID,
						guild.Name, guild.ID,
						reasonText,
					))

					guildsToNotify := make([]*discordgo.Guild, 0)

					for _, guildToNotify := range session.State.Guilds {
						if guildToNotify.ID != guild.ID {
							guildToNotifySettings := helpers.GuildSettingsGetCached(guildToNotify.ID)
							if guildToNotifySettings.TroublemakerIsParticipating == true && guildToNotifySettings.TroublemakerLogChannel != "" {
								guildsToNotify = append(guildsToNotify, guildToNotify)
							}
						}
					}
					_, err = helpers.EventlogLog(time.Now(), channel.GuildID, targetUser.ID,
						models.EventlogTargetTypeUser, msg.Author.ID,
						models.EventlogTypeRobyulTroublemakerReport, reasonText,
						nil,
						nil, false)
					helpers.RelaxLog(err)

					successMessages, _ := helpers.SendMessage(msg.ChannelID, helpers.GetTextF("plugins.troublemaker.report-successful", len(guildsToNotify)))

					// Send notifications out
					go func() {
						defer helpers.Recover()

						for _, guildToNotify := range guildsToNotify {
							guildToNotifySettings := helpers.GuildSettingsGetCached(guildToNotify.ID)
							if guildToNotifySettings.TroublemakerIsParticipating == true && guildToNotifySettings.TroublemakerLogChannel != "" {
								targetUserIsOnServer := false
								_, err := helpers.GetGuildMember(guildToNotify.ID, targetUser.ID)
								if err == nil {
									targetUserIsOnServer = true
								}

								reportEmbed := &discordgo.MessageEmbed{
									Title:       helpers.GetTextF("plugins.troublemaker.report-embed-title", targetUser.Username, targetUser.Discriminator),
									Description: helpers.GetTextF("plugins.troublemaker.report-embed-description", targetUser.ID, targetUser.ID),
									URL:         helpers.GetAvatarUrl(targetUser),
									Thumbnail:   &discordgo.MessageEmbedThumbnail{URL: helpers.GetAvatarUrl(targetUser)},
									Footer:      &discordgo.MessageEmbedFooter{Text: helpers.GetTextF("plugins.troublemaker.report-embed-footer", len(guildsToNotify))},
									Color:       0x0FADED,
									Fields: []*discordgo.MessageEmbedField{
										{Name: "Reason stated", Value: reasonText, Inline: false},
									},
								}

								if targetUserIsOnServer == true {
									reportEmbed.Fields = append(reportEmbed.Fields, &discordgo.MessageEmbedField{
										Name: "Member status", Value: ":warning: User is on this server", Inline: false,
									})
								} else {
									reportEmbed.Fields = append(reportEmbed.Fields, &discordgo.MessageEmbedField{
										Name: "Member status", Value: ":white_check_mark: User is not on this server", Inline: false,
									})
								}

								reportEmbed.Fields = append(reportEmbed.Fields, &discordgo.MessageEmbedField{
									Name: "Reported by", Value: fmt.Sprintf("**%s** on **%s**",
										msg.Author.Username, guild.Name,
									), Inline: false})

								_, err = helpers.SendEmbed(guildToNotifySettings.TroublemakerLogChannel, reportEmbed)
								if err != nil {
									cache.GetLogger().WithField("module", "troublemaker").Warnf("Failed to send troublemaker report to channel #%s on guild #%s: %s"+
										guildToNotifySettings.TroublemakerLogChannel, guildToNotifySettings.Guild, err.Error())
								}
							}
						}

						if len(successMessages) > 0 {
							session.MessageReactionAdd(msg.ChannelID, successMessages[0].ID, "👌")
						}
						return
					}()
					return
				}
				return
			})
			break
		}
	}
}

func (t *Troublemaker) getTroublemakerReports(user *discordgo.User) []DB_Troublemaker_Entry {
	var entryBucket []DB_Troublemaker_Entry
	listCursor, err := rethink.Table("troublemakerlog").Filter(
		rethink.Row.Field("userid").Eq(user.ID),
	).Run(helpers.GetDB())
	helpers.Relax(err)
	defer listCursor.Close()
	listCursor.All(&entryBucket)

	return entryBucket
}

func (t *Troublemaker) getEntryByOrCreateEmpty(key string, id string) DB_Troublemaker_Entry {
	var entryBucket DB_Troublemaker_Entry
	listCursor, err := rethink.Table("troublemakerlog").Filter(
		rethink.Row.Field(key).Eq(id),
	).Run(helpers.GetDB())
	if err != nil {
		panic(err)
	}
	defer listCursor.Close()
	err = listCursor.One(&entryBucket)

	// If user has no DB entries create an empty document
	if err == rethink.ErrEmptyResult {
		insert := rethink.Table("troublemakerlog").Insert(DB_Troublemaker_Entry{})
		res, e := insert.RunWrite(helpers.GetDB())
		// If the creation was successful read the document
		if e != nil {
			panic(e)
		} else {
			return t.getEntryByOrCreateEmpty("id", res.GeneratedKeys[0])
		}
	} else if err != nil {
		panic(err)
	}

	return entryBucket
}

func (t *Troublemaker) setEntry(entry DB_Troublemaker_Entry) {
	_, err := rethink.Table("troublemakerlog").Update(entry).Run(helpers.GetDB())
	helpers.Relax(err)
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package clnt

import "plan9/p"
import "strings"

// Opens the file associated with the fid. Returns nil if
// the operation is successful.
func (clnt *Clnt) Open(fid *Fid, mode uint8) *p.Error {
	tc := p.NewFcall(clnt.Msize);
	err := p.PackTopen(tc, fid.Fid, mode);
	if err != nil {
		return err
	}

	rc, err := clnt.rpc(tc);
	if err != nil {
		return err
	}

	fid.Qid = rc.Qid;
	fid.Iounit = rc.Iounit;
	if fid.Iounit == 0 || fid.Iounit > clnt.Msize-p.IOHDRSZ {
		fid.Iounit = clnt.Msize - p.IOHDRSZ
	}
	fid.Mode = mode;
	return nil;
}

// Creates a file in the directory associated with the fid. Returns nil
// if the operation is successful.
func (clnt *Clnt) Create(fid *Fid, name string, perm uint32, mode uint8, ext string) *p.Error {
	tc := p.NewFcall(clnt.Msize);
	err := p.PackTcreate(tc, fid.Fid, name, perm, mode, ext, clnt.Dotu);
	if err != nil {
		return err
	}

	rc, err := clnt.rpc(tc);
	if err != nil {
		return err
	}

	fid.Qid = rc.Qid;
	fid.Iounit = rc.Iounit;
	if fid.Iounit == 0 || fid.Iounit > clnt.Msize-p.IOHDRSZ {
		fid.Iounit = clnt.Msize - p.IOHDRSZ
	}
	fid.Mode = mode;
	return nil;
}

// Creates and opens a named file.
// Returns the file if the operation is successful, or an Error.
func (clnt *Clnt) FCreate(path string, perm uint32, mode uint8) (*File, *p.Error) {
	n := strings.LastIndex(path, "/");
	if n < 0 {
		n = 0
	}

	fid, err := clnt.FWalk(path[0:n]);
	if err != nil {
		return nil, err
	}

	err = clnt.Create(fid, path[n:len(path)], perm, mode, "");
	if err != nil {
		clnt.Clunk(fid);
		return nil, err;
	}

	return &File{fid, 0}, nil;
}

// Opens a named file. Returns the opened file, or an Error.
func (clnt *Clnt) FOpen(path string, mode uint8) (*File, *p.Error) {
	fid, err := clnt.FWalk(path);
	if err != nil {
		return nil, err
	}

	err = clnt.Open(fid, mode);
	if err != nil {
		clnt.Clunk(fid);
		return nil, err;
	}

	return &File{fid, 0}, nil;
}
<commit_msg>fix bug during create (basename index was off by one). should perhaps use path.Split()<commit_after>// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package clnt

import "plan9/p"
import "strings"

// Opens the file associated with the fid. Returns nil if
// the operation is successful.
func (clnt *Clnt) Open(fid *Fid, mode uint8) *p.Error {
	tc := p.NewFcall(clnt.Msize);
	err := p.PackTopen(tc, fid.Fid, mode);
	if err != nil {
		return err
	}

	rc, err := clnt.rpc(tc);
	if err != nil {
		return err
	}

	fid.Qid = rc.Qid;
	fid.Iounit = rc.Iounit;
	if fid.Iounit == 0 || fid.Iounit > clnt.Msize-p.IOHDRSZ {
		fid.Iounit = clnt.Msize - p.IOHDRSZ
	}
	fid.Mode = mode;
	return nil;
}

// Creates a file in the directory associated with the fid. Returns nil
// if the operation is successful.
func (clnt *Clnt) Create(fid *Fid, name string, perm uint32, mode uint8, ext string) *p.Error {
	tc := p.NewFcall(clnt.Msize);
	err := p.PackTcreate(tc, fid.Fid, name, perm, mode, ext, clnt.Dotu);
	if err != nil {
		return err
	}

	rc, err := clnt.rpc(tc);
	if err != nil {
		return err
	}

	fid.Qid = rc.Qid;
	fid.Iounit = rc.Iounit;
	if fid.Iounit == 0 || fid.Iounit > clnt.Msize-p.IOHDRSZ {
		fid.Iounit = clnt.Msize - p.IOHDRSZ
	}
	fid.Mode = mode;
	return nil;
}

// Creates and opens a named file.
// Returns the file if the operation is successful, or an Error.
func (clnt *Clnt) FCreate(path string, perm uint32, mode uint8) (*File, *p.Error) {
	n := strings.LastIndex(path, "/");
	if n < 0 {
		n = 0
	}

	fid, err := clnt.FWalk(path[0:n]);
	if err != nil {
		return nil, err
	}

	err = clnt.Create(fid, path[n+1:len(path)], perm, mode, "");
	if err != nil {
		clnt.Clunk(fid);
		return nil, err;
	}

	return &File{fid, 0}, nil;
}

// Opens a named file. Returns the opened file, or an Error.
func (clnt *Clnt) FOpen(path string, mode uint8) (*File, *p.Error) {
	fid, err := clnt.FWalk(path);
	if err != nil {
		return nil, err
	}

	err = clnt.Open(fid, mode);
	if err != nil {
		clnt.Clunk(fid);
		return nil, err;
	}

	return &File{fid, 0}, nil;
}
<|endoftext|>


<commit_before>package the_platinum_searcher

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"

	"github.com/monochromegane/conflag"
	"github.com/monochromegane/go-home"
	"github.com/monochromegane/terminal"
)

const version = "2.0.2"

const (
	ExitCodeOK = iota
	ExitCodeError
)

var opts Option

type PlatinumSearcher struct {
	Out, Err io.Writer
}

func (p PlatinumSearcher) Run(args []string) int {

	parser := newOptionParser(&opts)

	conflag.LongHyphen = true
	conflag.BoolValue = false
	for _, c := range []string{filepath.Join(home.Dir(), ".ptconfig.toml"), ".ptconfig.toml"} {
		if args, err := conflag.ArgsFrom(c); err == nil {
			parser.ParseArgs(args)
		}
	}

	args, err := parser.ParseArgs(args)
	if err != nil {
		return ExitCodeError
	}

	if opts.Version {
		fmt.Printf("pt version %s\n", version)
		return ExitCodeOK
	}

	if len(args) == 0 && !opts.SearchOption.EnableFilesWithRegexp {
		parser.WriteHelp(p.Err)
		return ExitCodeError
	}

	if !terminal.IsTerminal(os.Stdout) {
		opts.OutputOption.EnableColor = false
		opts.OutputOption.EnableGroup = false
	}

	if p.givenStdin() {
		opts.SearchOption.SearchStream = true
	}

	if opts.SearchOption.EnableFilesWithRegexp {
		args = append([]string{""}, args...)
	}

	search := search{
		roots: p.rootsFrom(args),
		out:   p.Out,
	}
	if err = search.start(p.patternFrom(args)); err != nil {
		fmt.Fprintf(p.Err, "%s\n", err)
		return ExitCodeError
	}
	return ExitCodeOK
}

func (p PlatinumSearcher) patternFrom(args []string) string {
	return args[0]
}

func (p PlatinumSearcher) rootsFrom(args []string) []string {
	if len(args) > 1 {
		return args[1:]
	} else {
		return []string{"."}
	}
}

func (p PlatinumSearcher) givenStdin() bool {
	fi, err := os.Stdin.Stat()
	if runtime.GOOS == "windows" {
		if err == nil {
			return true
		}
	} else {
		if err != nil {
			return false
		}

		mode := fi.Mode()
		if (mode&os.ModeNamedPipe != 0) || mode.IsRegular() {
			return true
		}
	}
	return false
}
<commit_msg>Bumped version to 2.0.3.<commit_after>package the_platinum_searcher

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"

	"github.com/monochromegane/conflag"
	"github.com/monochromegane/go-home"
	"github.com/monochromegane/terminal"
)

const version = "2.0.3"

const (
	ExitCodeOK = iota
	ExitCodeError
)

var opts Option

type PlatinumSearcher struct {
	Out, Err io.Writer
}

func (p PlatinumSearcher) Run(args []string) int {

	parser := newOptionParser(&opts)

	conflag.LongHyphen = true
	conflag.BoolValue = false
	for _, c := range []string{filepath.Join(home.Dir(), ".ptconfig.toml"), ".ptconfig.toml"} {
		if args, err := conflag.ArgsFrom(c); err == nil {
			parser.ParseArgs(args)
		}
	}

	args, err := parser.ParseArgs(args)
	if err != nil {
		return ExitCodeError
	}

	if opts.Version {
		fmt.Printf("pt version %s\n", version)
		return ExitCodeOK
	}

	if len(args) == 0 && !opts.SearchOption.EnableFilesWithRegexp {
		parser.WriteHelp(p.Err)
		return ExitCodeError
	}

	if !terminal.IsTerminal(os.Stdout) {
		opts.OutputOption.EnableColor = false
		opts.OutputOption.EnableGroup = false
	}

	if p.givenStdin() {
		opts.SearchOption.SearchStream = true
	}

	if opts.SearchOption.EnableFilesWithRegexp {
		args = append([]string{""}, args...)
	}

	search := search{
		roots: p.rootsFrom(args),
		out:   p.Out,
	}
	if err = search.start(p.patternFrom(args)); err != nil {
		fmt.Fprintf(p.Err, "%s\n", err)
		return ExitCodeError
	}
	return ExitCodeOK
}

func (p PlatinumSearcher) patternFrom(args []string) string {
	return args[0]
}

func (p PlatinumSearcher) rootsFrom(args []string) []string {
	if len(args) > 1 {
		return args[1:]
	} else {
		return []string{"."}
	}
}

func (p PlatinumSearcher) givenStdin() bool {
	fi, err := os.Stdin.Stat()
	if runtime.GOOS == "windows" {
		if err == nil {
			return true
		}
	} else {
		if err != nil {
			return false
		}

		mode := fi.Mode()
		if (mode&os.ModeNamedPipe != 0) || mode.IsRegular() {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package arn

import (
	"errors"
	"os"
	"os/exec"
	"path"
	"sort"
	"strings"

	"github.com/aerogo/nano"
	"github.com/animenotifier/arn/autocorrect"
	"github.com/fatih/color"
)

// SoundTrack ...
type SoundTrack struct {
	ID        string           `json:"id"`
	Title     string           `json:"title" editable:"true"`
	Media     []*ExternalMedia `json:"media" editable:"true"`
	Tags      []string         `json:"tags" editable:"true" tooltip:"

anime:ID to connect it with anime

opening for openings

ending for endings

cover for covers

remix for remixes

"`
	IsDraft   bool             `json:"isDraft" editable:"true"`
	File      string           `json:"file"`
	Created   string           `json:"created"`
	CreatedBy string           `json:"createdBy"`
	Edited    string           `json:"edited"`
	EditedBy  string           `json:"editedBy"`
	LikeableImplementation
}

// Link returns the permalink for the track.
func (track *SoundTrack) Link() string {
	return "/soundtrack/" + track.ID
}

// MediaByService ...
func (track *SoundTrack) MediaByService(service string) []*ExternalMedia {
	filtered := []*ExternalMedia{}

	for _, media := range track.Media {
		if media.Service == service {
			filtered = append(filtered, media)
		}
	}

	return filtered
}

// HasTag returns true if it contains the given tag.
func (track *SoundTrack) HasTag(search string) bool {
	for _, tag := range track.Tags {
		if tag == search {
			return true
		}
	}

	return false
}

// Anime fetches all tagged anime of the sound track.
func (track *SoundTrack) Anime() []*Anime {
	var animeList []*Anime

	for _, tag := range track.Tags {
		if strings.HasPrefix(tag, "anime:") {
			animeID := strings.TrimPrefix(tag, "anime:")
			anime, err := GetAnime(animeID)

			if err != nil {
				color.Red("Error fetching anime: %v", err)
				continue
			}

			animeList = append(animeList, anime)
		}
	}

	return animeList
}

// Beatmaps returns all osu beatmap IDs of the sound track.
func (track *SoundTrack) Beatmaps() []string {
	var beatmaps []string

	for _, tag := range track.Tags {
		if strings.HasPrefix(tag, "osu-beatmap:") {
			osuID := strings.TrimPrefix(tag, "osu-beatmap:")
			beatmaps = append(beatmaps, osuID)
		}
	}

	return beatmaps
}

// MainAnime ...
func (track *SoundTrack) MainAnime() *Anime {
	allAnime := track.Anime()

	if len(allAnime) == 0 {
		return nil
	}

	return allAnime[0]
}

// Creator returns the user who created this track.
func (track *SoundTrack) Creator() *User {
	user, _ := GetUser(track.CreatedBy)
	return user
}

// EditedByUser returns the user who edited this track last.
func (track *SoundTrack) EditedByUser() *User {
	user, _ := GetUser(track.EditedBy)
	return user
}

// OnLike is called when the soundtrack receives a like.
func (track *SoundTrack) OnLike(likedBy *User) {
	if likedBy.ID == track.CreatedBy {
		return
	}

	go func() {
		track.Creator().SendNotification(&PushNotification{
			Title:   likedBy.Nick + " liked your soundtrack " + track.Title,
			Message: likedBy.Nick + " liked your soundtrack " + track.Title + ".",
			Icon:    "https:" + likedBy.AvatarLink("large"),
			Link:    "https://notify.moe" + likedBy.Link(),
			Type:    NotificationTypeLike,
		})
	}()
}

// Publish ...
func (track *SoundTrack) Publish() error {
	// No draft
	if !track.IsDraft {
		return errors.New("Not a draft")
	}

	// No media added
	if len(track.Media) == 0 {
		return errors.New("No media specified (at least 1 media source is required)")
	}

	animeFound := false

	for _, tag := range track.Tags {
		tag = autocorrect.FixTag(tag)

		if strings.HasPrefix(tag, "anime:") {
			animeID := strings.TrimPrefix(tag, "anime:")
			_, err := GetAnime(animeID)

			if err != nil {
				return errors.New("Invalid anime ID")
			}

			animeFound = true
		}
	}

	// No anime found
	if !animeFound {
		return errors.New("Need to specify at least one anime")
	}

	// No tags
	if len(track.Tags) < 1 {
		return errors.New("Need to specify at least one tag")
	}

	draftIndex, err := GetDraftIndex(track.CreatedBy)

	if err != nil {
		return err
	}

	if draftIndex.SoundTrackID == "" {
		return errors.New("Soundtrack draft doesn't exist in the user draft index")
	}

	track.IsDraft = false
	draftIndex.SoundTrackID = ""
	draftIndex.Save()
	return nil
}

// Unpublish ...
func (track *SoundTrack) Unpublish() error {
	draftIndex, err := GetDraftIndex(track.CreatedBy)

	if err != nil {
		return err
	}

	if draftIndex.SoundTrackID != "" {
		return errors.New("You still have an unfinished draft")
	}

	track.IsDraft = true
	draftIndex.SoundTrackID = track.ID
	draftIndex.Save()
	return nil
}

// Download downloads the track.
func (track *SoundTrack) Download() error {
	youtubeVideos := track.MediaByService("Youtube")

	if len(youtubeVideos) == 0 {
		return errors.New("No Youtube ID")
	}

	youtubeID := youtubeVideos[0].ServiceID

	// Check for existing file
	if track.File != "" {
		stat, err := os.Stat(path.Join(Root, "audio", track.File))

		if err == nil && !stat.IsDir() && stat.Size() > 0 {
			return errors.New("Already downloaded")
		}
	}

	audioDirectory := path.Join(Root, "audio")
	baseName := track.ID + "|" + youtubeID
	filePath := path.Join(audioDirectory, baseName)

	cmd := exec.Command("youtube-dl", "--extract-audio", "--audio-quality", "0", "--output", filePath+".%(ext)s", youtubeID)
	err := cmd.Start()

	if err != nil {
		return err
	}

	err = cmd.Wait()

	if err != nil {
		return err
	}

	fullPath := FindFileWithExtension(baseName, audioDirectory, []string{
		".opus",
		".webm",
		".ogg",
		".m4a",
		".mp3",
		".flac",
		".wav",
	})

	extension := path.Ext(fullPath)
	track.File = baseName + extension

	return nil
}

// String implements the default string serialization.
func (track *SoundTrack) String() string {
	return track.Title
}

// SortSoundTracksLatestFirst ...
func SortSoundTracksLatestFirst(tracks []*SoundTrack) {
	sort.Slice(tracks, func(i, j int) bool {
		return tracks[i].Created > tracks[j].Created
	})
}

// SortSoundTracksPopularFirst ...
func SortSoundTracksPopularFirst(tracks []*SoundTrack) {
	sort.Slice(tracks, func(i, j int) bool {
		aLikes := len(tracks[i].Likes)
		bLikes := len(tracks[j].Likes)

		if aLikes == bLikes {
			return tracks[i].Created > tracks[j].Created
		}

		return aLikes > bLikes
	})
}

// GetSoundTrack ...
func GetSoundTrack(id string) (*SoundTrack, error) {
	track, err := DB.Get("SoundTrack", id)

	if err != nil {
		return nil, err
	}

	return track.(*SoundTrack), nil
}

// StreamSoundTracks returns a stream of all soundtracks.
func StreamSoundTracks() chan *SoundTrack {
	channel := make(chan *SoundTrack, nano.ChannelBufferSize)

	go func() {
		for obj := range DB.All("SoundTrack") {
			channel <- obj.(*SoundTrack)
		}

		close(channel)
	}()

	return channel
}

// AllSoundTracks ...
func AllSoundTracks() ([]*SoundTrack, error) {
	var all []*SoundTrack

	for obj := range StreamSoundTracks() {
		all = append(all, obj)
	}

	return all, nil
}

// FilterSoundTracks filters all soundtracks by a custom function.
func FilterSoundTracks(filter func(*SoundTrack) bool) []*SoundTrack {
	var filtered []*SoundTrack

	for obj := range StreamSoundTracks() {
		if filter(obj) {
			filtered = append(filtered, obj)
		}
	}

	return filtered
}
<commit_msg>Fixed AllSoundTracks() signature<commit_after>package arn

import (
	"errors"
	"os"
	"os/exec"
	"path"
	"sort"
	"strings"

	"github.com/aerogo/nano"
	"github.com/animenotifier/arn/autocorrect"
	"github.com/fatih/color"
)

// SoundTrack ...
type SoundTrack struct {
	ID        string           `json:"id"`
	Title     string           `json:"title" editable:"true"`
	Media     []*ExternalMedia `json:"media" editable:"true"`
	Tags      []string         `json:"tags" editable:"true" tooltip:"

anime:ID to connect it with anime

opening for openings

ending for endings

cover for covers

remix for remixes

"`
	IsDraft   bool             `json:"isDraft" editable:"true"`
	File      string           `json:"file"`
	Created   string           `json:"created"`
	CreatedBy string           `json:"createdBy"`
	Edited    string           `json:"edited"`
	EditedBy  string           `json:"editedBy"`
	LikeableImplementation
}

// Link returns the permalink for the track.
func (track *SoundTrack) Link() string {
	return "/soundtrack/" + track.ID
}

// MediaByService ...
func (track *SoundTrack) MediaByService(service string) []*ExternalMedia {
	filtered := []*ExternalMedia{}

	for _, media := range track.Media {
		if media.Service == service {
			filtered = append(filtered, media)
		}
	}

	return filtered
}

// HasTag returns true if it contains the given tag.
func (track *SoundTrack) HasTag(search string) bool {
	for _, tag := range track.Tags {
		if tag == search {
			return true
		}
	}

	return false
}

// Anime fetches all tagged anime of the sound track.
func (track *SoundTrack) Anime() []*Anime {
	var animeList []*Anime

	for _, tag := range track.Tags {
		if strings.HasPrefix(tag, "anime:") {
			animeID := strings.TrimPrefix(tag, "anime:")
			anime, err := GetAnime(animeID)

			if err != nil {
				color.Red("Error fetching anime: %v", err)
				continue
			}

			animeList = append(animeList, anime)
		}
	}

	return animeList
}

// Beatmaps returns all osu beatmap IDs of the sound track.
func (track *SoundTrack) Beatmaps() []string {
	var beatmaps []string

	for _, tag := range track.Tags {
		if strings.HasPrefix(tag, "osu-beatmap:") {
			osuID := strings.TrimPrefix(tag, "osu-beatmap:")
			beatmaps = append(beatmaps, osuID)
		}
	}

	return beatmaps
}

// MainAnime ...
func (track *SoundTrack) MainAnime() *Anime {
	allAnime := track.Anime()

	if len(allAnime) == 0 {
		return nil
	}

	return allAnime[0]
}

// Creator returns the user who created this track.
func (track *SoundTrack) Creator() *User {
	user, _ := GetUser(track.CreatedBy)
	return user
}

// EditedByUser returns the user who edited this track last.
func (track *SoundTrack) EditedByUser() *User {
	user, _ := GetUser(track.EditedBy)
	return user
}

// OnLike is called when the soundtrack receives a like.
func (track *SoundTrack) OnLike(likedBy *User) {
	if likedBy.ID == track.CreatedBy {
		return
	}

	go func() {
		track.Creator().SendNotification(&PushNotification{
			Title:   likedBy.Nick + " liked your soundtrack " + track.Title,
			Message: likedBy.Nick + " liked your soundtrack " + track.Title + ".",
			Icon:    "https:" + likedBy.AvatarLink("large"),
			Link:    "https://notify.moe" + likedBy.Link(),
			Type:    NotificationTypeLike,
		})
	}()
}

// Publish ...
func (track *SoundTrack) Publish() error {
	// No draft
	if !track.IsDraft {
		return errors.New("Not a draft")
	}

	// No media added
	if len(track.Media) == 0 {
		return errors.New("No media specified (at least 1 media source is required)")
	}

	animeFound := false

	for _, tag := range track.Tags {
		tag = autocorrect.FixTag(tag)

		if strings.HasPrefix(tag, "anime:") {
			animeID := strings.TrimPrefix(tag, "anime:")
			_, err := GetAnime(animeID)

			if err != nil {
				return errors.New("Invalid anime ID")
			}

			animeFound = true
		}
	}

	// No anime found
	if !animeFound {
		return errors.New("Need to specify at least one anime")
	}

	// No tags
	if len(track.Tags) < 1 {
		return errors.New("Need to specify at least one tag")
	}

	draftIndex, err := GetDraftIndex(track.CreatedBy)

	if err != nil {
		return err
	}

	if draftIndex.SoundTrackID == "" {
		return errors.New("Soundtrack draft doesn't exist in the user draft index")
	}

	track.IsDraft = false
	draftIndex.SoundTrackID = ""
	draftIndex.Save()
	return nil
}

// Unpublish ...
func (track *SoundTrack) Unpublish() error {
	draftIndex, err := GetDraftIndex(track.CreatedBy)

	if err != nil {
		return err
	}

	if draftIndex.SoundTrackID != "" {
		return errors.New("You still have an unfinished draft")
	}

	track.IsDraft = true
	draftIndex.SoundTrackID = track.ID
	draftIndex.Save()
	return nil
}

// Download downloads the track.
func (track *SoundTrack) Download() error {
	youtubeVideos := track.MediaByService("Youtube")

	if len(youtubeVideos) == 0 {
		return errors.New("No Youtube ID")
	}

	youtubeID := youtubeVideos[0].ServiceID

	// Check for existing file
	if track.File != "" {
		stat, err := os.Stat(path.Join(Root, "audio", track.File))

		if err == nil && !stat.IsDir() && stat.Size() > 0 {
			return errors.New("Already downloaded")
		}
	}

	audioDirectory := path.Join(Root, "audio")
	baseName := track.ID + "|" + youtubeID
	filePath := path.Join(audioDirectory, baseName)

	cmd := exec.Command("youtube-dl", "--extract-audio", "--audio-quality", "0", "--output", filePath+".%(ext)s", youtubeID)
	err := cmd.Start()

	if err != nil {
		return err
	}

	err = cmd.Wait()

	if err != nil {
		return err
	}

	fullPath := FindFileWithExtension(baseName, audioDirectory, []string{
		".opus",
		".webm",
		".ogg",
		".m4a",
		".mp3",
		".flac",
		".wav",
	})

	extension := path.Ext(fullPath)
	track.File = baseName + extension

	return nil
}

// String implements the default string serialization.
func (track *SoundTrack) String() string {
	return track.Title
}

// SortSoundTracksLatestFirst ...
func SortSoundTracksLatestFirst(tracks []*SoundTrack) {
	sort.Slice(tracks, func(i, j int) bool {
		return tracks[i].Created > tracks[j].Created
	})
}

// SortSoundTracksPopularFirst ...
func SortSoundTracksPopularFirst(tracks []*SoundTrack) {
	sort.Slice(tracks, func(i, j int) bool {
		aLikes := len(tracks[i].Likes)
		bLikes := len(tracks[j].Likes)

		if aLikes == bLikes {
			return tracks[i].Created > tracks[j].Created
		}

		return aLikes > bLikes
	})
}

// GetSoundTrack ...
func GetSoundTrack(id string) (*SoundTrack, error) {
	track, err := DB.Get("SoundTrack", id)

	if err != nil {
		return nil, err
	}

	return track.(*SoundTrack), nil
}

// StreamSoundTracks returns a stream of all soundtracks.
func StreamSoundTracks() chan *SoundTrack {
	channel := make(chan *SoundTrack, nano.ChannelBufferSize)

	go func() {
		for obj := range DB.All("SoundTrack") {
			channel <- obj.(*SoundTrack)
		}

		close(channel)
	}()

	return channel
}

// AllSoundTracks ...
func AllSoundTracks() []*SoundTrack {
	var all []*SoundTrack

	for obj := range StreamSoundTracks() {
		all = append(all, obj)
	}

	return all
}

// FilterSoundTracks filters all soundtracks by a custom function.
func FilterSoundTracks(filter func(*SoundTrack) bool) []*SoundTrack {
	var filtered []*SoundTrack

	for obj := range StreamSoundTracks() {
		if filter(obj) {
			filtered = append(filtered, obj)
		}
	}

	return filtered
}
<|endoftext|>


<commit_before>package aws

import (
	"github.com/GoogleCloudPlatform/terraformer/terraform_utils"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/firehose"
)

// Copyright 2018 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

type FirehoseGenerator struct {
	AWSService
}

func (g FirehoseGenerator) createResources(sess *session.Session, streamNames []*string, region string) []terraform_utils.Resource {

	var resources []terraform_utils.Resource
	for _, streamName := range streamNames {
		resourceName := aws.StringValue(streamName)

		resources = append(resources, terraform_utils.NewResource(
			resourceName,
			resourceName,
			"aws_kinesis_firehose_delivery_stream",
			"aws",
			map[string]string{"name": resourceName},
			[]string{".tags"},
			map[string]string{}))
	}
	return resources
}

// Generate TerraformResources from AWS API,
// Need deliver stream name for terraform resource
func (g *FirehoseGenerator) InitResources() error {
	sess := g.generateSession()
	svc := firehose.New(sess)
	var streamNames []*string
	for {
		output, err := svc.ListDeliveryStreams(&firehose.ListDeliveryStreamsInput{})
		if err != nil {
			return err
		}
		streamNames = append(streamNames, output.DeliveryStreamNames...)
		if *output.HasMoreDeliveryStreams == false {
			break
		}
	}

	g.Resources = g.createResources(sess, streamNames, g.GetArgs()["region"].(string))

	g.PopulateIgnoreKeys()
	return nil
}
<commit_msg>Fix some codes<commit_after>// Copyright 2018 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aws

import (
	"github.com/GoogleCloudPlatform/terraformer/terraform_utils"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/firehose"
)

type FirehoseGenerator struct {
	AWSService
}

func (g FirehoseGenerator) createResources(sess *session.Session, streamNames []*string) []terraform_utils.Resource {
	var resources []terraform_utils.Resource
	for _, streamName := range streamNames {
		resourceName := aws.StringValue(streamName)

		resources = append(resources, terraform_utils.NewResource(
			resourceName,
			resourceName,
			"aws_kinesis_firehose_delivery_stream",
			"aws",
			map[string]string{"name": resourceName},
			[]string{".tags"},
			map[string]string{}))
	}
	return resources
}

// Generate TerraformResources from AWS API,
// Need deliver stream name for terraform resource
func (g *FirehoseGenerator) InitResources() error {
	sess := g.generateSession()
	svc := firehose.New(sess)
	var streamNames []*string
	for {
		output, err := svc.ListDeliveryStreams(&firehose.ListDeliveryStreamsInput{})
		if err != nil {
			return err
		}
		streamNames = append(streamNames, output.DeliveryStreamNames...)
		if *output.HasMoreDeliveryStreams == false {
			break
		}
	}

	g.Resources = g.createResources(sess, streamNames)

	g.PopulateIgnoreKeys()
	return nil
}
<|endoftext|>


<commit_before>//
// Copyright 2016-2018 Bryan T. Meyers <bmeyers@datadrake.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package kde

import (
	"bytes"
	"compress/bzip2"
	"fmt"
	"github.com/DataDrake/cuppa/results"
    "io/ioutil"
	"net/http"
	"regexp"
	"strings"
	"time"
)

const (
	// ListingURL is the location of the KDE FTP file listing
	ListingURL = "https://download.kde.org/ls-lR.bz2"
	// ListingPrefix is the prefix of all paths in the KDE listing that is hidden by HTTP
	ListingPrefix = "/srv/archives/ftp/"
	// SourceFormat4 is the string format for KDE sources with 4 pieces
	SourceFormat4 = "https://download.kde.org/%s/%s/%s/%s-%s.tar.bz2"
	// SourceFormat5 is the string format for KDE sources with 5 pieces
	SourceFormat5 = "https://download.kde.org/%s/%s/%s/%s/%s-%s.tar.bz2"
	// SourceFormat6 is the string format for KDE sources with 6 pieces
	SourceFormat6 = "https://download.kde.org/%s/%s/%s/%s/%s/%s-%s.tar.bz2"
)

// TarballRegex matches KDE sources
var TarballRegex = regexp.MustCompile("https?://.*download.kde.org/(.+)")

// Provider is the upstream provider interface for KDE
type Provider struct{}

var listing []byte

func getListing() {
	// Query the API
	resp, err := http.Get(ListingURL)
	if err != nil {
		panic(err.Error())
	}
	defer resp.Body.Close()
	// Translate Status Code
    if resp.StatusCode != 200 {
        return
    }
	body := bzip2.NewReader(resp.Body)
	listing, _ = ioutil.ReadAll(body)
}

// Latest finds the newest release for a KDE package
func (c Provider) Latest(name string) (r *results.Result, s results.Status) {
	rs, s := c.Releases(name)
	if s != results.OK {
		return
	}
	r = rs.Last()
	return
}

// Match checks to see if this provider can handle this kind of query
func (c Provider) Match(query string) string {
	sm := TarballRegex.FindStringSubmatch(query)
	if len(sm) != 2 {
		return ""
	}
	pieces := strings.Split(sm[1], "/")
	if len(pieces) < 4 || len(pieces) > 6 {
		return ""
	}
	return sm[1]
}

// Name gives the name of this provider
func (c Provider) Name() string {
	return "KDE"
}

// Releases finds all matching releases for a KDE package
func (c Provider) Releases(name string) (rs *results.ResultSet, s results.Status) {
    if len(listing) == 0 {
        getListing()
    }
    buff := bytes.NewBuffer(listing)
	pieces := strings.Split(name, "/")
	name = strings.Split(pieces[len(pieces)-1], "-")[0]
	var searchPrefix string
	switch len(pieces) {
	case 4:
		searchPrefix = ListingPrefix + strings.Join(pieces[0:len(pieces)-2], "/") + ":\n"
	case 5,6:
		searchPrefix = ListingPrefix + strings.Join(pieces[0:len(pieces)-3], "/") + ":\n"
	}
	rs = results.NewResultSet(name)
	for {
		line, err := buff.ReadString('\n')
		if err != nil {
			break
		}
		if line != searchPrefix {
			continue
		}
		for line != "\n" {
			line, err = buff.ReadString('\n')
			if err != nil {
				break
			}
			if line == "\n" {
				break
			}
			fields := strings.Fields(line)
			version := fields[len(fields)-1]
			updated, _ := time.Parse("2006-01-02 15:04", strings.Join(fields[len(fields)-3:len(fields)-2], " "))
			var location string
			switch len(pieces) {
			case 4:
				location = fmt.Sprintf(SourceFormat4, pieces[0], pieces[1], version, name, version)
			case 5:
				location = fmt.Sprintf(SourceFormat5, pieces[0], pieces[1], version, pieces[3], name, version)
			case 6:
				location = fmt.Sprintf(SourceFormat6, pieces[0], pieces[1], version, pieces[3], pieces[4], name, version)
			}
			r := &results.Result{
				Name:      name,
				Version:   version,
				Location:  location,
				Published: updated,
			}
			rs.AddResult(r)
		}
		break
	}
	return
}
<commit_msg>Fix a couple KDE derpers by checking for non-numeric version number<commit_after>//
// Copyright 2016-2018 Bryan T. Meyers <bmeyers@datadrake.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package kde

import (
	"bytes"
	"compress/bzip2"
	"fmt"
	"github.com/DataDrake/cuppa/results"
    "io/ioutil"
	"net/http"
	"regexp"
	"strings"
	"time"
)

const (
	// ListingURL is the location of the KDE FTP file listing
	ListingURL = "https://download.kde.org/ls-lR.bz2"
	// ListingPrefix is the prefix of all paths in the KDE listing that is hidden by HTTP
	ListingPrefix = "/srv/archives/ftp/"
	// SourceFormat4 is the string format for KDE sources with 4 pieces
	SourceFormat4 = "https://download.kde.org/%s/%s/%s/%s-%s.tar.bz2"
	// SourceFormat5 is the string format for KDE sources with 5 pieces
	SourceFormat5 = "https://download.kde.org/%s/%s/%s/%s/%s-%s.tar.bz2"
	// SourceFormat6 is the string format for KDE sources with 6 pieces
	SourceFormat6 = "https://download.kde.org/%s/%s/%s/%s/%s/%s-%s.tar.bz2"
)

// TarballRegex matches KDE sources
var TarballRegex = regexp.MustCompile("https?://.*download.kde.org/(.+)")

// Provider is the upstream provider interface for KDE
type Provider struct{}

var listing []byte

func getListing() {
	// Query the API
	resp, err := http.Get(ListingURL)
	if err != nil {
		panic(err.Error())
	}
	defer resp.Body.Close()
	// Translate Status Code
    if resp.StatusCode != 200 {
        return
    }
	body := bzip2.NewReader(resp.Body)
	listing, _ = ioutil.ReadAll(body)
}

// Latest finds the newest release for a KDE package
func (c Provider) Latest(name string) (r *results.Result, s results.Status) {
	rs, s := c.Releases(name)
	if s != results.OK {
		return
	}
	r = rs.Last()
	return
}

// Match checks to see if this provider can handle this kind of query
func (c Provider) Match(query string) string {
	sm := TarballRegex.FindStringSubmatch(query)
	if len(sm) != 2 {
		return ""
	}
	pieces := strings.Split(sm[1], "/")
	if len(pieces) < 4 || len(pieces) > 6 {
		return ""
	}
	return sm[1]
}

// Name gives the name of this provider
func (c Provider) Name() string {
	return "KDE"
}

// Releases finds all matching releases for a KDE package
func (c Provider) Releases(name string) (rs *results.ResultSet, s results.Status) {
    if len(listing) == 0 {
        getListing()
    }
    buff := bytes.NewBuffer(listing)
	pieces := strings.Split(name, "/")
	name = strings.Split(pieces[len(pieces)-1], "-")[0]
	var searchPrefix string
	switch len(pieces) {
	case 4:
		searchPrefix = ListingPrefix + strings.Join(pieces[0:len(pieces)-2], "/") + ":\n"
	case 5,6:
		searchPrefix = ListingPrefix + strings.Join(pieces[0:len(pieces)-3], "/") + ":\n"
	}
	rs = results.NewResultSet(name)
	for {
		line, err := buff.ReadString('\n')
		if err != nil {
			break
		}
		if line != searchPrefix {
			continue
		}
		for line != "\n" {
			line, err = buff.ReadString('\n')
			if err != nil {
				break
			}
			if line == "\n" {
				break
			}
			fields := strings.Fields(line)
			version := fields[len(fields)-1]
            if version[0] > 57 || version[0] < 48 {
                continue
            }
			updated, _ := time.Parse("2006-01-02 15:04", strings.Join(fields[len(fields)-3:len(fields)-2], " "))
			var location string
			switch len(pieces) {
			case 4:
				location = fmt.Sprintf(SourceFormat4, pieces[0], pieces[1], version, name, version)
			case 5:
				location = fmt.Sprintf(SourceFormat5, pieces[0], pieces[1], version, pieces[3], name, version)
			case 6:
				location = fmt.Sprintf(SourceFormat6, pieces[0], pieces[1], version, pieces[3], pieces[4], name, version)
			}
			r := &results.Result{
				Name:      name,
				Version:   version,
				Location:  location,
				Published: updated,
			}
			rs.AddResult(r)
		}
		break
	}
	return
}
<|endoftext|>


<commit_before>package pq

import "sync"

type Element struct {
	Value    interface{}
	Priority int
}

func NewElement(value interface{}, priority int) (e *Element) {
	return &Element{
		Value:    value,
		Priority: priority,
	}
}

func (e Element) LessThan(hel Element) bool {
	return e.Priority < hel.Priority
}

type PQ struct {
	sync.Mutex
	heap  []Element
	len   int
	isMin bool
}

func MinPQConstructor() (pq *PQ) {
	return &PQ{heap: make([]Element, 0), isMin: true}
}

func MaxPQConstructor() (pq *PQ) {
	return &PQ{heap: make([]Element, 0), isMin: false}
}

func (pq *PQ) Len() int {
	pq.Lock()
	defer pq.Unlock()

	return pq.len
}

func (pq *PQ) Insert(el Element) {
	pq.Lock()
	defer pq.Unlock()

	pq.heap = append(pq.heap, el)
	pq.len++
	pq.precolateUp(pq.len - 1)
}

func (pq *PQ) Extract() (el Element) {
	pq.Lock()
	defer pq.Unlock()

	if pq.len == 0 {
		panic("Empty pq, cannot Extract.")
	}

	el, pq.heap[0] = pq.heap[0], pq.heap[pq.len-1]
	pq.heap = pq.heap[:pq.len-1]
	pq.len--
	pq.precolateDown(0)
	return
}

func (pq *PQ) Peek() (el Element) {
	pq.Lock()
	defer pq.Unlock()

	if pq.len == 0 {
		panic("Empty pq, cannot Peek.")
	}
	return pq.heap[0]
}

func (pq *PQ) ChangePriority(index, priority int) {
	pq.Lock()
	defer pq.Unlock()

	oldEl := pq.heap[index]
	pq.heap[index].Priority = priority
	if pq.less(Element{Priority: priority}, oldEl) {
		pq.precolateUp(index)
	} else {
		pq.precolateDown(index)
	}
}

func (pq *PQ) precolateUp(index int) {
	// 上滤，新元素在堆中上滤直到找出正确位置
	needUp, parent := index, index>>1
	for needUp > 0 && pq.less(pq.heap[needUp], pq.heap[parent]) {
		pq.heap[parent], pq.heap[needUp] = pq.heap[needUp], pq.heap[parent]
		needUp, parent = parent, parent>>1
	}
}

func (pq *PQ) precolateDown(index int) {
	// 下滤
	needDown, child := index, index<<1+1
	for needDown < pq.len && child < pq.len {
		// find min(leftChild, rightChild)
		if child+1 < pq.len && pq.less(pq.heap[child+1], pq.heap[child]) {
			child++
		}

		if pq.less(pq.heap[needDown], pq.heap[child]) {
			break
		}

		pq.heap[needDown], pq.heap[child] = pq.heap[child], pq.heap[needDown]
		needDown, child = child, needDown<<1+1
	}
}

func (pq *PQ) less(a, b Element) bool {
	if pq.isMin {
		return a.LessThan(b)
	} else {
		return b.LessThan(a)
	}
}
<commit_msg>use RWMutex instead of Mutex<commit_after>package pq

import "sync"

type Element struct {
	Value    interface{}
	Priority int
}

func NewElement(value interface{}, priority int) (e *Element) {
	return &Element{
		Value:    value,
		Priority: priority,
	}
}

func (e Element) LessThan(hel Element) bool {
	return e.Priority < hel.Priority
}

type PQ struct {
	sync.RWMutex
	heap  []Element
	len   int
	isMin bool
}

func MinPQConstructor() (pq *PQ) {
	return &PQ{heap: make([]Element, 0), isMin: true}
}

func MaxPQConstructor() (pq *PQ) {
	return &PQ{heap: make([]Element, 0), isMin: false}
}

func (pq *PQ) Len() int {
	pq.RLock()
	defer pq.RUnlock()

	return pq.len
}

func (pq *PQ) Insert(el Element) {
	pq.Lock()
	defer pq.Unlock()

	pq.heap = append(pq.heap, el)
	pq.len++
	pq.precolateUp(pq.len - 1)
}

func (pq *PQ) Extract() (el Element) {
	pq.Lock()
	defer pq.Unlock()

	if pq.len == 0 {
		panic("Empty pq, cannot Extract.")
	}

	el, pq.heap[0] = pq.heap[0], pq.heap[pq.len-1]
	pq.heap = pq.heap[:pq.len-1]
	pq.len--
	pq.precolateDown(0)
	return
}

func (pq *PQ) Peek() (el Element) {
	pq.RLock()
	defer pq.RUnlock()

	if pq.len == 0 {
		panic("Empty pq, cannot Peek.")
	}
	return pq.heap[0]
}

func (pq *PQ) ChangePriority(index, priority int) {
	pq.Lock()
	defer pq.Unlock()

	oldEl := pq.heap[index]
	pq.heap[index].Priority = priority
	if pq.less(Element{Priority: priority}, oldEl) {
		pq.precolateUp(index)
	} else {
		pq.precolateDown(index)
	}
}

func (pq *PQ) precolateUp(index int) {
	// 上滤，新元素在堆中上滤直到找出正确位置
	needUp, parent := index, index>>1
	for needUp > 0 && pq.less(pq.heap[needUp], pq.heap[parent]) {
		pq.heap[parent], pq.heap[needUp] = pq.heap[needUp], pq.heap[parent]
		needUp, parent = parent, parent>>1
	}
}

func (pq *PQ) precolateDown(index int) {
	// 下滤
	needDown, child := index, index<<1+1
	for needDown < pq.len && child < pq.len {
		// find min(leftChild, rightChild)
		if child+1 < pq.len && pq.less(pq.heap[child+1], pq.heap[child]) {
			child++
		}

		if pq.less(pq.heap[needDown], pq.heap[child]) {
			break
		}

		pq.heap[needDown], pq.heap[child] = pq.heap[child], pq.heap[needDown]
		needDown, child = child, needDown<<1+1
	}
}

func (pq *PQ) less(a, b Element) bool {
	if pq.isMin {
		return a.LessThan(b)
	} else {
		return b.LessThan(a)
	}
}
<|endoftext|>


<commit_before>package availability

import "time"

// Year in nanoseconds.
const yearNs = 3.154e16 * time.Nanosecond

// ByTime holds time based availability data.
//
// formula: availability = uptime / ( uptime + downtime )
type ByTime struct {
	percent  float64
	uptime   time.Duration
	downtime time.Duration
}

// NewByTime creates time based availability.
func NewByTime() *ByTime {
	return &ByTime{downtime: yearNs}
}

// ByRequests holds aggregate based availability.
//
// formula: availability = success / total
type ByRequests struct {
	percent float64
	success int
	total   int
}

// NewByRequests creates aggregate based availability.
func NewByRequests() *ByRequests {
	return &ByRequests{}
}
<commit_msg>availability: add SetPercent to ByRequests<commit_after>package availability

import (
	"errors"
	"time"
)

// Year in nanoseconds.
const yearNs = 3.154e16 * time.Nanosecond

// ByTime holds time based availability data.
//
// formula: availability = uptime / ( uptime + downtime )
type ByTime struct {
	percent  float64
	uptime   time.Duration
	downtime time.Duration
}

// NewByTime creates time based availability.
func NewByTime() *ByTime {
	return &ByTime{downtime: yearNs}
}

// ByRequests holds aggregate based availability.
//
// formula: availability = success / total
type ByRequests struct {
	percent float64
	success int
	total   int
}

// NewByRequests creates aggregate based availability.
func NewByRequests() *ByRequests {
	return &ByRequests{}
}

// SetPercent sets percent value.
func (a *ByRequests) SetPercent(v float64) error {

	if v <= 0 || v > 100 {

		return errors.New("invalid percent")
	}

	a.percent = v

	return nil
}
<|endoftext|>


<commit_before>package cmds

// Tests to add:
// basic login test
// login with no auth service deployed

import (
	"errors"
	"os"
	"sync"
	"testing"
	"time"

	"github.com/pachyderm/pachyderm/src/client/pkg/require"
	"github.com/pachyderm/pachyderm/src/server/pkg/backoff"
	tu "github.com/pachyderm/pachyderm/src/server/pkg/testutil"
)

var activateMut sync.Mutex

func activateAuth(t *testing.T) {
	t.Helper()
	activateMut.Lock()
	defer activateMut.Unlock()
	// TODO(msteffen): Make sure client & server have the same version

	// Check if Pachyderm Enterprise is active -- if not, activate it
	cmd := tu.Cmd("pachctl", "enterprise", "get-state")
	out, err := cmd.Output()
	require.NoError(t, err)
	if string(out) != "ACTIVE" {
		cmd = tu.Cmd("pachctl", "enterprise", "activate", tu.GetTestEnterpriseCode())
		require.NoError(t, cmd.Run())
	}

	// Logout (to clear any expired tokens) and activate Pachyderm auth
	require.NoError(t, tu.Cmd("pachctl", "auth", "logout").Run())
	require.NoError(t, tu.Cmd("pachctl", "auth", "activate", "--user=admin").Run())
}

func deactivateAuth(t *testing.T) {
	t.Helper()
	activateMut.Lock()
	defer activateMut.Unlock()

	// Check if Pachyderm Auth is active -- if so, deactivate it
	if err := tu.Cmd("pachctl", "auth", "login", "-u", "admin").Run(); err == nil {
		require.NoError(t, tu.BashCmd("yes | pachctl auth deactivate").Run())
	}
}

func TestAuthBasic(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}
	activateAuth(t)
	defer deactivateAuth(t)
	require.NoError(t, tu.BashCmd(`
		echo "\n" | pachctl auth login -u {{.alice}}
		pachctl create-repo {{.repo}}
		pachctl list-repo \
			| match {{.repo}}
		pachctl inspect-repo {{.repo}}
		`,
		"alice", tu.UniqueString("alice"),
		"repo", tu.UniqueString("TestAuthBasic-repo"),
	).Run())
}

func TestWhoAmI(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}
	activateAuth(t)
	defer deactivateAuth(t)
	require.NoError(t, tu.BashCmd(`
		pachctl auth login -u {{.alice}}
		pachctl auth whoami | match {{.alice}}
		`,
		"alice", tu.UniqueString("alice"),
	).Run())
}

func TestCheckGetSet(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}
	activateAuth(t)
	defer deactivateAuth(t)
	// Test both forms of the 'pachctl auth get' command, as well as 'pachctl auth check'
	require.NoError(t, tu.BashCmd(`
		pachctl auth login -u {{.alice}}
		pachctl create-repo {{.repo}}
		pachctl auth check owner {{.repo}}
		pachctl auth get {{.repo}} \
			| match {{.alice}}
		pachctl auth get {{.bob}} {{.repo}} \
			| match NONE
		`,
		"alice", tu.UniqueString("alice"),
		"bob", tu.UniqueString("bob"),
		"repo", tu.UniqueString("TestGet-repo"),
	).Run())

	// Test 'pachctl auth set'
	require.NoError(t, tu.BashCmd(`
		pachctl auth login -u {{.alice}}
		pachctl create-repo {{.repo}}
		pachctl auth set {{.bob}} reader {{.repo}}
		pachctl auth get {{.bob}} {{.repo}} \
			| match READER
		`,
		"alice", tu.UniqueString("alice"),
		"bob", tu.UniqueString("bob"),
		"repo", tu.UniqueString("TestGet-repo"),
	).Run())
}

func TestAdmins(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}
	activateAuth(t)
	defer deactivateAuth(t)

	// Modify the list of admins to replace 'admin' with 'admin2'
	require.NoError(t, tu.Cmd("pachctl", "auth", "login", "-u", "admin").Run())
	require.NoError(t, tu.BashCmd(`
		pachctl auth list-admins \
			| match "admin"
		pachctl auth modify-admins --add admin2
		pachctl auth list-admins \
			| match  "admin2"
		pachctl auth modify-admins --remove admin

		# as 'admin' is a substr of 'admin2', use '^admin$' regex...
		pachctl auth list-admins \
			| match -v "^admin$" \
			| match "^admin2$"
		`).Run())

	// Now 'admin2' is the only admin. Login as admin2, and swap 'admin' back in
	// (so that deactivateAuth() runs), and call 'list-admin' (to make sure it
	// works for non-admins)
	require.NoError(t, tu.Cmd("pachctl", "auth", "login", "-u", "admin2").Run())
	require.NoError(t, tu.BashCmd(`
		pachctl auth modify-admins --add admin --remove admin2
		pachctl auth list-admins \
			| match -v "admin2" \
			| match "admin"
		`).Run())
}

func TestModifyAdminsPropagateError(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}
	activateAuth(t)
	defer deactivateAuth(t)

	// Add admin2, and then try to remove it along with a fake admin. Make sure we
	// get an error
	require.NoError(t, tu.Cmd("pachctl", "auth", "login", "-u", "admin").Run())
	require.NoError(t, tu.BashCmd(`
		pachctl auth list-admins \
			| match "admin"
		pachctl auth modify-admins --add admin2
		pachctl auth list-admins \
			| match  "admin2"

 		# cmd should fail
		! pachctl auth modify-admins --remove admin1,not_in_list
		`).Run())
}

func TestMain(m *testing.M) {
	// Preemptively deactivate Pachyderm auth (to avoid errors in early tests)
	if err := tu.BashCmd("pachctl auth login -u admin").Run(); err == nil {
		if err := tu.BashCmd("yes | pachctl auth deactivate").Run(); err != nil {
			panic(err.Error())
		}
	}
	time.Sleep(5 * time.Second)
	backoff.Retry(func() error {
		cmd := tu.Cmd("pachctl", "auth", "login", "-u", "admin")
		if cmd.Run() != nil {
			return nil // success -- auth is deactivated
		}
		return errors.New("auth not deactivated yet")
	}, backoff.NewTestingBackOff())
	os.Exit(m.Run())
}
<commit_msg>Fix auth/cmds/cmds_test.go#TestAdmins<commit_after>package cmds

// Tests to add:
// basic login test
// login with no auth service deployed

import (
	"errors"
	"os"
	"sync"
	"testing"
	"time"

	"github.com/pachyderm/pachyderm/src/client/pkg/require"
	"github.com/pachyderm/pachyderm/src/server/pkg/backoff"
	tu "github.com/pachyderm/pachyderm/src/server/pkg/testutil"
)

var activateMut sync.Mutex

func activateAuth(t *testing.T) {
	t.Helper()
	activateMut.Lock()
	defer activateMut.Unlock()
	// TODO(msteffen): Make sure client & server have the same version

	// Check if Pachyderm Enterprise is active -- if not, activate it
	cmd := tu.Cmd("pachctl", "enterprise", "get-state")
	out, err := cmd.Output()
	require.NoError(t, err)
	if string(out) != "ACTIVE" {
		cmd = tu.Cmd("pachctl", "enterprise", "activate", tu.GetTestEnterpriseCode())
		require.NoError(t, cmd.Run())
	}

	// Logout (to clear any expired tokens) and activate Pachyderm auth
	require.NoError(t, tu.Cmd("pachctl", "auth", "logout").Run())
	require.NoError(t, tu.Cmd("pachctl", "auth", "activate", "--user=admin").Run())
}

func deactivateAuth(t *testing.T) {
	t.Helper()
	activateMut.Lock()
	defer activateMut.Unlock()

	// Check if Pachyderm Auth is active -- if so, deactivate it
	if err := tu.Cmd("pachctl", "auth", "login", "-u", "admin").Run(); err == nil {
		require.NoError(t, tu.BashCmd("yes | pachctl auth deactivate").Run())
	}
}

func TestAuthBasic(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}
	activateAuth(t)
	defer deactivateAuth(t)
	require.NoError(t, tu.BashCmd(`
		echo "\n" | pachctl auth login -u {{.alice}}
		pachctl create-repo {{.repo}}
		pachctl list-repo \
			| match {{.repo}}
		pachctl inspect-repo {{.repo}}
		`,
		"alice", tu.UniqueString("alice"),
		"repo", tu.UniqueString("TestAuthBasic-repo"),
	).Run())
}

func TestWhoAmI(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}
	activateAuth(t)
	defer deactivateAuth(t)
	require.NoError(t, tu.BashCmd(`
		pachctl auth login -u {{.alice}}
		pachctl auth whoami | match {{.alice}}
		`,
		"alice", tu.UniqueString("alice"),
	).Run())
}

func TestCheckGetSet(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}
	activateAuth(t)
	defer deactivateAuth(t)
	// Test both forms of the 'pachctl auth get' command, as well as 'pachctl auth check'
	require.NoError(t, tu.BashCmd(`
		pachctl auth login -u {{.alice}}
		pachctl create-repo {{.repo}}
		pachctl auth check owner {{.repo}}
		pachctl auth get {{.repo}} \
			| match {{.alice}}
		pachctl auth get {{.bob}} {{.repo}} \
			| match NONE
		`,
		"alice", tu.UniqueString("alice"),
		"bob", tu.UniqueString("bob"),
		"repo", tu.UniqueString("TestGet-repo"),
	).Run())

	// Test 'pachctl auth set'
	require.NoError(t, tu.BashCmd(`
		pachctl auth login -u {{.alice}}
		pachctl create-repo {{.repo}}
		pachctl auth set {{.bob}} reader {{.repo}}
		pachctl auth get {{.bob}} {{.repo}} \
			| match READER
		`,
		"alice", tu.UniqueString("alice"),
		"bob", tu.UniqueString("bob"),
		"repo", tu.UniqueString("TestGet-repo"),
	).Run())
}

func TestAdmins(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}
	activateAuth(t)
	defer deactivateAuth(t)

	// Modify the list of admins to replace 'admin' with 'admin2'
	require.NoError(t, tu.Cmd("pachctl", "auth", "login", "-u", "admin").Run())
	require.NoError(t, tu.BashCmd(`
		pachctl auth list-admins \
			| match "admin"
		pachctl auth modify-admins --add admin2
		pachctl auth list-admins \
			| match  "admin2"
		pachctl auth modify-admins --remove admin

		# as 'admin' is a substr of 'admin2', use '^admin$' regex...
		pachctl auth list-admins \
			| match -v "^github:admin$" \
			| match "^github:admin2$"
		`).Run())

	// Now 'admin2' is the only admin. Login as admin2, and swap 'admin' back in
	// (so that deactivateAuth() runs), and call 'list-admin' (to make sure it
	// works for non-admins)
	require.NoError(t, tu.Cmd("pachctl", "auth", "login", "-u", "admin2").Run())
	require.NoError(t, tu.BashCmd(`
		pachctl auth modify-admins --add admin --remove admin2
		pachctl auth list-admins \
			| match -v "admin2" \
			| match "admin"
		`).Run())
}

func TestModifyAdminsPropagateError(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration tests in short mode")
	}
	activateAuth(t)
	defer deactivateAuth(t)

	// Add admin2, and then try to remove it along with a fake admin. Make sure we
	// get an error
	require.NoError(t, tu.Cmd("pachctl", "auth", "login", "-u", "admin").Run())
	require.NoError(t, tu.BashCmd(`
		pachctl auth list-admins \
			| match "admin"
		pachctl auth modify-admins --add admin2
		pachctl auth list-admins \
			| match  "admin2"

 		# cmd should fail
		! pachctl auth modify-admins --remove admin1,not_in_list
		`).Run())
}

func TestMain(m *testing.M) {
	// Preemptively deactivate Pachyderm auth (to avoid errors in early tests)
	if err := tu.BashCmd("pachctl auth login -u admin").Run(); err == nil {
		if err := tu.BashCmd("yes | pachctl auth deactivate").Run(); err != nil {
			panic(err.Error())
		}
	}
	time.Sleep(5 * time.Second)
	backoff.Retry(func() error {
		cmd := tu.Cmd("pachctl", "auth", "login", "-u", "admin")
		if cmd.Run() != nil {
			return nil // success -- auth is deactivated
		}
		return errors.New("auth not deactivated yet")
	}, backoff.NewTestingBackOff())
	os.Exit(m.Run())
}
<|endoftext|>


<commit_before>package metrics

import (
	"fmt"
	"time"

	"github.com/pachyderm/pachyderm/src/client/pkg/config"
	"github.com/pachyderm/pachyderm/src/client/pkg/uuid"
	"github.com/pachyderm/pachyderm/src/client/version"
	db "github.com/pachyderm/pachyderm/src/server/pfs/db"

	"github.com/dancannon/gorethink"
	"github.com/segmentio/analytics-go"
	"go.pedge.io/lion"
	"golang.org/x/net/context"
	"google.golang.org/grpc/metadata"
	kube "k8s.io/kubernetes/pkg/client/unversioned"
)

//Reporter is used to submit user & cluster metrics to segment
type Reporter struct {
	segmentClient *analytics.Client
	clusterID     string
	kubeClient    *kube.Client
	dbClient      *gorethink.Session
	pfsDbName     string
	ppsDbName     string
}

// NewReporter creates a new reporter and kicks off the loop to report cluster
// metrics
func NewReporter(clusterID string, kubeClient *kube.Client, address string, pfsDbName string, ppsDbName string) *Reporter {

	dbClient, err := db.DbConnect(address)
	if err != nil {
		lion.Errorf("error connected to DB when reporting metrics: %v\n", err)
		return nil
	}
	reporter := &Reporter{
		segmentClient: newPersistentClient(),
		clusterID:     clusterID,
		kubeClient:    kubeClient,
		dbClient:      dbClient,
		pfsDbName:     pfsDbName,
		ppsDbName:     ppsDbName,
	}
	go reporter.reportClusterMetrics()
	return reporter
}

//ReportUserAction pushes the action into a queue for reporting,
// and reports the start, finish, and error conditions
func ReportUserAction(ctx context.Context, r *Reporter, action string) func(time.Time, error) {
	if r == nil {
		return func(time.Time, error) {}
	}
	r.reportUserAction(ctx, fmt.Sprintf("%vStarted", action), nil)
	return func(start time.Time, err error) {
		if err == nil {
			r.reportUserAction(ctx, fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
		} else {
			r.reportUserAction(ctx, fmt.Sprintf("%vErrored", action), err.Error())
		}
	}
}

func getKeyFromMD(md metadata.MD, key string) (string, error) {
	if md[key] != nil && len(md[key]) > 0 {
		return md[key][0], nil
	}
	return "", fmt.Errorf("error extracting userid from metadata. userid is empty\n")
}

func (r *Reporter) reportUserAction(ctx context.Context, action string, value interface{}) {
	md, ok := metadata.FromContext(ctx)
	if ok {
		// metadata API downcases all the key names
		userID, err := getKeyFromMD(md, "userid")
		if err != nil {
			lion.Errorln(err)
			return
		}
		prefix, err := getKeyFromMD(md, "prefix")
		if err != nil {
			lion.Errorln(err)
			return
		}
		reportUserMetricsToSegment(
			r.segmentClient,
			userID,
			prefix,
			action,
			value,
			r.clusterID,
		)
	} else {
		lion.Errorf("Error extracting userid metadata from context: %v\n", ctx)
	}
}

// ReportAndFlushUserAction immediately reports the metric
// It is used in the few places we need to report metrics from the client.
// It handles reporting the start, finish, and error conditions of the action
func ReportAndFlushUserAction(action string) func(time.Time, error) {
	reportAndFlushUserAction(fmt.Sprintf("%vStarted", action), nil)
	return func(start time.Time, err error) {
		if err == nil {
			reportAndFlushUserAction(fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
		} else {
			reportAndFlushUserAction(fmt.Sprintf("%vErrored", action), err.Error())
		}
	}
}

func reportAndFlushUserAction(action string, value interface{}) {
	client := newSegmentClient()
	defer client.Close()
	cfg, err := config.Read()
	if err != nil {
		lion.Errorf("Error reading userid from ~/.pachyderm/config: %v\n", err)
		// metrics errors are non fatal
		return
	}
	reportUserMetricsToSegment(client, cfg.UserID, "user", action, value, "")
}

func (r *Reporter) dbMetrics(metrics *Metrics) {
	cursor, err := gorethink.Object(
		"Repos",
		gorethink.DB(r.pfsDbName).Table("Repos").Count(),
		"Commits",
		gorethink.DB(r.pfsDbName).Table("Commits").Count(),
		"ArchivedCommits",
		gorethink.DB(r.pfsDbName).Table("Commits").Filter(
			map[string]interface{}{
				"Archived": true,
			},
		).Count(),
		"CancelledCommits",
		gorethink.DB(r.pfsDbName).Table("Commits").Filter(
			map[string]interface{}{
				"Cancelled": true,
			},
		).Count(),
		"Files",
		gorethink.DB(r.pfsDbName).Table("Diffs").Group("Path").Ungroup().Count(),
		"Jobs",
		gorethink.DB(r.ppsDbName).Table("JobInfos").Count(),
		"Pipelines",
		gorethink.DB(r.ppsDbName).Table("PipelineInfos").Count(),
	).Run(r.dbClient)
	if err != nil {
		lion.Errorf("Error Fetching Metrics:%+v", err)
	}
	cursor.One(&metrics)
}

func (r *Reporter) reportClusterMetrics() {
	for {
		time.Sleep(reportingInterval)
		metrics := &Metrics{}
		r.dbMetrics(metrics)
		externalMetrics(r.kubeClient, metrics)
		metrics.ClusterID = r.clusterID
		metrics.PodID = uuid.NewWithoutDashes()
		metrics.Version = version.PrettyPrintVersion(version.Version)
		reportClusterMetricsToSegment(r.segmentClient, metrics)
	}
}
<commit_msg>Report 0 value for started actions<commit_after>package metrics

import (
	"fmt"
	"time"

	"github.com/pachyderm/pachyderm/src/client/pkg/config"
	"github.com/pachyderm/pachyderm/src/client/pkg/uuid"
	"github.com/pachyderm/pachyderm/src/client/version"
	db "github.com/pachyderm/pachyderm/src/server/pfs/db"

	"github.com/dancannon/gorethink"
	"github.com/segmentio/analytics-go"
	"go.pedge.io/lion"
	"golang.org/x/net/context"
	"google.golang.org/grpc/metadata"
	kube "k8s.io/kubernetes/pkg/client/unversioned"
)

//Reporter is used to submit user & cluster metrics to segment
type Reporter struct {
	segmentClient *analytics.Client
	clusterID     string
	kubeClient    *kube.Client
	dbClient      *gorethink.Session
	pfsDbName     string
	ppsDbName     string
}

// NewReporter creates a new reporter and kicks off the loop to report cluster
// metrics
func NewReporter(clusterID string, kubeClient *kube.Client, address string, pfsDbName string, ppsDbName string) *Reporter {

	dbClient, err := db.DbConnect(address)
	if err != nil {
		lion.Errorf("error connected to DB when reporting metrics: %v\n", err)
		return nil
	}
	reporter := &Reporter{
		segmentClient: newPersistentClient(),
		clusterID:     clusterID,
		kubeClient:    kubeClient,
		dbClient:      dbClient,
		pfsDbName:     pfsDbName,
		ppsDbName:     ppsDbName,
	}
	go reporter.reportClusterMetrics()
	return reporter
}

//ReportUserAction pushes the action into a queue for reporting,
// and reports the start, finish, and error conditions
func ReportUserAction(ctx context.Context, r *Reporter, action string) func(time.Time, error) {
	if r == nil {
		return func(time.Time, error) {}
	}
	// If we report nil, segment sees it, but mixpanel omits the field
	r.reportUserAction(ctx, fmt.Sprintf("%vStarted", action), 0)
	return func(start time.Time, err error) {
		if err == nil {
			r.reportUserAction(ctx, fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
		} else {
			r.reportUserAction(ctx, fmt.Sprintf("%vErrored", action), err.Error())
		}
	}
}

func getKeyFromMD(md metadata.MD, key string) (string, error) {
	if md[key] != nil && len(md[key]) > 0 {
		return md[key][0], nil
	}
	return "", fmt.Errorf("error extracting userid from metadata. userid is empty\n")
}

func (r *Reporter) reportUserAction(ctx context.Context, action string, value interface{}) {
	md, ok := metadata.FromContext(ctx)
	if ok {
		// metadata API downcases all the key names
		userID, err := getKeyFromMD(md, "userid")
		if err != nil {
			lion.Errorln(err)
			return
		}
		prefix, err := getKeyFromMD(md, "prefix")
		if err != nil {
			lion.Errorln(err)
			return
		}
		reportUserMetricsToSegment(
			r.segmentClient,
			userID,
			prefix,
			action,
			value,
			r.clusterID,
		)
	} else {
		lion.Errorf("Error extracting userid metadata from context: %v\n", ctx)
	}
}

// ReportAndFlushUserAction immediately reports the metric
// It is used in the few places we need to report metrics from the client.
// It handles reporting the start, finish, and error conditions of the action
func ReportAndFlushUserAction(action string) func(time.Time, error) {
	// If we report nil, segment sees it, but mixpanel omits the field
	reportAndFlushUserAction(fmt.Sprintf("%vStarted", action), 0)
	return func(start time.Time, err error) {
		if err == nil {
			reportAndFlushUserAction(fmt.Sprintf("%vFinished", action), time.Since(start).Seconds())
		} else {
			reportAndFlushUserAction(fmt.Sprintf("%vErrored", action), err.Error())
		}
	}
}

func reportAndFlushUserAction(action string, value interface{}) {
	client := newSegmentClient()
	defer client.Close()
	cfg, err := config.Read()
	if err != nil {
		lion.Errorf("Error reading userid from ~/.pachyderm/config: %v\n", err)
		// metrics errors are non fatal
		return
	}
	reportUserMetricsToSegment(client, cfg.UserID, "user", action, value, "")
}

func (r *Reporter) dbMetrics(metrics *Metrics) {
	cursor, err := gorethink.Object(
		"Repos",
		gorethink.DB(r.pfsDbName).Table("Repos").Count(),
		"Commits",
		gorethink.DB(r.pfsDbName).Table("Commits").Count(),
		"ArchivedCommits",
		gorethink.DB(r.pfsDbName).Table("Commits").Filter(
			map[string]interface{}{
				"Archived": true,
			},
		).Count(),
		"CancelledCommits",
		gorethink.DB(r.pfsDbName).Table("Commits").Filter(
			map[string]interface{}{
				"Cancelled": true,
			},
		).Count(),
		"Files",
		gorethink.DB(r.pfsDbName).Table("Diffs").Group("Path").Ungroup().Count(),
		"Jobs",
		gorethink.DB(r.ppsDbName).Table("JobInfos").Count(),
		"Pipelines",
		gorethink.DB(r.ppsDbName).Table("PipelineInfos").Count(),
	).Run(r.dbClient)
	if err != nil {
		lion.Errorf("Error Fetching Metrics:%+v", err)
	}
	cursor.One(&metrics)
}

func (r *Reporter) reportClusterMetrics() {
	for {
		time.Sleep(reportingInterval)
		metrics := &Metrics{}
		r.dbMetrics(metrics)
		externalMetrics(r.kubeClient, metrics)
		metrics.ClusterID = r.clusterID
		metrics.PodID = uuid.NewWithoutDashes()
		metrics.Version = version.PrettyPrintVersion(version.Version)
		reportClusterMetricsToSegment(r.segmentClient, metrics)
	}
}
<|endoftext|>