1package launchpad
  2
  3import (
  4	"context"
  5	"fmt"
  6	"time"
  7
  8	"github.com/MichaelMure/git-bug/bridge/core"
  9	"github.com/MichaelMure/git-bug/cache"
 10	"github.com/MichaelMure/git-bug/entities/bug"
 11	"github.com/MichaelMure/git-bug/entity"
 12	"github.com/MichaelMure/git-bug/util/text"
 13)
 14
 15type launchpadImporter struct {
 16	conf core.Configuration
 17}
 18
 19func (li *launchpadImporter) Init(_ context.Context, repo *cache.RepoCache, conf core.Configuration) error {
 20	li.conf = conf
 21	return nil
 22}
 23
 24func (li *launchpadImporter) ensurePerson(repo *cache.RepoCache, owner LPPerson) (*cache.IdentityCache, error) {
 25	// Look first in the cache
 26	i, err := repo.ResolveIdentityImmutableMetadata(metaKeyLaunchpadLogin, owner.Login)
 27	if err == nil {
 28		return i, nil
 29	}
 30	if entity.IsErrMultipleMatch(err) {
 31		return nil, err
 32	}
 33
 34	return repo.NewIdentityRaw(
 35		owner.Name,
 36		"",
 37		owner.Login,
 38		"",
 39		nil,
 40		map[string]string{
 41			metaKeyLaunchpadLogin: owner.Login,
 42		},
 43	)
 44}
 45
 46func (li *launchpadImporter) ImportAll(ctx context.Context, repo *cache.RepoCache, since time.Time) (<-chan core.ImportResult, error) {
 47	out := make(chan core.ImportResult)
 48	lpAPI := new(launchpadAPI)
 49
 50	err := lpAPI.Init()
 51	if err != nil {
 52		return nil, err
 53	}
 54
 55	lpBugs, err := lpAPI.SearchTasks(ctx, li.conf["project"])
 56	if err != nil {
 57		return nil, err
 58	}
 59
 60	go func() {
 61		for _, lpBug := range lpBugs {
 62			select {
 63			case <-ctx.Done():
 64				return
 65			default:
 66				lpBugID := fmt.Sprintf("%d", lpBug.ID)
 67				b, err := repo.ResolveBugMatcher(func(excerpt *cache.BugExcerpt) bool {
 68					return excerpt.CreateMetadata[core.MetaKeyOrigin] == target &&
 69						excerpt.CreateMetadata[metaKeyLaunchpadID] == lpBugID
 70				})
 71				if err != nil && err != bug.ErrBugNotExist {
 72					out <- core.NewImportError(err, entity.Id(lpBugID))
 73					return
 74				}
 75
 76				owner, err := li.ensurePerson(repo, lpBug.Owner)
 77				if err != nil {
 78					out <- core.NewImportError(err, entity.Id(lpBugID))
 79					return
 80				}
 81
 82				if err == bug.ErrBugNotExist {
 83					createdAt, _ := time.Parse(time.RFC3339, lpBug.CreatedAt)
 84					b, _, err = repo.NewBugRaw(
 85						owner,
 86						createdAt.Unix(),
 87						text.CleanupOneLine(lpBug.Title),
 88						text.Cleanup(lpBug.Description),
 89						nil,
 90						map[string]string{
 91							core.MetaKeyOrigin: target,
 92							metaKeyLaunchpadID: lpBugID,
 93						},
 94					)
 95					if err != nil {
 96						out <- core.NewImportError(err, entity.Id(lpBugID))
 97						return
 98					}
 99
100					out <- core.NewImportBug(b.Id())
101
102				}
103
104				/* Handle messages */
105				if len(lpBug.Messages) == 0 {
106					return
107				}
108
109				// The Launchpad API returns the bug description as the first
110				// comment, so skip it.
111				for _, lpMessage := range lpBug.Messages[1:] {
112					_, err := b.ResolveOperationWithMetadata(metaKeyLaunchpadID, lpMessage.ID)
113					if err != nil && err != cache.ErrNoMatchingOp {
114						out <- core.NewImportError(err, entity.Id(lpMessage.ID))
115						return
116					}
117
118					// If this comment already exists, we are probably
119					// updating an existing bug. We do not want to duplicate
120					// the comments, so let us just skip this one.
121					// TODO: Can Launchpad comments be edited?
122					if err == nil {
123						continue
124					}
125
126					owner, err := li.ensurePerson(repo, lpMessage.Owner)
127					if err != nil {
128						out <- core.NewImportError(err, "")
129						return
130					}
131
132					// This is a new comment, we can add it.
133					createdAt, _ := time.Parse(time.RFC3339, lpMessage.CreatedAt)
134					op, err := b.AddCommentRaw(
135						owner,
136						createdAt.Unix(),
137						text.Cleanup(lpMessage.Content),
138						nil,
139						map[string]string{
140							metaKeyLaunchpadID: lpMessage.ID,
141						})
142					if err != nil {
143						out <- core.NewImportError(err, b.Id())
144						return
145					}
146
147					out <- core.NewImportComment(op.Id())
148				}
149
150				if !b.NeedCommit() {
151					out <- core.NewImportNothing(b.Id(), "no imported operation")
152				} else if err := b.Commit(); err != nil {
153					out <- core.NewImportError(err, "")
154					return
155				}
156			}
157		}
158	}()
159
160	return out, nil
161}