github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/importer/dummy/dummy.go (about)

     1  /*
     2  Copyright 2013 The Camlistore Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package dummy is an example importer for development purposes.
    18  package dummy
    19  
    20  import (
    21  	"fmt"
    22  	"log"
    23  	"math/rand"
    24  	"net/http"
    25  	"os"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  
    30  	"camlistore.org/pkg/blob"
    31  	"camlistore.org/pkg/httputil"
    32  	"camlistore.org/pkg/importer"
    33  	"camlistore.org/pkg/schema"
    34  )
    35  
    36  func init() {
    37  	if os.Getenv("CAMLI_DEV_CAMLI_ROOT") == "" {
    38  		// For this particular example importer, we only
    39  		// register it if we're in "devcam server" mode.
    40  		// Normally you'd avoid this check.
    41  		return
    42  	}
    43  
    44  	// This Register call must happen during init.
    45  	//
    46  	// Register only registers an importer site type and not a
    47  	// specific account on a site.
    48  	importer.Register("dummy", &imp{})
    49  }
    50  
    51  // imp is the dummy importer, as a demo of how to write an importer.
    52  //
    53  // It must implement the importer.Importer interface in order for
    54  // it to be registered (in the init above).
    55  type imp struct {
    56  	// The struct or underlying type implementing an importer
    57  	// holds state that is global, and not per-account, so it
    58  	// should not be used to cache account-specific
    59  	// resources. Some importers (e.g. Foursquare) use this space
    60  	// to cache mappings from site-specific global resource URLs
    61  	// (e.g. category icons) to the fileref once it's been copied
    62  	// into Camlistore.
    63  
    64  	mu          sync.Mutex          // mu guards cache
    65  	categoryRef map[string]blob.Ref // URL -> file schema ref
    66  }
    67  
    68  func (*imp) NeedsAPIKey() bool {
    69  	// This tells the importer framework that we our importer will
    70  	// be calling the {RunContext,SetupContext}.Credentials method
    71  	// to get the OAuth client ID & client secret, which may be
    72  	// either configured on the importer permanode, or statically
    73  	// in the server's config file.
    74  	return true
    75  }
    76  
    77  const (
    78  	acctAttrToken     = "my_token"
    79  	acctAttrUsername  = "username"
    80  	acctAttrRunNumber = "run_number" // some state
    81  )
    82  
    83  func (*imp) IsAccountReady(acct *importer.Object) (ready bool, err error) {
    84  	// This method tells the importer framework whether this account
    85  	// permanode (accessed via the importer.Object) is ready to start
    86  	// an import.  Here you would typically check whether you have the
    87  	// right metadata/tokens on the account.
    88  	return acct.Attr(acctAttrToken) != "" && acct.Attr(acctAttrUsername) != "", nil
    89  }
    90  
    91  func (*imp) SummarizeAccount(acct *importer.Object) string {
    92  	// This method is run by the importer framework if the account is
    93  	// ready (see IsAccountReady) and summarizes the account in
    94  	// the list of accounts on the importer page.
    95  	return acct.Attr(acctAttrUsername)
    96  }
    97  
    98  func (*imp) ServeSetup(w http.ResponseWriter, r *http.Request, ctx *importer.SetupContext) error {
    99  	// ServeSetup gets called at the beginning of adding a new account
   100  	// to an importer, or when an account is being re-logged into to
   101  	// refresh its access token.
   102  	// You typically start the OAuth redirect flow here.
   103  	// The importer.OAuth2.RedirectURL and importer.OAuth2.RedirectState helpers can be used for OAuth2.
   104  	http.Redirect(w, r, ctx.CallbackURL(), http.StatusFound)
   105  	return nil
   106  }
   107  
   108  // Statically declare that our importer supports the optional
   109  // importer.ImporterSetupHTMLer interface.
   110  //
   111  // We do this in case importer.ImporterSetupHTMLer changes, or if we
   112  // typo the method name below. It turns this into a compile-time
   113  // error. In general you should do this in Go whenever you implement
   114  // optional interfaces.
   115  var _ importer.ImporterSetupHTMLer = (*imp)(nil)
   116  
   117  func (im *imp) AccountSetupHTML(host *importer.Host) string {
   118  	return "<h1>Hello from the dummy importer!</h1><p>I am example HTML. This importer is a demo of how to write an importer.</p>"
   119  }
   120  
   121  func (im *imp) ServeCallback(w http.ResponseWriter, r *http.Request, ctx *importer.SetupContext) {
   122  	// ServeCallback is called after ServeSetup, at the end of an
   123  	// OAuth redirect flow.
   124  
   125  	code := r.FormValue("code") // e.g. get the OAuth code out of the redirect
   126  	if code == "" {
   127  		code = "some_dummy_code"
   128  	}
   129  	name := ctx.AccountNode.Attr(acctAttrUsername)
   130  	if name == "" {
   131  		names := []string{
   132  			"alfred", "alice", "bob", "bethany",
   133  			"cooper", "claire", "doug", "darla",
   134  			"ed", "eve", "frank", "francine",
   135  		}
   136  		name = names[rand.Intn(len(names))]
   137  	}
   138  	if err := ctx.AccountNode.SetAttrs(
   139  		"title", fmt.Sprintf("dummy account: %s", name),
   140  		acctAttrUsername, name,
   141  		acctAttrToken, code,
   142  	); err != nil {
   143  		httputil.ServeError(w, r, fmt.Errorf("Error setting attributes: %v", err))
   144  		return
   145  	}
   146  	http.Redirect(w, r, ctx.AccountURL(), http.StatusFound)
   147  }
   148  
   149  func (im *imp) Run(ctx *importer.RunContext) (err error) {
   150  	log.Printf("Running dummy importer.")
   151  	defer func() {
   152  		log.Printf("Dummy importer returned: %v", err)
   153  	}()
   154  	root := ctx.RootNode()
   155  	fileRef, err := schema.WriteFileFromReader(ctx.Host.Target(), "foo.txt", strings.NewReader("Some file.\n"))
   156  	if err != nil {
   157  		return err
   158  	}
   159  	obj, err := root.ChildPathObject("foo.txt")
   160  	if err != nil {
   161  		return err
   162  	}
   163  	if err = obj.SetAttr("camliContent", fileRef.String()); err != nil {
   164  		return err
   165  	}
   166  	n, _ := strconv.Atoi(ctx.AccountNode().Attr(acctAttrRunNumber))
   167  	n++
   168  	ctx.AccountNode().SetAttr(acctAttrRunNumber, fmt.Sprint(n))
   169  	// Update the title each time, just to show it working. You
   170  	// wouldn't actually do this:
   171  	return root.SetAttr("title", fmt.Sprintf("dummy: %s import #%d", ctx.AccountNode().Attr(acctAttrUsername), n))
   172  }
   173  
   174  func (im *imp) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   175  	httputil.BadRequestError(w, "Unexpected path: %s", r.URL.Path)
   176  }
   177  
   178  func (im *imp) CallbackRequestAccount(r *http.Request) (blob.Ref, error) {
   179  	// We do not actually use OAuth, but this method works for us anyway.
   180  	// Even if your importer implementation does not use OAuth, you can
   181  	// probably just embed importer.OAuth1 in your implementation type.
   182  	// If OAuth2, embedding importer.OAuth2 should work.
   183  	return importer.OAuth1{}.CallbackRequestAccount(r)
   184  }
   185  
   186  func (im *imp) CallbackURLParameters(acctRef blob.Ref) string {
   187  	// See comment in CallbackRequestAccount.
   188  	return importer.OAuth1{}.CallbackURLParameters(acctRef)
   189  }