显示上传百分比的替代方法

Alternative way to show upload percentage

由于错误 https://github.com/codenameone/CodenameOne/issues/3043,我不知道如何在使用 MultipartRequest 时显示上传百分比。你有什么建议,比如显示百分比的替代方法吗?谢谢

目前还没有,因为没有对问题所在进行评估。我认为进度侦听器跟踪写入上传代码的输出流而不是实际连接时间,这在 Java 中通常很难跟踪。

例如在 Java SE 中,您将打开一个 URL,然后写入 POST 连接的输出流。然后当您尝试获取输入流响应时,写入实际上会发生。但在这一点上,我没有关于上传状态的指示,因为它是完全抽象的并且在幕后发生。

所以我不确定这在技术上是否可行。

我解决了这个问题,解决了它。在花了几天时间尝试仅客户端解决方案之后,最终我得到了一个涉及客户端代码(代号一)和服务器代码(Spring 引导)的解决方案。

基本上,在客户端我把要上传的文件分成100kb的小块,一个一个上传,所以我可以准确计算上传百分比。在服务器上,我放置了一个控制器来接收小文件,另一个控制器来合并它们。我知道我的代码特定于我的用例(将图像和视频发送到 Cloudinary),但是我复制了一些相关部分,这些部分可能会启发其他与代号一有类似问题的人。

截图("Caricamento"表示"Uploading","Annulla"表示"Cancel"):

客户代码

服务器class

    /**
     * SYNC - Upload a MultipartFile as partial file
     *
     * @param data
     * @param partNumber
     * @param uniqueId containing the totalBytes before the first "-"
     * @return true if success, false otherwise
     */
    public static boolean uploadPartialFile(byte[] data, int partNumber, String uniqueId) {

        String api = "/cloud/partialUpload";

        MultipartRequest request = new MultipartRequest();
        request.setUrl(Server.getServerURL() + api);
        request.addData("file", data, "application/octet-stream");
        request.addRequestHeader("authToken", DB.userDB.authToken.get());
        request.addRequestHeader("email", DB.userDB.email.get());
        request.addRequestHeader("partNumber", partNumber + "");
        request.addRequestHeader("uniqueId", uniqueId);
        NetworkManager.getInstance().addToQueueAndWait(request);
        try {
            String response = Util.readToString(new ByteArrayInputStream(request.getResponseData()), "UTF-8");
            if ("OK".equals(response)) {
                return true;
            }
        } catch (IOException ex) {
            Log.p("Server.uploadPartialFile ERROR -> Util.readToString failed");
            Log.e(ex);
            SendLog.sendLogAsync();
        }
        return false;
    }

    /**
     * ASYNC - Merges the previously upload partial files
     *
     * @param uniqueId containing the totalBytes before the first "-"
     * @param callback to do something with the publicId of the uploaded file
     */
    public static void mergeUpload(String uniqueId, OnComplete<Response<String>> callback) {
        String api = "/cloud/mergeUpload";
        Map<String, String> headers = Server.getUserHeaders();
        headers.put("uniqueId", uniqueId);
        Server.asyncGET(api, headers, callback);
    }

public static void uploadFile(String filePath, OnComplete<String> callback) {

        String api = "/cloud/upload"; 

        // to show the progress, we send a piece of the file at a time
        String url = Server.getServerURL() + api;
        Map<String, String> headers = new HashMap<>();
        headers.put("authToken", DB.userDB.authToken.get());
        headers.put("email", DB.userDB.email.get());
        DialogUtilities.genericUploadProgress(url, filePath, headers, callback);
        }
    }

DialogUtilities class

