1package azblob
2
3import (
4	"context"
5	"io"
6	"net/url"
7
8	"encoding/base64"
9	"encoding/binary"
10
11	"github.com/Azure/azure-pipeline-go/pipeline"
12)
13
14const (
15	// BlockBlobMaxUploadBlobBytes indicates the maximum number of bytes that can be sent in a call to Upload.
16	BlockBlobMaxUploadBlobBytes = 256 * 1024 * 1024 // 256MB
17
18	// BlockBlobMaxStageBlockBytes indicates the maximum number of bytes that can be sent in a call to StageBlock.
19	BlockBlobMaxStageBlockBytes = 100 * 1024 * 1024 // 100MB
20
21	// BlockBlobMaxBlocks indicates the maximum number of blocks allowed in a block blob.
22	BlockBlobMaxBlocks = 50000
23)
24
25// BlockBlobURL defines a set of operations applicable to block blobs.
26type BlockBlobURL struct {
27	BlobURL
28	bbClient blockBlobClient
29}
30
31// NewBlockBlobURL creates a BlockBlobURL object using the specified URL and request policy pipeline.
32func NewBlockBlobURL(url url.URL, p pipeline.Pipeline) BlockBlobURL {
33	blobClient := newBlobClient(url, p)
34	bbClient := newBlockBlobClient(url, p)
35	return BlockBlobURL{BlobURL: BlobURL{blobClient: blobClient}, bbClient: bbClient}
36}
37
38// WithPipeline creates a new BlockBlobURL object identical to the source but with the specific request policy pipeline.
39func (bb BlockBlobURL) WithPipeline(p pipeline.Pipeline) BlockBlobURL {
40	return NewBlockBlobURL(bb.blobClient.URL(), p)
41}
42
43// WithSnapshot creates a new BlockBlobURL object identical to the source but with the specified snapshot timestamp.
44// Pass "" to remove the snapshot returning a URL to the base blob.
45func (bb BlockBlobURL) WithSnapshot(snapshot string) BlockBlobURL {
46	p := NewBlobURLParts(bb.URL())
47	p.Snapshot = snapshot
48	return NewBlockBlobURL(p.URL(), bb.blobClient.Pipeline())
49}
50
51// Upload creates a new block blob or overwrites an existing block blob.
52// Updating an existing block blob overwrites any existing metadata on the blob. Partial updates are not
53// supported with Upload; the content of the existing blob is overwritten with the new content. To
54// perform a partial update of a block blob, use StageBlock and CommitBlockList.
55// This method panics if the stream is not at position 0.
56// Note that the http client closes the body stream after the request is sent to the service.
57// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-blob.
58func (bb BlockBlobURL) Upload(ctx context.Context, body io.ReadSeeker, h BlobHTTPHeaders, metadata Metadata, ac BlobAccessConditions) (*BlockBlobUploadResponse, error) {
59	ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
60	count, err := validateSeekableStreamAt0AndGetCount(body)
61	if err != nil {
62		return nil, err
63	}
64	return bb.bbClient.Upload(ctx, body, count, nil,
65		&h.ContentType, &h.ContentEncoding, &h.ContentLanguage, h.ContentMD5,
66		&h.CacheControl, metadata, ac.LeaseAccessConditions.pointers(),
67		&h.ContentDisposition, ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag,
68		nil)
69}
70
71// StageBlock uploads the specified block to the block blob's "staging area" to be later committed by a call to CommitBlockList.
72// Note that the http client closes the body stream after the request is sent to the service.
73// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-block.
74func (bb BlockBlobURL) StageBlock(ctx context.Context, base64BlockID string, body io.ReadSeeker, ac LeaseAccessConditions, transactionalMD5 []byte) (*BlockBlobStageBlockResponse, error) {
75	count, err := validateSeekableStreamAt0AndGetCount(body)
76	if err != nil {
77		return nil, err
78	}
79	return bb.bbClient.StageBlock(ctx, base64BlockID, count, body, transactionalMD5, nil, ac.pointers(), nil)
80}
81
82// StageBlockFromURL copies the specified block from a source URL to the block blob's "staging area" to be later committed by a call to CommitBlockList.
83// If count is CountToEnd (0), then data is read from specified offset to the end.
84// For more information, see https://docs.microsoft.com/en-us/rest/api/storageservices/put-block-from-url.
85func (bb BlockBlobURL) StageBlockFromURL(ctx context.Context, base64BlockID string, sourceURL url.URL, offset int64, count int64, destinationAccessConditions LeaseAccessConditions, sourceAccessConditions ModifiedAccessConditions) (*BlockBlobStageBlockFromURLResponse, error) {
86	sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatchETag, sourceIfNoneMatchETag := sourceAccessConditions.pointers()
87	return bb.bbClient.StageBlockFromURL(ctx, base64BlockID, 0, sourceURL.String(), httpRange{offset: offset, count: count}.pointers(), nil, nil, destinationAccessConditions.pointers(), sourceIfModifiedSince, sourceIfUnmodifiedSince, sourceIfMatchETag, sourceIfNoneMatchETag, nil)
88}
89
90// CommitBlockList writes a blob by specifying the list of block IDs that make up the blob.
91// In order to be written as part of a blob, a block must have been successfully written
92// to the server in a prior PutBlock operation. You can call PutBlockList to update a blob
93// by uploading only those blocks that have changed, then committing the new and existing
94// blocks together. Any blocks not specified in the block list and permanently deleted.
95// For more information, see https://docs.microsoft.com/rest/api/storageservices/put-block-list.
96func (bb BlockBlobURL) CommitBlockList(ctx context.Context, base64BlockIDs []string, h BlobHTTPHeaders,
97	metadata Metadata, ac BlobAccessConditions) (*BlockBlobCommitBlockListResponse, error) {
98	ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag := ac.ModifiedAccessConditions.pointers()
99	return bb.bbClient.CommitBlockList(ctx, BlockLookupList{Latest: base64BlockIDs}, nil,
100		&h.CacheControl, &h.ContentType, &h.ContentEncoding, &h.ContentLanguage, h.ContentMD5,
101		metadata, ac.LeaseAccessConditions.pointers(), &h.ContentDisposition,
102		ifModifiedSince, ifUnmodifiedSince, ifMatchETag, ifNoneMatchETag, nil)
103}
104
105// GetBlockList returns the list of blocks that have been uploaded as part of a block blob using the specified block list filter.
106// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-block-list.
107func (bb BlockBlobURL) GetBlockList(ctx context.Context, listType BlockListType, ac LeaseAccessConditions) (*BlockList, error) {
108	return bb.bbClient.GetBlockList(ctx, listType, nil, nil, ac.pointers(), nil)
109}
110
111//////////////////////////////////////////////////////////////////////////////////////////////////////////////
112
113type BlockID [64]byte
114
115func (blockID BlockID) ToBase64() string {
116	return base64.StdEncoding.EncodeToString(blockID[:])
117}
118
119func (blockID *BlockID) FromBase64(s string) error {
120	*blockID = BlockID{} // Zero out the block ID
121	_, err := base64.StdEncoding.Decode(blockID[:], ([]byte)(s))
122	return err
123}
124
125//////////////////////////////////////////////////////////////////////////////////////////////////////////////
126
127type uuidBlockID BlockID
128
129func (ubi uuidBlockID) UUID() uuid {
130	u := uuid{}
131	copy(u[:], ubi[:len(u)])
132	return u
133}
134
135func (ubi uuidBlockID) Number() uint32 {
136	return binary.BigEndian.Uint32(ubi[len(uuid{}):])
137}
138
139func newUuidBlockID(u uuid) uuidBlockID {
140	ubi := uuidBlockID{}     // Create a new uuidBlockID
141	copy(ubi[:len(u)], u[:]) // Copy the specified UUID into it
142	// Block number defaults to 0
143	return ubi
144}
145
146func (ubi *uuidBlockID) SetUUID(u uuid) *uuidBlockID {
147	copy(ubi[:len(u)], u[:])
148	return ubi
149}
150
151func (ubi uuidBlockID) WithBlockNumber(blockNumber uint32) uuidBlockID {
152	binary.BigEndian.PutUint32(ubi[len(uuid{}):], blockNumber) // Put block number after UUID
153	return ubi                                                 // Return the passed-in copy
154}
155
156func (ubi uuidBlockID) ToBase64() string {
157	return BlockID(ubi).ToBase64()
158}
159
160func (ubi *uuidBlockID) FromBase64(s string) error {
161	return (*BlockID)(ubi).FromBase64(s)
162}
163