Thorben GüntherUpdate links7d5d49c2 years agoHistory
180 lines · 4.3 KB · Go
RawDownload
package export import (	"context"	"errors"	"fmt"	"io"	"log"	"net/http"	"os"	"path"	"time" 	"git.sr.ht/~emersion/gqlclient" 	"git.sr.ht/~xenrox/hut/srht/listssrht") const archiveFilename = "archive.mbox" type ListsExporter struct {	client *gqlclient.Client	http   *http.Client} func NewListsExporter(client *gqlclient.Client, http *http.Client) *ListsExporter {	newHttp := *http	// XXX: Is this a sane default? Maybe large lists or slow	// connections could require more. Would be nice to ensure a	// constant flow of data rather than ensuring the entire request is	// complete within a deadline. Would also be nice to use range	// headers to be able to resume this on failure or interruption.	newHttp.Timeout = 10 * time.Minute	return &ListsExporter{		client: client,		http:   &newHttp,	}} // A subset of listssrht.MailingList which only contains the fields we want to// export (i.e. the ones filled in by the GraphQL query)type MailingListInfo struct {	Info	Description *string              `json:"description"`	Visibility  listssrht.Visibility `json:"visibility"`	PermitMime  []string             `json:"permitMime"`	RejectMime  []string             `json:"rejectMime"`} func (ex *ListsExporter) Export(ctx context.Context, dir string) error {	var cursor *listssrht.Cursor	var ret error 	for {		user, err := listssrht.ExportMailingLists(ex.client, ctx, cursor)		if err != nil {			return err		} 		for _, list := range user.Lists.Results {			base := path.Join(dir, list.Name)			if err := os.MkdirAll(base, 0o755); err != nil {				return err			} 			if err := ex.exportList(ctx, &list, base); err != nil {				var pe partialError				if errors.As(err, &pe) {					ret = err					continue				}				return err			}		} 		cursor = user.Lists.Cursor		if cursor == nil {			break		}	} 	return ret} func (ex *ListsExporter) ExportResource(ctx context.Context, dir, owner, resource string) error {	user, err := listssrht.ExportMailingList(ex.client, ctx, owner, resource)	if err != nil {		return err	}	return ex.exportList(ctx, user.List, dir)} func (ex *ListsExporter) exportList(ctx context.Context, list *listssrht.MailingList, base string) error {	infoPath := path.Join(base, infoFilename)	if _, err := os.Stat(infoPath); err == nil {		log.Printf("\tSkipping %s (already exists)", list.Name)		return nil	} 	log.Printf("\t%s", list.Name)	req, err := http.NewRequestWithContext(ctx, http.MethodGet,		string(list.Archive), nil)	if err != nil {		return err	}	resp, err := ex.http.Do(req)	if err != nil {		return err	}	defer resp.Body.Close() 	if resp.StatusCode != http.StatusOK {		return partialError{fmt.Errorf("%s: server returned non-200 status %d",			list.Name, resp.StatusCode)}	} 	archive, err := os.Create(path.Join(base, archiveFilename))	if err != nil {		return err	}	defer archive.Close()	if _, err := io.Copy(archive, resp.Body); err != nil {		return err	} 	listInfo := MailingListInfo{		Info: Info{			Service: "lists.sr.ht",			Name:    list.Name,		},		Description: list.Description,		Visibility:  list.Visibility,		PermitMime:  list.PermitMime,		RejectMime:  list.RejectMime,	}	if err := writeJSON(infoPath, &listInfo); err != nil {		return err	} 	return nil} func (ex *ListsExporter) ImportResource(ctx context.Context, dir string) error {	var info MailingListInfo	if err := readJSON(path.Join(dir, infoFilename), &info); err != nil {		return err	} 	return ex.importList(ctx, &info, dir)} func (ex *ListsExporter) importList(ctx context.Context, list *MailingListInfo, base string) error {	l, err := listssrht.CreateMailingList(ex.client, ctx, list.Name, list.Description, list.Visibility)	if err != nil {		return fmt.Errorf("failed to create mailing list: %v", err)	} 	if _, err := listssrht.UpdateMailingList(ex.client, ctx, l.Id, listssrht.MailingListInput{		PermitMime: list.PermitMime,		RejectMime: list.RejectMime,	}); err != nil {		return fmt.Errorf("failed to update mailing list: %v", err)	} 	archive, err := os.Open(path.Join(base, archiveFilename))	if err != nil {		return err	}	defer archive.Close() 	if _, err := listssrht.ImportMailingListSpool(ex.client, ctx, l.Id, gqlclient.Upload{		Filename: archiveFilename,		MIMEType: "application/mbox",		Body:     archive,	}); err != nil {		return fmt.Errorf("failed to import mailing list emails: %v", err)	} 	return nil}