summaryrefslogtreecommitdiff
path: root/lib/dolce/utils/upload-with-progress.ts
blob: 8e36afe4b4af169a4d576a0faf3b0caf9ebc2022 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
 * XMLHttpRequest를 사용하여 파일 업로드 진행도 추적
 */
export interface UploadProgressCallback {
  onProgress: (fileIndex: number, progress: number) => void;
  onFileComplete: (fileIndex: number) => void;
  onFileError: (fileIndex: number, error: string) => void;
}

export interface UploadFilesWithProgressOptions {
  uploadId: string;
  userId: string;
  files: File[];
  callbacks: UploadProgressCallback;
}

export interface UploadResult {
  success: boolean;
  uploadedCount?: number;
  error?: string;
}

/**
 * 진행도 추적을 지원하는 파일 업로드 함수
 */
export async function uploadFilesWithProgress({
  uploadId,
  userId,
  files,
  callbacks,
}: UploadFilesWithProgressOptions): Promise<UploadResult> {
  return new Promise((resolve) => {
    const formData = new FormData();
    formData.append("uploadId", uploadId);
    formData.append("userId", userId);
    formData.append("fileCount", String(files.length));

    files.forEach((file, index) => {
      formData.append(`file_${index}`, file);
    });

    const xhr = new XMLHttpRequest();

    // 전체 업로드 진행도 (단순화: 전체 진행도를 각 파일에 분배)
    xhr.upload.addEventListener("progress", (event) => {
      if (event.lengthComputable) {
        const totalProgress = (event.loaded / event.total) * 100;
        
        // 현재 업로드 중인 파일 인덱스 추정
        const filesCompleted = Math.floor((totalProgress / 100) * files.length);
        const currentFileIndex = Math.min(filesCompleted, files.length - 1);
        
        // 각 파일별 진행도 계산
        files.forEach((_, index) => {
          if (index < filesCompleted) {
            callbacks.onProgress(index, 100);
            callbacks.onFileComplete(index);
          } else if (index === currentFileIndex) {
            const fileProgress = ((totalProgress / 100) * files.length - filesCompleted) * 100;
            callbacks.onProgress(index, Math.min(fileProgress, 99));
          } else {
            callbacks.onProgress(index, 0);
          }
        });
      }
    });

    xhr.addEventListener("load", () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        try {
          const response = JSON.parse(xhr.responseText);
          
          // 모든 파일 완료 처리
          files.forEach((_, index) => {
            callbacks.onProgress(index, 100);
            callbacks.onFileComplete(index);
          });

          resolve(response);
        } catch (error) {
          const errorMsg = "응답 파싱 실패";
          files.forEach((_, index) => {
            callbacks.onFileError(index, errorMsg);
          });
          resolve({
            success: false,
            error: errorMsg,
          });
        }
      } else {
        const errorMsg = `업로드 실패: ${xhr.status} ${xhr.statusText}`;
        files.forEach((_, index) => {
          callbacks.onFileError(index, errorMsg);
        });
        resolve({
          success: false,
          error: errorMsg,
        });
      }
    });

    xhr.addEventListener("error", () => {
      const errorMsg = "네트워크 오류";
      files.forEach((_, index) => {
        callbacks.onFileError(index, errorMsg);
      });
      resolve({
        success: false,
        error: errorMsg,
      });
    });

    xhr.addEventListener("abort", () => {
      const errorMsg = "업로드가 취소되었습니다";
      files.forEach((_, index) => {
        callbacks.onFileError(index, errorMsg);
      });
      resolve({
        success: false,
        error: errorMsg,
      });
    });

    xhr.open("POST", "/api/dolce/upload-files");
    xhr.send(formData);
  });
}