1 package server
2
3 import (
4 "archive/zip"
5 "database/sql"
6 "fmt"
7 "io"
8 "log"
9 "os"
10 "strings"
11 "time"
12
13 "codeberg.org/tslocum/sriracha/internal/database"
14 . "codeberg.org/tslocum/sriracha/model"
15 . "codeberg.org/tslocum/sriracha/util"
16 _ "modernc.org/sqlite"
17 )
18
19 func (s *Server) _exportBoardPosts(db *database.DB, b *Board, threads [][2]int) (*os.File, error) {
20 f, err := os.CreateTemp("", "*.db")
21 if err != nil {
22 return nil, fmt.Errorf("failed to create temporary file: %s", err)
23 }
24
25 export, err := sql.Open("sqlite", f.Name())
26 if err != nil {
27 log.Fatalf("failed to open temporary file %s: %s", f.Name(), err)
28 }
29 defer export.Close()
30
31 _, err = export.Exec(`
32 CREATE TABLE post (
33 id INTEGER PRIMARY KEY,
34 parent INTEGER NOT NULL,
35 timestamp INTEGER NOT NULL,
36 bumped INTEGER NOT NULL,
37 name TEXT NOT NULL,
38 tripcode TEXT NOT NULL,
39 email TEXT NOT NULL,
40 nameblock TEXT NOT NULL,
41 subject TEXT NOT NULL,
42 message TEXT NOT NULL,
43 file TEXT NOT NULL,
44 filemime TEXT NOT NULL,
45 filehash TEXT NOT NULL,
46 fileoriginal TEXT NOT NULL,
47 filesize INTEGER NOT NULL,
48 filewidth INTEGER NOT NULL,
49 fileheight INTEGER NOT NULL,
50 thumb TEXT NOT NULL,
51 thumbwidth INTEGER NOT NULL,
52 thumbheight INTEGER NOT NULL,
53 stickied INTEGER NOT NULL,
54 locked INTEGER NOT NULL
55 );`)
56 if err != nil {
57 return nil, fmt.Errorf("failed to create post schema: %s", err)
58 }
59
60 var hash string
61 var stickied, locked int
62 for _, thread := range threads {
63 for _, p := range db.AllPostsInThread(thread[0], false) {
64 if p.IsEmbed() {
65 hash = p.FileHash
66 } else {
67 hash = ""
68 }
69 if p.Stickied {
70 stickied = 1
71 } else {
72 stickied = 0
73 }
74 if p.Locked {
75 locked = 1
76 } else {
77 locked = 0
78 }
79 _, err = export.Exec("INSERT INTO post VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
80 p.ID,
81 p.Parent,
82 p.Timestamp,
83 p.Bumped,
84 p.Name,
85 p.Tripcode,
86 p.Email,
87 p.NameBlock,
88 p.Subject,
89 p.Message,
90 p.File,
91 p.FileMIME,
92 hash,
93 p.FileOriginal,
94 p.FileSize,
95 p.FileWidth,
96 p.FileHeight,
97 p.Thumb,
98 p.ThumbWidth,
99 p.ThumbHeight,
100 stickied,
101 locked)
102 if err != nil {
103 return nil, fmt.Errorf("failed to export post: %s", err)
104 }
105 }
106 }
107 return f, nil
108 }
109
110 func (s *Server) exportPosts(db *database.DB, exportPath string) error {
111 boards := db.AllBoards()
112 if len(boards) == 0 {
113 return fmt.Errorf("no boards available to export")
114 }
115 var havePosts bool
116 for _, b := range boards {
117 if len(db.AllThreads(b, false)) > 0 {
118 havePosts = true
119 continue
120 }
121 }
122 if !havePosts {
123 return fmt.Errorf("no posts available to export")
124 }
125
126 _, err := os.Stat(exportPath)
127 if !os.IsNotExist(err) {
128 return fmt.Errorf("file %s already exists", exportPath)
129 }
130 zipFile, err := os.OpenFile(exportPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, NewFilePermission)
131 if err != nil {
132 return fmt.Errorf("failed to open zip file %s: %s", exportPath, err)
133 }
134 defer zipFile.Close()
135
136 zip := zip.NewWriter(zipFile)
137
138 date := time.Now().Format("20060102")
139 for _, b := range boards {
140 threads := db.AllThreads(b, false)
141 if len(threads) == 0 {
142 continue
143 }
144 fmt.Printf("Exporting %s...\n", b.Path())
145
146 fName := date
147 if b.Dir == "" {
148 fName += "_root"
149 } else {
150 fName += "_" + strings.ToLower(b.Dir)
151 }
152 if b.Description != "" {
153 fName += "_" + strings.ReplaceAll(strings.ToLower(b.Name), " ", "_")
154 }
155 fName += ".sriracha.db"
156
157 boardFile, err := s._exportBoardPosts(db, b, threads)
158 if err != nil {
159 return fmt.Errorf("failed to export board %s: %s", b.Path(), err)
160 }
161 zipBoardFile, err := zip.Create(fName)
162 if err != nil {
163 return fmt.Errorf("failed to create file in zip archive: %s", err)
164 }
165 _, err = io.Copy(zipBoardFile, boardFile)
166 if err != nil {
167 return fmt.Errorf("failed to write zip archive: %s", err)
168 }
169 boardFile.Close()
170 }
171
172 err = zip.Close()
173 if err != nil {
174 return fmt.Errorf("failed to write zip archive: %s", err)
175 }
176
177 fmt.Printf("Exported post data to %s\n", exportPath)
178 fmt.Printf("Warning: Attachment files are not included within the export. To import posts later, you will also need a copy of the src and thumb directories of each board.\n")
179 return nil
180 }
181
View as plain text