【问题标题】:Alternative way to show upload percentage显示上传百分比的替代方法
【发布时间】:2020-03-17 14:34:41
【问题描述】:

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

【问题讨论】:

    标签: codenameone


    【解决方案1】:

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

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

    截图(“Caricamento”表示“上传”,“Annulla”表示“取消”):

    客户端代码

    服务器类

        /**
         * 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 类

    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);
    

    服务器代码

    CloudinaryController 类

        /**
         * 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);
        }
    

    CloudinaryService 类

       /**
         * 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 类

    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: https://stackoverflow.com/a/14673198
     */
    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);
            }
        }
    }
    
    

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

      • 您的意思是问题无法解决? php或者jquery怎么样?
      • 这是一个基于客户端的 API。我不是说不能解决。我是说还没有人评估过这个问题,所以没有估计
      • @ShaiAlmog 我解决了这个问题,解决它。我用我的相关代码添加了一个答案。我需要帮助:即使我在DialogUtilities 类中添加了uploadThread.kill();,取消按钮(屏幕截图中的“Annulla”)实际上并没有停止上传。线程是EasyThread。如果您能理解为什么上传没有停止,您能否在我的答案中添加评论以给我一些提示?谢谢
      • 而不是使用睡眠和一个运行块。将该代码移动到一个方法并一次又一次地调用它。 kill() 不会在中间停止运行方法,但会阻止下一个块运行。
      • 好的,谢谢,我误解了kill() 的工作原理。我修复了我的代码,现在它在用户按下取消按钮时停止上传(更准确地说:它完成了当前 100kb 块的上传然后停止)。
      猜你喜欢
      • 2019-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-24
      • 1970-01-01
      • 2019-12-01
      • 2012-02-10
      相关资源
      最近更新 更多