1package writefreely 2 3import ( 4 "encoding/json" 5 "fmt" 6 "html/template" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "os" 11 "path/filepath" 12 "strings" 13 "time" 14 15 "github.com/hashicorp/go-multierror" 16 "github.com/writeas/impart" 17 wfimport "github.com/writeas/import" 18 "github.com/writeas/web-core/log" 19) 20 21func viewImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error { 22 // Fetch extra user data 23 p := NewUserPage(app, r, u, "Import Posts", nil) 24 25 c, err := app.db.GetCollections(u, app.Config().App.Host) 26 if err != nil { 27 return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("unable to fetch collections: %v", err)} 28 } 29 30 d := struct { 31 *UserPage 32 Collections *[]Collection 33 Flashes []template.HTML 34 Message string 35 InfoMsg bool 36 }{ 37 UserPage: p, 38 Collections: c, 39 Flashes: []template.HTML{}, 40 } 41 42 flashes, _ := getSessionFlashes(app, w, r, nil) 43 for _, flash := range flashes { 44 if strings.HasPrefix(flash, "SUCCESS: ") { 45 d.Message = strings.TrimPrefix(flash, "SUCCESS: ") 46 } else if strings.HasPrefix(flash, "INFO: ") { 47 d.Message = strings.TrimPrefix(flash, "INFO: ") 48 d.InfoMsg = true 49 } else { 50 d.Flashes = append(d.Flashes, template.HTML(flash)) 51 } 52 } 53 54 showUserPage(w, "import", d) 55 return nil 56} 57 58func handleImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error { 59 // limit 10MB per submission 60 r.ParseMultipartForm(10 << 20) 61 62 collAlias := r.PostFormValue("collection") 63 coll := &Collection{ 64 ID: 0, 65 } 66 var err error 67 if collAlias != "" { 68 coll, err = app.db.GetCollection(collAlias) 69 if err != nil { 70 log.Error("Unable to get collection for import: %s", err) 71 return err 72 } 73 // Only allow uploading to collection if current user is owner 74 if coll.OwnerID != u.ID { 75 err := ErrUnauthorizedGeneral 76 _ = addSessionFlash(app, w, r, err.Message, nil) 77 return err 78 } 79 coll.hostName = app.cfg.App.Host 80 } 81 82 fileDates := make(map[string]int64) 83 err = json.Unmarshal([]byte(r.FormValue("fileDates")), &fileDates) 84 if err != nil { 85 log.Error("invalid form data for file dates: %v", err) 86 return impart.HTTPError{http.StatusBadRequest, "form data for file dates was invalid"} 87 } 88 files := r.MultipartForm.File["files"] 89 var fileErrs []error 90 filesSubmitted := len(files) 91 var filesImported int 92 for _, formFile := range files { 93 fname := "" 94 ok := func() bool { 95 file, err := formFile.Open() 96 if err != nil { 97 fileErrs = append(fileErrs, fmt.Errorf("Unable to read file %s", formFile.Filename)) 98 log.Error("import file: open from form: %v", err) 99 return false 100 } 101 defer file.Close() 102 103 tempFile, err := ioutil.TempFile("", "post-upload-*.txt") 104 if err != nil { 105 fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename)) 106 log.Error("import file: create temp file %s: %v", formFile.Filename, err) 107 return false 108 } 109 defer tempFile.Close() 110 111 _, err = io.Copy(tempFile, file) 112 if err != nil { 113 fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename)) 114 log.Error("import file: copy to temp location %s: %v", formFile.Filename, err) 115 return false 116 } 117 118 info, err := tempFile.Stat() 119 if err != nil { 120 fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename)) 121 log.Error("import file: stat temp file %s: %v", formFile.Filename, err) 122 return false 123 } 124 fname = info.Name() 125 return true 126 }() 127 if !ok { 128 continue 129 } 130 131 post, err := wfimport.FromFile(filepath.Join(os.TempDir(), fname)) 132 if err == wfimport.ErrEmptyFile { 133 // not a real error so don't log 134 _ = addSessionFlash(app, w, r, fmt.Sprintf("%s was empty, import skipped", formFile.Filename), nil) 135 continue 136 } else if err == wfimport.ErrInvalidContentType { 137 // same as above 138 _ = addSessionFlash(app, w, r, fmt.Sprintf("%s is not a supported post file", formFile.Filename), nil) 139 continue 140 } else if err != nil { 141 fileErrs = append(fileErrs, fmt.Errorf("failed to read copy of %s", formFile.Filename)) 142 log.Error("import textfile: file to post: %v", err) 143 continue 144 } 145 146 if collAlias != "" { 147 post.Collection = collAlias 148 } 149 dateTime := time.Unix(fileDates[formFile.Filename], 0) 150 post.Created = &dateTime 151 created := post.Created.Format("2006-01-02T15:04:05Z") 152 submittedPost := SubmittedPost{ 153 Title: &post.Title, 154 Content: &post.Content, 155 Font: "norm", 156 Created: &created, 157 } 158 rp, err := app.db.CreatePost(u.ID, coll.ID, &submittedPost) 159 if err != nil { 160 fileErrs = append(fileErrs, fmt.Errorf("failed to create post from %s", formFile.Filename)) 161 log.Error("import textfile: create db post: %v", err) 162 continue 163 } 164 165 // Federate post, if necessary 166 if app.cfg.App.Federation && coll.ID > 0 { 167 go federatePost( 168 app, 169 &PublicPost{ 170 Post: rp, 171 Collection: &CollectionObj{ 172 Collection: *coll, 173 }, 174 }, 175 coll.ID, 176 false, 177 ) 178 } 179 filesImported++ 180 } 181 if len(fileErrs) != 0 { 182 _ = addSessionFlash(app, w, r, multierror.ListFormatFunc(fileErrs), nil) 183 } 184 185 if filesImported == filesSubmitted { 186 verb := "posts" 187 if filesSubmitted == 1 { 188 verb = "post" 189 } 190 _ = addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: Import complete, %d %s imported.", filesImported, verb), nil) 191 } else if filesImported > 0 { 192 _ = addSessionFlash(app, w, r, fmt.Sprintf("INFO: %d of %d posts imported, see details below.", filesImported, filesSubmitted), nil) 193 } 194 return impart.HTTPError{http.StatusFound, "/me/import"} 195} 196