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{}{
{{$base := .Host.ImporterBaseURL}}
{{range .Importers}}
- - {{.Name}}
+ {{if .TODOIssue}}
+ - {{.Title}}: TODO: Issue {{.TODOIssue}}
+ {{else}}
+ - {{.Title}}{{if .Description}}: {{.Description}}{{end}}
+ {{end}}
{{end}}
{{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,
}