diff --git a/pkg/importer/allimporters/importers.go b/pkg/importer/allimporters/importers.go index 7a417467a..a5ec1a1bf 100644 --- a/pkg/importer/allimporters/importers.go +++ b/pkg/importer/allimporters/importers.go @@ -18,6 +18,7 @@ limitations under the License. package allimporters // import "perkeep.org/pkg/importer/allimporters" import ( + "perkeep.org/pkg/importer" _ "perkeep.org/pkg/importer/dummy" _ "perkeep.org/pkg/importer/feed" _ "perkeep.org/pkg/importer/flickr" @@ -28,3 +29,26 @@ import ( _ "perkeep.org/pkg/importer/swarm" _ "perkeep.org/pkg/importer/twitter" ) + +func init() { + importer.RegisterTODO("facebook", importer.Properties{ + Title: "Facebook", + Description: "import Facebook content", + TODOIssue: 1027, + }) + importer.RegisterTODO("runkeeper", importer.Properties{ + Title: "Runkeeper", + Description: "import workout data from Runkeeper", + TODOIssue: 1124, + }) + importer.RegisterTODO("strava", importer.Properties{ + Title: "Strava", + Description: "import workout data from Strava", + TODOIssue: 1125, + }) + importer.RegisterTODO("instagram", importer.Properties{ + Title: "Instagram", + Description: "import photos from Instagram", + TODOIssue: 1126, + }) +} diff --git a/pkg/importer/feed/feed.go b/pkg/importer/feed/feed.go index 3803cab0b..76170a811 100644 --- a/pkg/importer/feed/feed.go +++ b/pkg/importer/feed/feed.go @@ -57,6 +57,8 @@ type imp struct { func (*imp) Properties() importer.Properties { return importer.Properties{ + Title: "Feed", + Description: "importer for RSS, Atom, and RDF feeds", SupportsIncremental: true, NeedsAPIKey: false, } diff --git a/pkg/importer/flickr/flickr.go b/pkg/importer/flickr/flickr.go index 1a56ccd2c..78c1122eb 100644 --- a/pkg/importer/flickr/flickr.go +++ b/pkg/importer/flickr/flickr.go @@ -69,6 +69,8 @@ type imp struct { func (imp) Properties() importer.Properties { return importer.Properties{ + Title: "Flickr", + Description: "import your photos from Flickr.com", SupportsIncremental: false, NeedsAPIKey: true, } diff --git a/pkg/importer/gphotos/gphotos.go b/pkg/importer/gphotos/gphotos.go index 081fcb2f9..850c88bb6 100644 --- a/pkg/importer/gphotos/gphotos.go +++ b/pkg/importer/gphotos/gphotos.go @@ -87,6 +87,8 @@ type imp struct { func (imp) Properties() importer.Properties { return importer.Properties{ + Title: "Google Photos (via Drive API)", + Description: "import all your photos from Google Photos, via Google Drive. (requires settings changes in Drive)", SupportsIncremental: true, // TODO: but not well NeedsAPIKey: true, } diff --git a/pkg/importer/html.go b/pkg/importer/html.go index c23f1169d..70de805d9 100644 --- a/pkg/importer/html.go +++ b/pkg/importer/html.go @@ -104,7 +104,11 @@ var tmpl = template.Must(template.New("root").Funcs(map[string]interface{}{ {{end}} diff --git a/pkg/importer/importer.go b/pkg/importer/importer.go index 509ec39f1..caf908557 100644 --- a/pkg/importer/importer.go +++ b/pkg/importer/importer.go @@ -92,6 +92,13 @@ type Importer interface { // Properties contains the properties of an importer type. type Properties struct { + Title string + Description string + + // TODOIssue, if non-zero, marks the importer as invalid, but the UI + // will link to a tracking bug for implementing it. + TODOIssue int + // NeedsAPIKey reports whether this importer requires an API key // (OAuth2 client_id & client_secret, or equivalent). // If the API only requires a username & password, or a flow to get @@ -180,6 +187,10 @@ func Register(name string, im Importer) { importers[name] = im } +func RegisterTODO(name string, p Properties) { + Register(name, &todoImp{Props: p}) +} + func init() { // Register the meta "importer" handler, which handles all other handlers. blobserver.RegisterHandlerConstructor("importer", newFromConfig) @@ -558,6 +569,9 @@ func (h *Host) serveImportersRoot(w http.ResponseWriter, r *http.Request) { for _, v := range h.importers { body.Importers = append(body.Importers, h.imp[v]) } + sort.Slice(body.Importers, func(i, j int) bool { + return body.Importers[i].Title() < body.Importers[j].Title() + }) h.execTemplate(w, r, importersRootPage{ Title: "Importers", Body: body, @@ -656,6 +670,7 @@ func (h *Host) serveImporterAccount(w http.ResponseWriter, r *http.Request, imp func (h *Host) startPeriodicImporters() { res, err := h.search.Query(context.TODO(), &search.SearchQuery{ + Sort: search.Unsorted, Expression: "attr:camliNodeType:importerAccount", Describe: &search.DescribeRequest{ Depth: 1, @@ -788,6 +803,21 @@ type importer struct { func (im *importer) Name() string { return im.name } +func (im *importer) Title() string { + if im.props.Title != "" { + return im.props.Title + } + return im.name +} + +func (im *importer) TODOIssue() int { + return im.props.TODOIssue +} + +func (im *importer) Description() string { + return im.props.Description +} + // ImporterType returns the account permanode's attrImporterType // value. This is almost always the same as the Name, except in cases // where a product gets rebranded. (e.g. "foursquare" to "swarm", in @@ -961,6 +991,7 @@ func (im *importer) Accounts() ([]*importerAcct, error) { if needQuery { res, err := im.host.search.Query(context.TODO(), &search.SearchQuery{ + Sort: search.Unsorted, Expression: fmt.Sprintf("attr:%s:%s attr:%s:%s", attrNodeType, nodeTypeImporterAccount, attrImporterType, im.ImporterType(), @@ -1010,6 +1041,7 @@ func (im *importer) Node() (*Object, error) { attrImporterType, im.ImporterType(), ) res, err := im.host.search.Query(context.TODO(), &search.SearchQuery{ + Sort: search.Unsorted, Limit: 10, // might be more than one because of multiple blob hash types Expression: expr, }) diff --git a/pkg/importer/noop_test.go b/pkg/importer/noop_test.go index 0748fa721..70b632aec 100644 --- a/pkg/importer/noop_test.go +++ b/pkg/importer/noop_test.go @@ -16,45 +16,4 @@ limitations under the License. package importer -import ( - "errors" - "fmt" - "net/http" -) - -var TODOImporter Importer = todoImp{} - -type todoImp struct { - OAuth1 // for CallbackRequestAccount and CallbackURLParameters -} - -func (todoImp) Properties() Properties { - return Properties{ - NeedsAPIKey: false, - SupportsIncremental: false, - } -} - -func (todoImp) Run(*RunContext) error { - return errors.New("fake error from todo importer") -} - -func (todoImp) IsAccountReady(acctNode *Object) (ok bool, err error) { - return -} - -func (todoImp) SummarizeAccount(acctNode *Object) string { return "" } - -func (todoImp) ServeSetup(w http.ResponseWriter, r *http.Request, ctx *SetupContext) error { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - fmt.Fprintf(w, "The Setup page for the TODO importer.\nnode = %v\ncallback = %s\naccount URL = %s\n", - ctx.AccountNode, - ctx.CallbackURL(), - "ctx.AccountURL()") - return nil -} - -func (todoImp) ServeCallback(w http.ResponseWriter, r *http.Request, ctx *SetupContext) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - fmt.Fprintf(w, "The callback page for the TODO importer.\n") -} +var TODOImporter Importer = &todoImp{} diff --git a/pkg/importer/picasa/picasa.go b/pkg/importer/picasa/picasa.go index b5d396ebe..9fdde309c 100644 --- a/pkg/importer/picasa/picasa.go +++ b/pkg/importer/picasa/picasa.go @@ -84,6 +84,8 @@ type imp struct { func (imp) Properties() importer.Properties { return importer.Properties{ + Title: "Google Photos (via Picasa API)", + Description: "import your photos from Google Photos. (limited to 10,000 photos per Google Photos API bug for now)", SupportsIncremental: true, NeedsAPIKey: true, } diff --git a/pkg/importer/pinboard/pinboard.go b/pkg/importer/pinboard/pinboard.go index 9f41b1adf..ceeb90d4e 100644 --- a/pkg/importer/pinboard/pinboard.go +++ b/pkg/importer/pinboard/pinboard.go @@ -103,6 +103,8 @@ type imp struct { func (imp) Properties() importer.Properties { return importer.Properties{ + Title: "Pinboard", + Description: "import your pinboard.in posts", SupportsIncremental: false, NeedsAPIKey: false, } diff --git a/pkg/importer/plaid/plaid.go b/pkg/importer/plaid/plaid.go index 1140f999c..0cded2026 100644 --- a/pkg/importer/plaid/plaid.go +++ b/pkg/importer/plaid/plaid.go @@ -44,6 +44,8 @@ type imp struct{} func (*imp) Properties() importer.Properties { return importer.Properties{ + Title: "Plaid", + Description: "import your financial transactions from plaid.com", SupportsIncremental: true, NeedsAPIKey: true, } diff --git a/pkg/importer/swarm/swarm.go b/pkg/importer/swarm/swarm.go index 73aba8d1f..bb7253a04 100644 --- a/pkg/importer/swarm/swarm.go +++ b/pkg/importer/swarm/swarm.go @@ -88,6 +88,8 @@ type imp struct { func (*imp) Properties() importer.Properties { return importer.Properties{ + Title: "Swarm", + Description: "import check-ins and venues from Foursquare Swarm (swarmapp.com)", SupportsIncremental: true, NeedsAPIKey: true, PermanodeImporterType: "foursquare", // old brand name diff --git a/pkg/importer/twitter/twitter.go b/pkg/importer/twitter/twitter.go index 5a5a96152..19ddd39f8 100644 --- a/pkg/importer/twitter/twitter.go +++ b/pkg/importer/twitter/twitter.go @@ -104,6 +104,9 @@ type imp struct { func (*imp) Properties() importer.Properties { return importer.Properties{ + Title: "Twitter", + Description: "import tweets and media from tweets", + // TODO: doc URL for linking to info on historical tweets from ZIP files beyond API limit SupportsIncremental: true, NeedsAPIKey: true, }