backup_zip.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. package backup
  2. import (
  3. "archive/zip"
  4. "bytes"
  5. "crypto/sha256"
  6. "encoding/hex"
  7. "io"
  8. "os"
  9. "path/filepath"
  10. "github.com/uozi-tech/cosy"
  11. )
  12. // createZipArchive creates a zip archive from a directory
  13. func createZipArchive(zipPath, srcDir string) error {
  14. // Create a new zip file
  15. zipFile, err := os.Create(zipPath)
  16. if err != nil {
  17. return cosy.WrapErrorWithParams(ErrCreateZipFile, err.Error())
  18. }
  19. defer zipFile.Close()
  20. // Create a new zip writer
  21. zipWriter := zip.NewWriter(zipFile)
  22. defer zipWriter.Close()
  23. // Walk through all files in the source directory
  24. err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
  25. if err != nil {
  26. return err
  27. }
  28. // Get relative path
  29. relPath, err := filepath.Rel(srcDir, path)
  30. if err != nil {
  31. return err
  32. }
  33. // Skip if it's the source directory itself
  34. if relPath == "." {
  35. return nil
  36. }
  37. // Check if it's a symlink
  38. if info.Mode()&os.ModeSymlink != 0 {
  39. // Get target of symlink
  40. linkTarget, err := os.Readlink(path)
  41. if err != nil {
  42. return cosy.WrapErrorWithParams(ErrReadSymlink, err.Error())
  43. }
  44. // Create symlink entry in zip
  45. header := &zip.FileHeader{
  46. Name: relPath,
  47. Method: zip.Deflate,
  48. }
  49. header.SetMode(info.Mode())
  50. writer, err := zipWriter.CreateHeader(header)
  51. if err != nil {
  52. return cosy.WrapErrorWithParams(ErrCreateZipEntry, err.Error())
  53. }
  54. // Write link target as content (common way to store symlinks in zip)
  55. _, err = writer.Write([]byte(linkTarget))
  56. if err != nil {
  57. return cosy.WrapErrorWithParams(ErrCopyContent, relPath)
  58. }
  59. return nil
  60. }
  61. // Create zip header
  62. header, err := zip.FileInfoHeader(info)
  63. if err != nil {
  64. return cosy.WrapErrorWithParams(ErrCreateZipHeader, err.Error())
  65. }
  66. // Set relative path as name
  67. header.Name = relPath
  68. if info.IsDir() {
  69. header.Name += "/"
  70. }
  71. // Set compression method
  72. header.Method = zip.Deflate
  73. // Create zip entry writer
  74. writer, err := zipWriter.CreateHeader(header)
  75. if err != nil {
  76. return cosy.WrapErrorWithParams(ErrCreateZipEntry, err.Error())
  77. }
  78. // Skip if it's a directory
  79. if info.IsDir() {
  80. return nil
  81. }
  82. // Open source file
  83. source, err := os.Open(path)
  84. if err != nil {
  85. return cosy.WrapErrorWithParams(ErrOpenSourceFile, err.Error())
  86. }
  87. defer source.Close()
  88. // Copy to zip
  89. _, err = io.Copy(writer, source)
  90. return err
  91. })
  92. return err
  93. }
  94. // createZipArchiveFromFiles creates a zip archive from a list of files
  95. func createZipArchiveFromFiles(zipPath string, files []string) error {
  96. // Create a new zip file
  97. zipFile, err := os.Create(zipPath)
  98. if err != nil {
  99. return cosy.WrapErrorWithParams(ErrCreateZipFile, err.Error())
  100. }
  101. defer zipFile.Close()
  102. // Create a new zip writer
  103. zipWriter := zip.NewWriter(zipFile)
  104. defer zipWriter.Close()
  105. // Add each file to the zip
  106. for _, file := range files {
  107. // Get file info
  108. info, err := os.Stat(file)
  109. if err != nil {
  110. return cosy.WrapErrorWithParams(ErrOpenSourceFile, err.Error())
  111. }
  112. // Create zip header
  113. header, err := zip.FileInfoHeader(info)
  114. if err != nil {
  115. return cosy.WrapErrorWithParams(ErrCreateZipHeader, err.Error())
  116. }
  117. // Set base name as header name
  118. header.Name = filepath.Base(file)
  119. // Set compression method
  120. header.Method = zip.Deflate
  121. // Create zip entry writer
  122. writer, err := zipWriter.CreateHeader(header)
  123. if err != nil {
  124. return cosy.WrapErrorWithParams(ErrCreateZipEntry, err.Error())
  125. }
  126. // Open source file
  127. source, err := os.Open(file)
  128. if err != nil {
  129. return cosy.WrapErrorWithParams(ErrOpenSourceFile, err.Error())
  130. }
  131. defer source.Close()
  132. // Copy to zip
  133. _, err = io.Copy(writer, source)
  134. if err != nil {
  135. return cosy.WrapErrorWithParams(ErrCopyContent, file)
  136. }
  137. }
  138. return nil
  139. }
  140. // calculateFileHash calculates the SHA-256 hash of a file
  141. func calculateFileHash(filePath string) (string, error) {
  142. // Open file
  143. file, err := os.Open(filePath)
  144. if err != nil {
  145. return "", cosy.WrapErrorWithParams(ErrReadFile, filePath)
  146. }
  147. defer file.Close()
  148. // Create hash
  149. hash := sha256.New()
  150. if _, err := io.Copy(hash, file); err != nil {
  151. return "", cosy.WrapErrorWithParams(ErrCalculateHash, err.Error())
  152. }
  153. // Return hex hash
  154. return hex.EncodeToString(hash.Sum(nil)), nil
  155. }
  156. // createZipArchiveToBuffer creates a zip archive of files in the specified directory
  157. // and writes the zip content to the provided buffer
  158. func createZipArchiveToBuffer(buffer *bytes.Buffer, sourceDir string) error {
  159. // Create a zip writer that writes to the buffer
  160. zipWriter := zip.NewWriter(buffer)
  161. defer zipWriter.Close()
  162. // Walk through all files in the source directory
  163. err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
  164. if err != nil {
  165. return err
  166. }
  167. // Skip the source directory itself
  168. if path == sourceDir {
  169. return nil
  170. }
  171. // Get the relative path to the source directory
  172. relPath, err := filepath.Rel(sourceDir, path)
  173. if err != nil {
  174. return err
  175. }
  176. // Check if it's a symlink
  177. if info.Mode()&os.ModeSymlink != 0 {
  178. // Get target of symlink
  179. linkTarget, err := os.Readlink(path)
  180. if err != nil {
  181. return cosy.WrapErrorWithParams(ErrReadSymlink, err.Error())
  182. }
  183. // Create symlink entry in zip
  184. header := &zip.FileHeader{
  185. Name: relPath,
  186. Method: zip.Deflate,
  187. }
  188. header.SetMode(info.Mode())
  189. writer, err := zipWriter.CreateHeader(header)
  190. if err != nil {
  191. return cosy.WrapErrorWithParams(ErrCreateZipEntry, err.Error())
  192. }
  193. // Write link target as content
  194. _, err = writer.Write([]byte(linkTarget))
  195. if err != nil {
  196. return cosy.WrapErrorWithParams(ErrCopyContent, relPath)
  197. }
  198. return nil
  199. }
  200. // Create a zip header from the file info
  201. header, err := zip.FileInfoHeader(info)
  202. if err != nil {
  203. return cosy.WrapErrorWithParams(ErrCreateZipHeader, err.Error())
  204. }
  205. // Set the name to be relative to the source directory
  206. header.Name = relPath
  207. // Set the compression method
  208. if !info.IsDir() {
  209. header.Method = zip.Deflate
  210. }
  211. // Create the entry in the zip file
  212. writer, err := zipWriter.CreateHeader(header)
  213. if err != nil {
  214. return cosy.WrapErrorWithParams(ErrCreateZipEntry, err.Error())
  215. }
  216. // If it's a directory, we're done
  217. if info.IsDir() {
  218. return nil
  219. }
  220. // Open the source file
  221. file, err := os.Open(path)
  222. if err != nil {
  223. return cosy.WrapErrorWithParams(ErrOpenSourceFile, err.Error())
  224. }
  225. defer file.Close()
  226. // Copy the file contents to the zip entry
  227. _, err = io.Copy(writer, file)
  228. if err != nil {
  229. return cosy.WrapErrorWithParams(ErrCopyContent, relPath)
  230. }
  231. return nil
  232. })
  233. if err != nil {
  234. return err
  235. }
  236. // Close the zip writer to ensure all data is written
  237. return zipWriter.Close()
  238. }