[mirror] Scalable static site server for Git forges (like GitHub Pages)
1package git_pages
2
3import (
4 "archive/tar"
5 "context"
6 "fmt"
7 "io"
8)
9
10type Flusher interface {
11 Flush() error
12}
13
14// Inverse of `ExtractTar`.
15func CollectTar(
16 context context.Context, writer io.Writer, manifest *Manifest, metadata ManifestMetadata,
17) (
18 err error,
19) {
20 archive := tar.NewWriter(writer)
21
22 appendFile := func(header *tar.Header, data []byte, transform Transform) (err error) {
23 switch transform {
24 case Transform_Identity:
25 case Transform_Zstd:
26 data, err = zstdDecoder.DecodeAll(data, []byte{})
27 if err != nil {
28 return fmt.Errorf("zstd: %s: %w", header.Name, err)
29 }
30 default:
31 return fmt.Errorf("%s: unexpected transform", header.Name)
32 }
33 header.Size = int64(len(data))
34
35 err = archive.WriteHeader(header)
36 if err != nil {
37 return fmt.Errorf("tar: %w", err)
38 }
39 _, err = archive.Write(data)
40 if err != nil {
41 return fmt.Errorf("tar: %w", err)
42 }
43 return
44 }
45
46 for fileName, entry := range manifest.Contents {
47 var header tar.Header
48 if fileName == "" {
49 continue
50 }
51 header.Name = fileName
52
53 switch entry.GetType() {
54 case Type_Directory:
55 header.Typeflag = tar.TypeDir
56 header.Mode = 0755
57 header.ModTime = metadata.LastModified
58 err = appendFile(&header, nil, Transform_Identity)
59
60 case Type_InlineFile:
61 header.Typeflag = tar.TypeReg
62 header.Mode = 0644
63 header.ModTime = metadata.LastModified
64 err = appendFile(&header, entry.GetData(), entry.GetTransform())
65
66 case Type_ExternalFile:
67 var blobReader io.Reader
68 var blobMetadata BlobMetadata
69 var blobData []byte
70 blobReader, blobMetadata, err = backend.GetBlob(context, string(entry.Data))
71 if err != nil {
72 return
73 }
74 blobData, err = io.ReadAll(blobReader)
75 if err != nil {
76 return
77 }
78 header.Typeflag = tar.TypeReg
79 header.Mode = 0644
80 header.ModTime = blobMetadata.LastModified
81 err = appendFile(&header, blobData, entry.GetTransform())
82
83 case Type_Symlink:
84 header.Typeflag = tar.TypeSymlink
85 header.Mode = 0644
86 header.ModTime = metadata.LastModified
87 err = appendFile(&header, entry.GetData(), Transform_Identity)
88
89 default:
90 panic(fmt.Errorf("CollectTar encountered invalid entry: %v, %v",
91 entry.GetType(), entry.GetTransform()))
92 }
93 if err != nil {
94 return err
95 }
96 }
97
98 if redirects := CollectRedirectsFile(manifest); redirects != "" {
99 err = appendFile(&tar.Header{
100 Name: RedirectsFileName,
101 Typeflag: tar.TypeReg,
102 Mode: 0644,
103 ModTime: metadata.LastModified,
104 }, []byte(redirects), Transform_Identity)
105 if err != nil {
106 return err
107 }
108 }
109
110 if headers := CollectHeadersFile(manifest); headers != "" {
111 err = appendFile(&tar.Header{
112 Name: HeadersFileName,
113 Typeflag: tar.TypeReg,
114 Mode: 0644,
115 ModTime: metadata.LastModified,
116 }, []byte(headers), Transform_Identity)
117 if err != nil {
118 return err
119 }
120 }
121
122 err = archive.Flush()
123 if err != nil {
124 return fmt.Errorf("tar: %w", err)
125 }
126
127 flusher, ok := writer.(Flusher)
128 if ok {
129 err = flusher.Flush()
130 }
131 return err
132}