Search code examples
azurereact-nativeazure-blob-storageazure-storage

Azure blocklist contains 2 committed blocks but the result blob only shows the first block


I am using rest api of azure tu put block and also put block list, the operations finished successfully. I checked this with get method of this url https://accountname.blob.core.windows.net/container/myblobname?comp=blocklist the result is:

<?xml version="1.0" encoding="utf-8"?>
<BlockList>
    <CommittedBlocks>
        <Block>
            <Name>MjAyMy0wNy0xM1QxNTo0Mjo0MS4xM</Name>
            <Size>1048057</Size>
        </Block>
        <Block>
            <Name>MjAyMy0wNy0xM1QxNTo0MzoxMC44M</Name>
            <Size>460893</Size>
        </Block>
    </CommittedBlocks>
</BlockList>

These 2 blocks are related to a video file. my problem is that when I use the url (https://accountname.blob.core.windows.net/container/myblobname) to get whole video it just return first block so the second one is missing. where is the problem? request details enter image description here

Updated: here is my code

export const uploadLongFile = async (param: any) => {
  const {path, filename, mime} = param;
  const blockSize = 1 * 1024 * 1024; // 1MB
  const fileSize = param?.size;
  const blockCount = Math.ceil(fileSize / blockSize);
  const sasContainerUri =  Config.CONTAINER_URI;
  const customBlobName = Math.random().toString(16).slice(2);
  const fileExtension = (filename ?? path).split('.').pop();
  const newFileName = `${customBlobName}.${fileExtension}`;
  const container = 'images';
  const assetPath = `${sasContainerUri}/${container}/${newFileName}`;
  const sasToken = Config.SAS_TOKEN; // you may need to play with other html verbs in this string e.g., `sp`, `ss` e.t.c.
  const blockIds: any[] = [];
  const videoChunks: any[] = [];
  const timeInterval = param?.duration / 1000 / blockCount;
  const chunks = await TrimWithIntervalVideo(path, timeInterval);// Trim video into blockCount chunks
  let totalSize = 0;
  for (let i = 0; i < blockCount; i++) {
    let dataString = encodeURIComponent(encode(new Date().toISOString()));
    blockIds.push(dataString);
    let info = await ReactNativeBlobUtil.fs.stat(videoChunks[i]);
    let chunkPath =
      i < 10
        ? `${chunks?.url}00${i}.${chunks?.ext}`
        : i < 100
        ? `${chunks?.url}0${i}.${chunks?.ext}`
        : `${chunks?.url}${i}.${chunks?.ext}`;
    videoChunks.push(chunkPath);
    totalSize += info.size;
    await stageBlock(
      blockIds[i],
      videoChunks[i],
      `${assetPath}?${sasToken}`,
      info?.size,
    );
    if (i === blockCount - 1) {
      commitBlockList(blockIds, totalSize, `${assetPath}?${sasToken}`);
    }
  }
};
const stageBlock = async (
  blockId?: any,
  chunk?: any,
  url?: any,
  length?: any,
) => {
  const res = await ReactNativeBlobUtil.fetch(
    'PUT',
    `${url}&comp=block&blockid=${blockId}`,
    {
      'x-ms-date': new Date().toUTCString(),
      'content-length': `${length}`,
    },
// Change BASE64 encoded data to a file path with prefix `ReactNativeBlobUtil-file://`.
// Or simply wrap the file path with ReactNativeBlobUtil.wrap(
    ReactNativeBlobUtil.wrap(chunk),
  ).catch(error => console.log({error}));
  console.log({res});
};
const commitBlockList = async (blockIds: any, length: any, url: any) => {
  let blockList: any[] = blockIds?.map(
    (value: string) => `<Latest>${value}</Latest>`,
  );
  let ff = blockList
    .toString()
    .replace('[', '')
    .replace(']', '')
    .replace(',', '\n');
  console.log({url}, {blockList}, {ff});
  const res2 = await ReactNativeBlobUtil.fetch(
    'PUT',
    `${url}&comp=blocklist`,
    {
      'x-ms-date': new Date().toUTCString(),
      'content-length': `${length}`,
      'content-type': 'text/plain; charset=UTF-8',
    },
    `<?xml version="1.0" encoding="utf-8"?>
    <BlockList>
      ${ff}
    </BlockList>`,
  )
    .progress((re, t) => console.log({re}, {t}))
    .catch(error => console.log({error}));
  console.log({res2});
};

Solution

  • thanks to @GauravMantri . I change my method to extract chunks and it works fine. Code in the question set video type for each chunk (for example chunk1.mp4) it seems azure storage cannot merge these files. So I store each chunk without type and change code to this, now its working.

    export const uploadLargeFile = async (param: any) => {
      const {path, filename, mime} = param;
      const blockSize = 1 * 1024 * 1024; // 1MB
      const fileSize = param?.size;
      const blockCount = Math.ceil(fileSize / blockSize);
      const sasContainerUri = Config.sasContainerUri ;
      const customBlobName = Math.random().toString(16).slice(2);
      const fileExtension = (filename ?? path).split('.').pop();
      const newFileName = `${customBlobName}.${fileExtension}`;
      const container = 'images';
      const assetPath = `${sasContainerUri}/${container}/${newFileName}`;
      const sasToken = Config.SAS_TOKEN; // you may need to play with other html verbs in this string e.g., `sp`, `ss` e.t.c.
      const blockIds: any[] = [];
      let totalSize = 0;
      for (let i = 0; i < blockCount; i++) {
        const blockId = btoa('block-' + i.toString().padStart(6, '0'));
        blockIds.push(blockId);
        const start = i * blockSize;
        const end = Math.min(start + blockSize, fileSize);
        let dest = `${ReactNativeBlobUtil.fs.dirs.CacheDir}/RIM3/d3_${i}`;
        let file = await ReactNativeBlobUtil.fs.slice(path, dest, start, end);
        let info = await ReactNativeBlobUtil.fs.stat(file);
        const stageResult = await stageBlock(
          blockId,
          file,
          `${assetPath}?${sasToken}`,
          info?.size,
          i,
          blockCount,
          param?.changeProgress,
        );
        if (!stageResult) {
          showErrorMessage('Error upload file');
          break;
        }
        totalSize += info.size;
        console.log({i});
        if (i === blockCount - 1) {
          const isSuccess = await commitBlockList(
            blockIds,
            fileSize,
            `${assetPath}?${sasToken}`,
          );
          if (isSuccess) {
            return newFileName;
          } else {
            return undefined;
          }
        }
      }
    };