public static void genericUploadProgress(String url, String filePath, Map<String, String> headers, OnComplete<String> callback) {
        Command[] cmds = {Command.create("Cancel", null, ev -> {
            ((Dialog) Display.getInstance().getCurrent()).dispose();
            uploadThread.kill();
        })};
        Container bodyCmp = new Container(new BorderLayout());
        Label infoText = new Label("DialogUtilities-Upload-Starting");
        bodyCmp.add(BorderLayout.CENTER, infoText);

        // Dialog blocks the current thread (that is the EDT), so the following code needs to be run in another thread
        uploadThread.run(() -> {
            // waits some time to give the Dialog the time to be open
            // it's not necessary, but useful to use the SelectorUtilities below in the case that the uploaded file is very small
            Util.sleep(500);
            try {
                long size = FileSystemStorage.getInstance().getLength(filePath);
                String uniqueId = size + "-" + DB.userDB.email + "_" + System.currentTimeMillis();
                // splits the file in blocks of 100kb
                InputStream inputStream = FileSystemStorage.getInstance().openInputStream(filePath);
                byte[] buffer = new byte[100 * 1024];
                int readByte = inputStream.read(buffer);
                int totalReadByte = 0;
                int partNumber = 0;
                while (readByte != -1) {
                    boolean result = Server.uploadPartialFile(Arrays.copyOfRange(buffer, 0, readByte), partNumber, uniqueId);
                    if (!result) {
                        CN.callSerially(() -> {
                            DialogUtilities.genericServerError();
                        });
                        break;
                    }
                    partNumber++;
                    totalReadByte += readByte;
                    int percentage = (int) (totalReadByte * 100 / size);
                    CN.callSerially(() -> {
                        infoText.setText(percentage + "%");
                    });
                    readByte = inputStream.read(buffer);
                }
                CN.callSerially(() -> {
                    if (CN.getCurrentForm() instanceof Dialog) {
                        // upload finished, before merging the files on the server we disable the "Cancel" button
                        Button cancelBtn = SelectorUtilities.$(Button.class, CN.getCurrentForm()).iterator().next();
                        cancelBtn.setEnabled(false);
                        cancelBtn.setText("DialogUtilities-Wait");
                        cancelBtn.repaint();
                    }
                });

                Server.mergeUpload(uniqueId, new OnComplete<Response<String>>() {
                    @Override
                    public void completed(Response<String> response) {
                        String fileId = response.getResponseData();

                        CN.callSerially(() -> {
                            if (Display.getInstance().getCurrent() instanceof Dialog) {
                                ((Dialog) Display.getInstance().getCurrent()).dispose();
                            }
                        });

                        callback.completed(fileId);
                    }
                });

            } catch (IOException ex) {
                Log.p("DialogUtilities.genericUploadProgress ERROR", Log.ERROR);
                CN.callSerially(() -> {
                    DialogUtilities.genericDialogError("DialogUtilities-UploadError-Title", "DialogUtilities-UploadError-Text");
                });
                Log.e(ex);
                SendLog.sendLogAsync();
            }
        });

        showDialog("Server-Uploading", null, cmds[0], cmds, DialogUtilities.TYPE_UPLOAD, null, 0l, CommonTransitions.createDialogPulsate().copy(false), null, null, bodyCmp);

服务器代码

CloudinaryControllerclass

    /**
     * Upload a MultipartFile as partial file.
     *
     * @param authToken
     * @param email
     * @param partNumber
     * @param uniqueId containing the totalBytes before the first "-"
     * @param file
     * @return "OK" if success
     */
    @PostMapping("/partialUpload")
    public @ResponseBody
    String partialUpload(@RequestHeader(value = "authToken") String authToken, @RequestHeader(value = "email") String email, @RequestHeader(value = "partNumber") String partNumber, @RequestHeader(value = "uniqueId") String uniqueId, @RequestParam("file") MultipartFile file) throws IOException {
        return cloudinaryService.partialUpload(authToken, email, partNumber, uniqueId, file);
    }

    /**
     * Merges the files previuosly uploaded by "/partialUpload", upload that
     * file to Cloudinary and returns the id assigned by Cloudinary
     *
     * @param authToken
     * @param email
     * @param uniqueId containing the totalBytes before the first "-"
     * @return the id assigned by Cloudinary
     */
    @GetMapping("/mergeUpload")
    public @ResponseBody
    String mergeUpload(@RequestHeader(value = "authToken") String authToken, @RequestHeader(value = "email") String email, @RequestHeader(value = "uniqueId") String uniqueId) throws IOException {
        return cloudinaryService.mergeUpload(authToken, email, uniqueId);
    }

CloudinaryServiceclass

   /**
     * Upload a MultipartFile as partial file.
     *
     * @param authToken
     * @param email
     * @param partNumber
     * @param uniqueId containing the totalBytes before the first "-"
     * @param file
     * @return "OK" if success
     */
    public String partialUpload(String authToken, String email, String partNumber, String uniqueId, MultipartFile file) throws IOException {
        User user = userService.getUser(authToken, email);
        if (user != null) {
            String output = AppApplication.uploadTempDir + "/" + uniqueId + "-" + partNumber;
            Path destination = Paths.get(output);
            Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
            return "OK";
        } else {
            logger.error("Error: a not authenticated user tried to upload a file (email: " + email + ", authToken: " + authToken + ")");
            return null;
        }
    }

    /**
     * Merges the files previuosly uploaded by "/partialUpload", upload that
     * file to Cloudinary and returns the id assigned by Cloudinary
     *
     * @param authToken
     * @param email
     * @param uniqueId containing the totalBytes before the first "-"
     * @return the id assigned by Cloudinary
     */
    public String mergeUpload(String authToken, String email, String uniqueId) throws IOException {
        User user = userService.getUser(authToken, email);
        if (user != null) {
            long totalBytes = Long.valueOf(uniqueId.split("-", 2)[0]);
            List<File> files = new ArrayList<>();
            int partNumber = 0;
            File testFile = new File(AppApplication.uploadTempDir + "/" + uniqueId + "-" + partNumber);
            while (testFile.exists()) {
                files.add(testFile);
                partNumber++;
                testFile = new File(AppApplication.uploadTempDir + "/" + uniqueId + "-" + partNumber);
            }

            // the list of files is ready, we can now merge them
            File merged = new File(AppApplication.uploadTempDir + "/" + uniqueId);
            IOCopier.joinFiles(merged, files);

            // uploads the file to Cloudinary
            Map uploadResult = cloudinary.uploader().upload(merged, ObjectUtils.emptyMap());
            String publicId = uploadResult.get("public_id").toString();

            // removes the files
            for (File file : files) {
                file.delete();
            }
            merged.delete();

            return publicId;

        } else {
            logger.error("Error: a not authenticated user tried to upload a file (email: " + email + ", authToken: " + authToken + ")");
            return null;
        }
    }

IOCopier class

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.apache.commons.io.IOUtils;

/**
 * Useful to merge files. See: 
 */
public class IOCopier {

    public static void joinFiles(File destination, List<File> sources)
            throws IOException {
        OutputStream output = null;
        try {
            output = createAppendableStream(destination);
            for (File source : sources) {
                appendFile(output, source);
            }
        } finally {
            IOUtils.closeQuietly(output);
        }
    }

    private static BufferedOutputStream createAppendableStream(File destination)
            throws FileNotFoundException {
        return new BufferedOutputStream(new FileOutputStream(destination, true));
    }

    private static void appendFile(OutputStream output, File source)
            throws IOException {
        InputStream input = null;
        try {
            input = new BufferedInputStream(new FileInputStream(source));
            IOUtils.copy(input, output);
        } finally {
            IOUtils.closeQuietly(input);
        }
    }
}