diff options
Diffstat (limited to 'bridge/launchpad')
-rw-r--r-- | bridge/launchpad/import.go | 185 | ||||
-rw-r--r-- | bridge/launchpad/launchpad_api.go | 54 |
2 files changed, 112 insertions, 127 deletions
diff --git a/bridge/launchpad/import.go b/bridge/launchpad/import.go index 7ef11416..7f50d898 100644 --- a/bridge/launchpad/import.go +++ b/bridge/launchpad/import.go @@ -1,11 +1,10 @@ package launchpad import ( + "context" "fmt" "time" - "github.com/pkg/errors" - "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" @@ -45,98 +44,116 @@ func (li *launchpadImporter) ensurePerson(repo *cache.RepoCache, owner LPPerson) ) } -func (li *launchpadImporter) ImportAll(repo *cache.RepoCache, since time.Time) error { +func (li *launchpadImporter) ImportAll(ctx context.Context, repo *cache.RepoCache, since time.Time) (<-chan core.ImportResult, error) { + out := make(chan core.ImportResult) lpAPI := new(launchpadAPI) err := lpAPI.Init() if err != nil { - return err + return nil, err } - lpBugs, err := lpAPI.SearchTasks(li.conf["project"]) + lpBugs, err := lpAPI.SearchTasks(ctx, li.conf["project"]) if err != nil { - return err + return nil, err } - for _, lpBug := range lpBugs { - var b *cache.BugCache - var err error - - lpBugID := fmt.Sprintf("%d", lpBug.ID) - b, err = repo.ResolveBugCreateMetadata(keyLaunchpadID, lpBugID) - if err != nil && err != bug.ErrBugNotExist { - return err - } - - owner, err := li.ensurePerson(repo, lpBug.Owner) - if err != nil { - return err - } - - if err == bug.ErrBugNotExist { - createdAt, _ := time.Parse(time.RFC3339, lpBug.CreatedAt) - b, _, err = repo.NewBugRaw( - owner, - createdAt.Unix(), - lpBug.Title, - lpBug.Description, - nil, - map[string]string{ - keyLaunchpadID: lpBugID, - }, - ) - if err != nil { - return errors.Wrapf(err, "failed to add bug id #%s", lpBugID) + go func() { + for _, lpBug := range lpBugs { + select { + case <-ctx.Done(): + return + default: + lpBugID := fmt.Sprintf("%d", lpBug.ID) + b, err := repo.ResolveBugCreateMetadata(keyLaunchpadID, lpBugID) + if err != nil && err != bug.ErrBugNotExist { + out <- core.NewImportError(err, entity.Id(lpBugID)) + return + } + + owner, err := li.ensurePerson(repo, lpBug.Owner) + if err != nil { + out <- core.NewImportError(err, entity.Id(lpBugID)) + return + } + + if err == bug.ErrBugNotExist { + createdAt, _ := time.Parse(time.RFC3339, lpBug.CreatedAt) + b, _, err = repo.NewBugRaw( + owner, + createdAt.Unix(), + lpBug.Title, + lpBug.Description, + nil, + map[string]string{ + keyLaunchpadID: lpBugID, + }, + ) + if err != nil { + out <- core.NewImportError(err, entity.Id(lpBugID)) + return + } + + out <- core.NewImportBug(b.Id()) + + } + + /* Handle messages */ + if len(lpBug.Messages) == 0 { + err := fmt.Sprintf("bug doesn't have any comments") + out <- core.NewImportNothing(entity.Id(lpBugID), err) + return + } + + // The Launchpad API returns the bug description as the first + // comment, so skip it. + for _, lpMessage := range lpBug.Messages[1:] { + _, err := b.ResolveOperationWithMetadata(keyLaunchpadID, lpMessage.ID) + if err != nil && err != cache.ErrNoMatchingOp { + out <- core.NewImportError(err, entity.Id(lpMessage.ID)) + return + } + + // If this comment already exists, we are probably + // updating an existing bug. We do not want to duplicate + // the comments, so let us just skip this one. + // TODO: Can Launchpad comments be edited? + if err == nil { + continue + } + + owner, err := li.ensurePerson(repo, lpMessage.Owner) + if err != nil { + out <- core.NewImportError(err, "") + return + } + + // This is a new comment, we can add it. + createdAt, _ := time.Parse(time.RFC3339, lpMessage.CreatedAt) + op, err := b.AddCommentRaw( + owner, + createdAt.Unix(), + lpMessage.Content, + nil, + map[string]string{ + keyLaunchpadID: lpMessage.ID, + }) + if err != nil { + out <- core.NewImportError(err, op.Id()) + return + } + + out <- core.NewImportComment(op.Id()) + } + + err = b.CommitAsNeeded() + if err != nil { + out <- core.NewImportError(err, "") + return + } } - } else { - /* TODO: Update bug */ - fmt.Println("TODO: Update bug") } + }() - /* Handle messages */ - if len(lpBug.Messages) == 0 { - return errors.Wrapf(err, "failed to fetch comments for bug #%s", lpBugID) - } - - // The Launchpad API returns the bug description as the first - // comment, so skip it. - for _, lpMessage := range lpBug.Messages[1:] { - _, err := b.ResolveOperationWithMetadata(keyLaunchpadID, lpMessage.ID) - if err != nil && err != cache.ErrNoMatchingOp { - return errors.Wrapf(err, "failed to fetch comments for bug #%s", lpBugID) - } - - // If this comment already exists, we are probably - // updating an existing bug. We do not want to duplicate - // the comments, so let us just skip this one. - // TODO: Can Launchpad comments be edited? - if err == nil { - continue - } - - owner, err := li.ensurePerson(repo, lpMessage.Owner) - if err != nil { - return err - } - - // This is a new comment, we can add it. - createdAt, _ := time.Parse(time.RFC3339, lpMessage.CreatedAt) - _, err = b.AddCommentRaw( - owner, - createdAt.Unix(), - lpMessage.Content, - nil, - map[string]string{ - keyLaunchpadID: lpMessage.ID, - }) - if err != nil { - return errors.Wrapf(err, "failed to add comment to bug #%s", lpBugID) - } - } - err = b.CommitAsNeeded() - if err != nil { - return err - } - } - return nil + return out, nil } diff --git a/bridge/launchpad/launchpad_api.go b/bridge/launchpad/launchpad_api.go index 8cafa241..763e774e 100644 --- a/bridge/launchpad/launchpad_api.go +++ b/bridge/launchpad/launchpad_api.go @@ -14,6 +14,7 @@ package launchpad */ import ( + "context" "encoding/json" "fmt" "net/http" @@ -33,43 +34,6 @@ type LPPerson struct { // https://api.launchpad.net/devel/~login var personCache = make(map[string]LPPerson) -func (owner *LPPerson) UnmarshalJSON(data []byte) error { - type LPPersonX LPPerson // Avoid infinite recursion - var ownerLink string - if err := json.Unmarshal(data, &ownerLink); err != nil { - return err - } - - // First, try to gather info about the bug owner using our cache. - if cachedPerson, hasKey := personCache[ownerLink]; hasKey { - *owner = cachedPerson - return nil - } - - // If the bug owner is not already known, we have to send a request. - req, err := http.NewRequest("GET", ownerLink, nil) - if err != nil { - return nil - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil - } - - defer resp.Body.Close() - - var p LPPersonX - if err := json.NewDecoder(resp.Body).Decode(&p); err != nil { - return nil - } - *owner = LPPerson(p) - // Do not forget to update the cache. - personCache[ownerLink] = *owner - return nil -} - // LPBug describes a Launchpad bug. type LPBug struct { Title string `json:"title"` @@ -109,11 +73,13 @@ type launchpadAPI struct { } func (lapi *launchpadAPI) Init() error { - lapi.client = &http.Client{} + lapi.client = &http.Client{ + Timeout: defaultTimeout, + } return nil } -func (lapi *launchpadAPI) SearchTasks(project string) ([]LPBug, error) { +func (lapi *launchpadAPI) SearchTasks(ctx context.Context, project string) ([]LPBug, error) { var bugs []LPBug // First, let us build the URL. Not all statuses are included by @@ -153,7 +119,7 @@ func (lapi *launchpadAPI) SearchTasks(project string) ([]LPBug, error) { } for _, bugEntry := range result.Entries { - bug, err := lapi.queryBug(bugEntry.BugLink) + bug, err := lapi.queryBug(ctx, bugEntry.BugLink) if err == nil { bugs = append(bugs, bug) } @@ -170,13 +136,14 @@ func (lapi *launchpadAPI) SearchTasks(project string) ([]LPBug, error) { return bugs, nil } -func (lapi *launchpadAPI) queryBug(url string) (LPBug, error) { +func (lapi *launchpadAPI) queryBug(ctx context.Context, url string) (LPBug, error) { var bug LPBug req, err := http.NewRequest("GET", url, nil) if err != nil { return bug, err } + req = req.WithContext(ctx) resp, err := lapi.client.Do(req) if err != nil { @@ -191,7 +158,7 @@ func (lapi *launchpadAPI) queryBug(url string) (LPBug, error) { /* Fetch messages */ messagesCollectionLink := fmt.Sprintf("%s/bugs/%d/messages", apiRoot, bug.ID) - messages, err := lapi.queryMessages(messagesCollectionLink) + messages, err := lapi.queryMessages(ctx, messagesCollectionLink) if err != nil { return bug, err } @@ -200,7 +167,7 @@ func (lapi *launchpadAPI) queryBug(url string) (LPBug, error) { return bug, nil } -func (lapi *launchpadAPI) queryMessages(messagesURL string) ([]LPMessage, error) { +func (lapi *launchpadAPI) queryMessages(ctx context.Context, messagesURL string) ([]LPMessage, error) { var messages []LPMessage for { @@ -208,6 +175,7 @@ func (lapi *launchpadAPI) queryMessages(messagesURL string) ([]LPMessage, error) if err != nil { return nil, err } + req = req.WithContext(ctx) resp, err := lapi.client.Do(req) if err != nil { |