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 }