inleft
2022-02-27 2782f3e576b56a9ca078055026394646d57440f8
commit | author | age
9bcb19 1 /*
I 2 Copyright [2020] [https://www.xiaonuo.vip]
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8   http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15
16 Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
17
18 1.请不要删除和修改根目录下的LICENSE文件。
19 2.请不要删除和修改Snowy源码头部的版权声明。
20 3.请保留源码和相关描述文件的项目出处,作者声明等。
21 4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy
22 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy
23 6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
24  */
25 package vip.xiaonuo.sys.modular.file.service.impl;
26
27 import cn.hutool.core.bean.BeanUtil;
28 import cn.hutool.core.convert.Convert;
29 import cn.hutool.core.date.DateUtil;
30 import cn.hutool.core.io.FileUtil;
31 import cn.hutool.core.io.IoUtil;
32 import cn.hutool.core.lang.Dict;
33 import cn.hutool.core.util.NumberUtil;
34 import cn.hutool.core.util.ObjectUtil;
35 import cn.hutool.core.util.StrUtil;
36 import cn.hutool.json.JSONUtil;
37 import cn.hutool.log.Log;
38 import com.alibaba.fastjson.JSONObject;
39 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
40 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
41 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
42 import org.springframework.http.MediaType;
43 import org.springframework.stereotype.Service;
44 import org.springframework.transaction.annotation.Transactional;
45 import org.springframework.web.multipart.MultipartFile;
46 import vip.xiaonuo.core.consts.CommonConstant;
47 import vip.xiaonuo.core.consts.SymbolConstant;
48 import vip.xiaonuo.core.context.login.LoginContextHolder;
49 import vip.xiaonuo.core.context.requestno.RequestNoContext;
50 import vip.xiaonuo.core.exception.LibreOfficeException;
51 import vip.xiaonuo.core.exception.ServiceException;
52 import vip.xiaonuo.core.factory.PageFactory;
53 import vip.xiaonuo.core.file.FileOperator;
54 import vip.xiaonuo.core.file.modular.local.LocalFileOperator;
55 import vip.xiaonuo.core.pojo.login.SysLoginUser;
56 import vip.xiaonuo.core.pojo.page.PageResult;
57 import vip.xiaonuo.core.util.HttpServletUtil;
58 import vip.xiaonuo.core.util.LibreOfficeUtil;
59 import vip.xiaonuo.sys.modular.file.entity.SysFileInfo;
60 import vip.xiaonuo.sys.modular.file.enums.SysFileLocationEnum;
61 import vip.xiaonuo.sys.modular.file.enums.SysFileInfoExceptionEnum;
62 import vip.xiaonuo.sys.modular.file.mapper.SysFileInfoMapper;
63 import vip.xiaonuo.sys.modular.file.param.SysFileInfoParam;
64 import vip.xiaonuo.sys.modular.file.result.SysFileInfoResult;
65 import vip.xiaonuo.sys.modular.file.result.SysOnlineFileInfoResult;
66 import vip.xiaonuo.sys.modular.file.service.SysFileInfoService;
67 import vip.xiaonuo.sys.modular.file.util.DownloadUtil;
68 import vip.xiaonuo.sys.modular.file.util.OnlineDocumentUtil;
69
70 import javax.annotation.Resource;
71 import javax.servlet.ServletOutputStream;
72 import javax.servlet.http.HttpServletRequest;
73 import javax.servlet.http.HttpServletResponse;
74 import java.io.File;
75 import java.io.FileWriter;
76 import java.io.IOException;
77 import java.io.InputStream;
78 import java.math.BigDecimal;
79 import java.nio.charset.Charset;
80 import java.util.Arrays;
81 import java.util.List;
82 import java.util.Scanner;
83
84 import static vip.xiaonuo.sys.config.FileConfig.DEFAULT_BUCKET;
85
86 /**
87  * 文件信息表 服务实现类
88  *
89  * @author yubaoshan
90  * @date 2020/6/7 22:15
91  */
92 @Service
93 public class SysFileInfoServiceImpl extends ServiceImpl<SysFileInfoMapper, SysFileInfo> implements SysFileInfoService {
94
95     private static final Log log = Log.get();
96
97     @Resource
98     private FileOperator fileOperator;
99
100     @Override
101     public SysOnlineFileInfoResult onlineAddOrUpdate(SysFileInfoParam sysFileInfoParam) {
102         if(fileOperator instanceof LocalFileOperator) {
103             //文件后缀
104             String fileSuffix = sysFileInfoParam.getFileSuffix();
105             //文件名称
106             String fileOriginName = sysFileInfoParam.getFileOriginName();
107             //文件id
108             Long id = sysFileInfoParam.getId();
109             //参数错误
110             if(ObjectUtil.isAllEmpty(fileSuffix, fileOriginName, id)) {
111                 throw new ServiceException(SysFileInfoExceptionEnum.ONLINE_EDIT_PARAM_ERROR);
112             }
113             //获取登录用户
114             SysLoginUser sysLoginUser = LoginContextHolder.me().getSysLoginUser();
115             SysFileInfo sysFileInfo;
116             SysOnlineFileInfoResult sysOnlineFileInfoResult;
117             //文件id不为空则表示编辑
118             if(ObjectUtil.isNotEmpty(id)) {
119                 sysFileInfo = this.getById(id);
120                 sysOnlineFileInfoResult= new SysOnlineFileInfoResult(Convert.toStr(sysFileInfo.getId()), sysFileInfo.getFileOriginName(), Convert.toStr(sysLoginUser.getId()), sysLoginUser.getName());
121             } else {
122                 //否则表示新增
123                 Boolean sample = sysFileInfoParam.getSample();
124                 sysFileInfo = createDemo(fileSuffix, fileOriginName, sample, Convert.toStr(sysLoginUser.getId()), sysLoginUser.getName());
125                 sysOnlineFileInfoResult= new SysOnlineFileInfoResult(Convert.toStr(sysFileInfo.getId()), fileOriginName + SymbolConstant.PERIOD + fileSuffix, Convert.toStr(sysLoginUser.getId()), sysLoginUser.getName());
126
127             }
128             //设置history
129             sysOnlineFileInfoResult.history = OnlineDocumentUtil.getHistory(sysOnlineFileInfoResult);
130             if(ObjectUtil.isAllNotEmpty(sysFileInfoParam.getMode(), sysFileInfoParam.getType())) {
131                 sysOnlineFileInfoResult.changeType(sysFileInfoParam.getMode(), sysFileInfoParam.getType());
132             }
133             return sysOnlineFileInfoResult;
134         } else {
135             //暂时只支持本地文件
136             throw new ServiceException(SysFileInfoExceptionEnum.ONLINE_EDIT_SUPPORT_LOCAL_ONLY);
137         }
138     }
139
140     @Override
141     public PageResult<SysFileInfo> page(SysFileInfoParam sysFileInfoParam) {
142
143         // 构造条件
144         LambdaQueryWrapper<SysFileInfo> queryWrapper = new LambdaQueryWrapper<>();
145
146         // 拼接查询条件-文件存储位置(1:阿里云,2:腾讯云,3:minio,4:本地)
147         if (ObjectUtil.isNotNull(sysFileInfoParam)) {
148             if (ObjectUtil.isNotEmpty(sysFileInfoParam.getFileLocation())) {
149                 queryWrapper.like(SysFileInfo::getFileLocation, sysFileInfoParam.getFileLocation());
150             }
151
152             // 拼接查询条件-文件仓库
153             if (ObjectUtil.isNotEmpty(sysFileInfoParam.getFileBucket())) {
154                 queryWrapper.like(SysFileInfo::getFileBucket, sysFileInfoParam.getFileBucket());
155             }
156
157             // 拼接查询条件-文件名称(上传时候的文件名)
158             if (ObjectUtil.isNotEmpty(sysFileInfoParam.getFileOriginName())) {
159                 queryWrapper.like(SysFileInfo::getFileOriginName, sysFileInfoParam.getFileOriginName());
160             }
161
162             // 根据后缀查询
163             if(ObjectUtil.isNotEmpty(sysFileInfoParam.getFileSuffix())) {
164                 if(sysFileInfoParam.getFileSuffix().contains(SymbolConstant.COMMA)) {
165                     queryWrapper.in(SysFileInfo::getFileSuffix, Arrays.asList(sysFileInfoParam.getFileSuffix().split(SymbolConstant.COMMA)));
166                 } else {
167                     queryWrapper.eq(SysFileInfo::getFileSuffix, sysFileInfoParam.getFileSuffix());
168                 }
169             }
170         }
171
172         // 查询分页结果
173         return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper));
174     }
175
176     @Override
177     public List<SysFileInfo> list(SysFileInfoParam sysFileInfoParam) {
178
179         // 构造条件
180         LambdaQueryWrapper<SysFileInfo> queryWrapper = new LambdaQueryWrapper<>();
181
182         return this.list(queryWrapper);
183     }
184
185     @Override
186     public void add(SysFileInfoParam sysFileInfoParam) {
187
188         // 将dto转为实体
189         SysFileInfo sysFileInfo = new SysFileInfo();
190         BeanUtil.copyProperties(sysFileInfoParam, sysFileInfo);
191
192         this.save(sysFileInfo);
193     }
194
195     @Transactional(rollbackFor = Exception.class)
196     @Override
197     public void delete(SysFileInfoParam sysFileInfoParam) {
198
199         // 查询文件的信息
200         SysFileInfo sysFileInfo = this.getById(sysFileInfoParam.getId());
201
202         // 删除文件记录
203         this.removeById(sysFileInfoParam.getId());
204
205         // 删除具体文件
206         this.fileOperator.deleteFile(sysFileInfo.getFileBucket(), sysFileInfo.getFileObjectName());
207     }
208
209     @Override
210     public void edit(SysFileInfoParam sysFileInfoParam) {
211
212         // 根据id查询实体
213         SysFileInfo sysFileInfo = this.querySysFileInfo(sysFileInfoParam);
214
215         // 请求参数转化为实体
216         BeanUtil.copyProperties(sysFileInfoParam, sysFileInfo);
217
218         this.updateById(sysFileInfo);
219     }
220
221     @Override
222     public SysFileInfo detail(SysFileInfoParam sysFileInfoParam) {
223         return this.querySysFileInfo(sysFileInfoParam);
224     }
225
226     @Override
227     public Long uploadFile(MultipartFile file) {
228
229         // 生成文件的唯一id
230         Long fileId = IdWorker.getId();
231
232         // 获取文件原始名称
233         String originalFilename = file.getOriginalFilename();
234
235         // 获取文件后缀
236         String fileSuffix = null;
237
238         if (ObjectUtil.isNotEmpty(originalFilename)) {
239             fileSuffix = StrUtil.subAfter(originalFilename, SymbolConstant.PERIOD, true);
240         }
241         // 生成文件的最终名称
242         String finalName = fileId + SymbolConstant.PERIOD + fileSuffix;
243
244         // 存储文件
245         byte[] bytes;
246         try {
247             bytes = file.getBytes();
248             fileOperator.storageFile(DEFAULT_BUCKET, finalName, bytes);
249         } catch (IOException e) {
250             throw new ServiceException(SysFileInfoExceptionEnum.ERROR_FILE);
251         }
252
253         // 计算文件大小kb
254         long fileSizeKb = Convert.toLong(NumberUtil.div(new BigDecimal(file.getSize()), BigDecimal.valueOf(1024))
255                 .setScale(0, BigDecimal.ROUND_HALF_UP));
256
257         //计算文件大小信息
258         String fileSizeInfo = FileUtil.readableFileSize(file.getSize());
259
260         // 存储文件信息
261         SysFileInfo sysFileInfo = new SysFileInfo();
262         sysFileInfo.setId(fileId);
263         sysFileInfo.setFileLocation(SysFileLocationEnum.LOCAL.getCode());
264         sysFileInfo.setFileBucket(DEFAULT_BUCKET);
265         sysFileInfo.setFileObjectName(finalName);
266         sysFileInfo.setFileOriginName(originalFilename);
267         sysFileInfo.setFileSuffix(fileSuffix);
268         sysFileInfo.setFileSizeKb(fileSizeKb);
269         sysFileInfo.setFileSizeInfo(fileSizeInfo);
270         this.save(sysFileInfo);
271
272         // 返回文件id
273         return fileId;
274     }
275
276     @Override
277     public SysFileInfoResult getFileInfoResult(Long fileId) {
278         byte[] fileBytes;
279         // 获取文件名
280         SysFileInfo sysFileInfo = this.getById(fileId);
281         if (sysFileInfo == null) {
282             throw new ServiceException(SysFileInfoExceptionEnum.NOT_EXISTED_FILE);
283         }
284         try {
285             // 返回文件字节码
286             fileBytes = fileOperator.getFileBytes(DEFAULT_BUCKET, sysFileInfo.getFileObjectName());
287         } catch (Exception e) {
288             log.error(">>> 获取文件流异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage());
289             throw new ServiceException(SysFileInfoExceptionEnum.FILE_STREAM_ERROR);
290         }
291
292         SysFileInfoResult sysFileInfoResult = new SysFileInfoResult();
293         BeanUtil.copyProperties(sysFileInfo, sysFileInfoResult);
294         sysFileInfoResult.setFileBytes(fileBytes);
295
296         return sysFileInfoResult;
297     }
298
299     @Override
300     public void assertFile(Long field) {
301         SysFileInfo sysFileInfo = this.getById(field);
302         if (ObjectUtil.isEmpty(sysFileInfo)) {
303             throw new ServiceException(SysFileInfoExceptionEnum.NOT_EXISTED);
304         }
305     }
306
307     @Override
308     public void preview(SysFileInfoParam sysFileInfoParam, HttpServletResponse response) {
309
310         byte[] fileBytes;
311         //根据文件id获取文件信息结果集
312         SysFileInfoResult sysFileInfoResult = this.getFileInfoResult(sysFileInfoParam.getId());
313         //获取文件后缀
314         String fileSuffix = sysFileInfoResult.getFileSuffix().toLowerCase();
315         //获取文件字节码
316         fileBytes = sysFileInfoResult.getFileBytes();
317         //如果是图片类型,则直接输出
318         if (LibreOfficeUtil.isPic(fileSuffix)) {
319             try {
320                 //设置contentType
321                 response.setContentType(MediaType.IMAGE_JPEG_VALUE);
322                 //获取outputStream
323                 ServletOutputStream outputStream = response.getOutputStream();
324                 //输出
325                 IoUtil.write(outputStream, true, fileBytes);
326             } catch (IOException e) {
327                 throw new ServiceException(SysFileInfoExceptionEnum.PREVIEW_ERROR_NOT_SUPPORT);
328             }
329
330         } else if (LibreOfficeUtil.isDoc(fileSuffix)) {
331             try {
332                 //如果是文档类型,则使用libreoffice转换为pdf或html
333                 InputStream inputStream = IoUtil.toStream(fileBytes);
334
335                 //获取目标contentType(word和ppt和text转成pdf,excel转成html)
336                 String targetContentType = LibreOfficeUtil.getTargetContentTypeBySuffix(fileSuffix);
337
338                 //设置contentType
339                 response.setContentType(targetContentType);
340
341                 //获取outputStream
342                 ServletOutputStream outputStream = response.getOutputStream();
343
344                 //转换
345                 LibreOfficeUtil.convertToPdf(inputStream, outputStream, fileSuffix);
346
347                 //输出
348                 IoUtil.write(outputStream, true, fileBytes);
349             } catch (IOException e) {
350                 log.error(">>> 预览文件异常", e.getMessage());
351                 throw new ServiceException(SysFileInfoExceptionEnum.PREVIEW_ERROR_NOT_SUPPORT);
352
353             } catch (LibreOfficeException e) {
354                 log.error(">>> 初始化LibreOffice失败", e.getMessage());
355                 throw new ServiceException(SysFileInfoExceptionEnum.PREVIEW_ERROR_LIBREOFFICE);
356             }
357
358         } else {
359             //否则不支持预览(暂时)
360             throw new ServiceException(SysFileInfoExceptionEnum.PREVIEW_ERROR_NOT_SUPPORT);
361         }
362     }
363
364     @Override
365     public void download(SysFileInfoParam sysFileInfoParam, HttpServletResponse response) {
366         // 获取文件信息结果集
367         SysFileInfoResult sysFileInfoResult = this.getFileInfoResult(sysFileInfoParam.getId());
368         String fileName = sysFileInfoResult.getFileOriginName();
369         byte[] fileBytes = sysFileInfoResult.getFileBytes();
370         DownloadUtil.download(fileName, fileBytes, response);
371     }
372
373     /**
374      * 获取文件信息表
375      *
376      * @author yubaoshan
377      * @date 2020/6/7 22:15
378      */
379     private SysFileInfo querySysFileInfo(SysFileInfoParam sysFileInfoParam) {
380         SysFileInfo sysFileInfo = this.getById(sysFileInfoParam.getId());
381         if (ObjectUtil.isEmpty(sysFileInfo)) {
382             throw new ServiceException(SysFileInfoExceptionEnum.NOT_EXISTED);
383         }
384         return sysFileInfo;
385     }
386
387     @Override
388     public void track() {
389         HttpServletRequest request = HttpServletUtil.getRequest();
390         HttpServletResponse response = HttpServletUtil.getResponse();
391         String fileObjectName = request.getParameter("fileObjectName");
392         String id = request.getParameter("id");
393         String storagePath = OnlineDocumentUtil.getStoragePath(id + SymbolConstant.PERIOD + FileUtil.getSuffix(fileObjectName));
394         String body = "";
395         Scanner scanner;
396
397         try {
398             scanner = new Scanner(request.getInputStream());
399             scanner.useDelimiter("\\A");
400             body = scanner.hasNext() ? scanner.next() : "";
401             scanner.close();
402         } catch (IOException e) {
403             e.printStackTrace();
404         }
405
406         JSONObject jsonObj;
407
408         if (body.isEmpty()) {
409             log.error(">>> 读取文件request输入流为空");
410             return;
411         }
412
413         try {
414             jsonObj = JSONObject.parseObject(body);
415         } catch (Exception ex) {
416             log.error(">>> 文件信息body格式化错误");
417             return;
418         }
419
420         int status = (int) jsonObj.get("status");
421         String downloadUri =  (String) jsonObj.get("url");
422         String changesUri = (String) jsonObj.get("changesurl");
423         String key = (String) jsonObj.get("key");
424
425         int saved = 0;
426         if (status == 2 || status == 3) {
427             //MustSave, Corrupted
428             try {
429                 String histDir = OnlineDocumentUtil.getHistoryDir(OnlineDocumentUtil.getStoragePath(id));
430                 String versionDir =  OnlineDocumentUtil.getVersionDir(histDir, OnlineDocumentUtil.getFileVersion(histDir) + 1);
431                 File ver = new File(versionDir);
432                 File toSave = new File(storagePath);
433                 if (!ver.exists()) ver.mkdirs();
434                 toSave.renameTo(new File(versionDir + File.separator + "prev" + SymbolConstant.PERIOD + FileUtil.getSuffix(fileObjectName)));
435                 DownloadUtil.downloadToFile(downloadUri, toSave);
436                 DownloadUtil.downloadToFile(changesUri, new File(versionDir + File.separator + "diff.zip"));
437
438                 String history = (String) jsonObj.get("changeshistory");
439                 if (history == null && jsonObj.containsKey("history")) {
440                     history = ((JSONObject) jsonObj.get("history")).toJSONString();
441                 }
442                 if (history != null && !history.isEmpty()) {
443                     FileWriter fw = new FileWriter(new File(versionDir + File.separator + "changes.json"));
444                     fw.write(history);
445                     fw.close();
446                 }
447
448                 FileWriter fw = new FileWriter(new File(versionDir + File.separator + "key.txt"));
449                 fw.write(key);
450                 fw.close();
451             } catch (Exception ex) {
452                 saved = 1;
453             }
454         }
455         try {
456             response.getWriter().write("{\"error\":" + saved + "}");
457         } catch (IOException e) {
458             e.printStackTrace();
459         }
460     }
461
462     /**
463      * 创建模板文件
464      *
465      * @param fileSuffix 文件后缀
466      * @param originalFilename 文件原始名称
467      * @param sample 是否创建相同文件内容的模板文件
468      * @param userId 用户id
469      * @param userName 用户名称
470      * @author xuyuxiang
471      * @date 2021/3/24 11:01
472      */
473     public SysFileInfo createDemo(String fileSuffix, String originalFilename, Boolean sample, String userId, String userName) {
474         // 文件名称拼接
475         originalFilename = originalFilename + SymbolConstant.PERIOD + fileSuffix;
476         // 模板名称
477         String demoName = (sample ? "sample." : "new.") + fileSuffix;
478         InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("assets/" + demoName);
479         // 生成文件的唯一id
480         Long fileId = IdWorker.getId();
481         // 生成文件的最终名称
482         String finalName = fileId + SymbolConstant.PERIOD + fileSuffix;
483         // 读取流
484         byte[] bytes = IoUtil.readBytes(stream);
485         // 将该模板文件存到存储桶
486         fileOperator.storageFile(DEFAULT_BUCKET, finalName, bytes);
487         // 创建元数据信息
488         createMeta(Convert.toStr(fileId), userId, userName);
489         // 计算文件大小kb
490         long fileSizeKb = Convert.toLong(NumberUtil.div(new BigDecimal(bytes.length), BigDecimal.valueOf(1024))
491                 .setScale(0, BigDecimal.ROUND_HALF_UP));
492         // 计算文件大小信息
493         String fileSizeInfo = FileUtil.readableFileSize(bytes.length);
494         // 存储文件信息
495         SysFileInfo sysFileInfo = new SysFileInfo();
496         sysFileInfo.setId(fileId);
497         sysFileInfo.setFileLocation(SysFileLocationEnum.LOCAL.getCode());
498         sysFileInfo.setFileBucket(DEFAULT_BUCKET);
499         sysFileInfo.setFileObjectName(finalName);
500         sysFileInfo.setFileOriginName(originalFilename);
501         sysFileInfo.setFileSuffix(fileSuffix);
502         sysFileInfo.setFileSizeKb(fileSizeKb);
503         sysFileInfo.setFileSizeInfo(fileSizeInfo);
504         // 将新创建的文件保存到数据库
505         this.save(sysFileInfo);
506         return sysFileInfo;
507     }
508
509     /**
510      * 创建元数据信息
511      *
512      * @param fileId 文件id
513      * @param userId 用户id
514      * @param userName 用户名称
515      * @author xuyuxiang
516      * @date 2021/3/24 11:19
517      */
518     public void createMeta(String fileId, String userId, String userName) {
519         // 仅限本地文件
520         Object localClient = fileOperator.getClient();
521         if(ObjectUtil.isNull(localClient)) {
522             throw new ServiceException(SysFileInfoExceptionEnum.CLIENT_INIT_ERROR);
523         }
524         Dict localClientDict = (Dict) localClient;
525         // 拼接获取文档历史路径
526         String histDir = localClientDict.getStr("currentSavePath") + File.separator + DEFAULT_BUCKET + File.separator + fileId + "-hist";
527         if(!FileUtil.exist(histDir)) {
528             // 历史路径不存在则创建
529             File dir = new File(histDir);
530             dir.mkdir();
531         }
532         Dict dict = new Dict();
533         dict.put("created", DateUtil.now());
534         dict.put("id", (userId == null || userId.isEmpty()) ? -1 : userId);
535         dict.put("name", (userName == null || userName.isEmpty()) ? CommonConstant.UNKNOWN : userName);
536         File metaFile = new File(histDir + File.separator + "createdInfo.json");
537         FileUtil.writeString(JSONUtil.toJsonStr(dict), metaFile, Charset.defaultCharset());
538     }
539
540 }