mirror of
https://github.com/KOHGYLW/kiftd-source.git
synced 2025-01-09 04:27:56 +08:00
初步完成了WebDAV功能,同时还进行了一些细节改进。
This commit is contained in:
parent
86c53a075d
commit
642b42dc8c
@ -1,7 +1,7 @@
|
||||
## 欢迎访问kiftd源代码资源库!
|
||||
### Welcome to visit source of kiftd!
|
||||
|
||||
_当前版本:v1.0.35-RELEASE_
|
||||
_当前版本:v1.0.36-SNAPSHOT_
|
||||
|
||||
### 简介
|
||||
_kiftd——一款便捷、开源、功能完善的个人&团队&小型团队网盘服务器系统。_
|
||||
@ -72,5 +72,5 @@ _提示:源代码路径下包含了一些程序运行所需的非源代码资
|
||||
### 联系作者?
|
||||
如有任何需要(例如对该资源有疑问、意见或建议),请发件联系作者: kohgylw@163.com (青阳龙野),随时恭候您的来信!
|
||||
|
||||
青阳龙野@kohgylw by 2020年07月06日
|
||||
青阳龙野@kohgylw by 2022年06月02日
|
||||
|
||||
|
10
TODO.txt
10
TODO.txt
@ -179,3 +179,13 @@ test.auth.xxx=ucd
|
||||
--------------
|
||||
【已完成】修复了当用户执行批量文件上传操作时,如果中途切换浏览的文件夹可能会导致后续上传的目标文件夹不正确的问题。
|
||||
【已完成】升级了内置的MySQL数据库驱动版本,修复旧版本中存在的安全漏洞(CVE-2019-2692)。
|
||||
|
||||
计划中 v1.0.36
|
||||
--------------
|
||||
【计划中】新增WebDAV支持功能!该功能允许用户将kiftd挂载为一个“网络驱动器”,并像访问本地文件夹那样访问kiftd中的文件。
|
||||
开启方法:在conf/server.properties中添加“webdav=enable”设置,之后使用 http://{IP}:{端口}/dav/ 进行挂载。
|
||||
【计划中】新增删除留档功能。启用该功能后,用户删除的文件将以原件的形式保留在指定路径内,从而使得管理者能够妥善处理这些被删除的文件。
|
||||
【计划中】新增保留转码缓存的功能,启用该功能后,诸如视频转码缓存文件等临时文件将会在软件退出后继续保留,以便在服务器下次启动时重复使用而无需再次转码。
|
||||
【已完成】优化了下载限速算法,现在下载限速的精度变得更高了。
|
||||
【已完成】规范了下载功能中的ETag响应头的生成格式。
|
||||
【已完成】将内置的JAVE升级为3.3.1版本。
|
||||
|
4
pom.xml
4
pom.xml
@ -4,7 +4,7 @@
|
||||
|
||||
<groupId>kohgylw</groupId>
|
||||
<artifactId>kiftd</artifactId>
|
||||
<version>1.0.35-RELEASE</version>
|
||||
<version>1.0.36-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>kiftd</name>
|
||||
@ -150,7 +150,7 @@
|
||||
<dependency>
|
||||
<groupId>ws.schild</groupId>
|
||||
<artifactId>jave-all-deps</artifactId>
|
||||
<version>2.5.0</version>
|
||||
<version>3.3.1</version>
|
||||
</dependency>
|
||||
<!-- end JAVE -->
|
||||
|
||||
|
@ -9,9 +9,12 @@ import org.springframework.beans.factory.annotation.*;
|
||||
import org.springframework.web.servlet.config.annotation.*;
|
||||
|
||||
import kohgylw.kiftd.server.util.*;
|
||||
import kohgylw.kiftd.server.webdav.KiftdWebDAVServlet;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import javax.servlet.*;
|
||||
|
||||
import org.springframework.boot.web.servlet.*;
|
||||
import org.springframework.context.annotation.*;
|
||||
|
||||
@ -26,24 +29,25 @@ import org.springframework.context.annotation.*;
|
||||
* @version 1.0
|
||||
*/
|
||||
@Configurable
|
||||
@ComponentScan({ "kohgylw.kiftd.server.controller", "kohgylw.kiftd.server.service.impl", "kohgylw.kiftd.server.util" })
|
||||
@ComponentScan({ "kohgylw.kiftd.server.controller", "kohgylw.kiftd.server.service.impl", "kohgylw.kiftd.server.util",
|
||||
"kohgylw.kiftd.server.webdav.util" })
|
||||
@ServletComponentScan({ "kohgylw.kiftd.server.listener", "kohgylw.kiftd.server.filter" })
|
||||
@Import({ DataAccess.class })
|
||||
public class MVC extends ResourceHttpRequestHandler implements WebMvcConfigurer {
|
||||
|
||||
|
||||
// 启用DefaultServlet用以处理可直接请求的静态资源
|
||||
@Override
|
||||
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
|
||||
configurer.enable();
|
||||
}
|
||||
|
||||
|
||||
// 设置Web静态资源映射路径
|
||||
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
|
||||
// 将静态页面资源所在文件夹加入至资源路径中
|
||||
registry.addResourceHandler(new String[] { "/**" }).addResourceLocations(new String[] {
|
||||
"file:" + ConfigureReader.instance().getPath() + File.separator + "webContext" + File.separator });
|
||||
}
|
||||
|
||||
|
||||
// 生成上传管理器,用于接收/缓存上传文件
|
||||
@Bean
|
||||
public MultipartConfigElement multipartConfigElement() {
|
||||
@ -52,10 +56,16 @@ public class MVC extends ResourceHttpRequestHandler implements WebMvcConfigurer
|
||||
factory.setLocation(ConfigureReader.instance().getTemporaryfilePath());
|
||||
return factory.createMultipartConfig();
|
||||
}
|
||||
|
||||
|
||||
// 生成Gson实例,用于服务Json序列化和反序列化
|
||||
@Bean
|
||||
public Gson gson() {
|
||||
return new GsonBuilder().create();
|
||||
}
|
||||
|
||||
// 注册WebDAV处理Servlet
|
||||
@Bean
|
||||
public ServletRegistrationBean<Servlet> WebDAVServlet() {
|
||||
return new ServletRegistrationBean<Servlet>(new KiftdWebDAVServlet(), "/dav/*");
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,11 @@ public class MastLoginFilter implements Filter {
|
||||
final HttpServletResponse hsr = (HttpServletResponse) response;
|
||||
final String url = hsq.getServletPath();
|
||||
final HttpSession session = hsq.getSession();
|
||||
if (url.startsWith("/externalLinksController/") || url.startsWith("//externalLinksController/")
|
||||
if (url.startsWith("/externalLinksController") || url.startsWith("//externalLinksController")
|
||||
|| url.startsWith("/homeController/getNewVerCode.do")
|
||||
|| url.startsWith("//homeController/getNewVerCode.do")) {
|
||||
chain.doFilter(request, response);// 对于外部链接控制器和验证码的请求直接放行。
|
||||
|| url.startsWith("//homeController/getNewVerCode.do") || url.startsWith("/dav")
|
||||
|| url.startsWith("//dav")) {
|
||||
chain.doFilter(request, response);// 对于外部链接控制器、验证码和WebDAV的请求直接放行。
|
||||
return;
|
||||
}
|
||||
// 如果是无需登录的请求,那么直接放行(如果访问者已经登录,那么会被后面的过滤器重定向至主页,此处无需处理)
|
||||
|
@ -9,11 +9,11 @@ import org.apache.commons.codec.digest.DigestUtils;
|
||||
import kohgylw.kiftd.printer.Printer;
|
||||
import kohgylw.kiftd.server.util.ConfigureReader;
|
||||
import ws.schild.jave.Encoder;
|
||||
import ws.schild.jave.EncoderProgressListener;
|
||||
import ws.schild.jave.EncodingAttributes;
|
||||
import ws.schild.jave.FFMPEGLocator;
|
||||
import ws.schild.jave.MultimediaInfo;
|
||||
import ws.schild.jave.MultimediaObject;
|
||||
import ws.schild.jave.encode.EncodingAttributes;
|
||||
import ws.schild.jave.info.MultimediaInfo;
|
||||
import ws.schild.jave.process.ProcessLocator;
|
||||
import ws.schild.jave.progress.EncoderProgressListener;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -32,7 +32,7 @@ public class VideoTranscodeThread {
|
||||
private Encoder encoder;
|
||||
private String outputFileName;
|
||||
|
||||
public VideoTranscodeThread(File f, EncodingAttributes ea,FFMPEGLocator fl) throws Exception {
|
||||
public VideoTranscodeThread(File f, EncodingAttributes ea,ProcessLocator fl) throws Exception {
|
||||
// 首先计算MD5值
|
||||
md5 = DigestUtils.md5Hex(new FileInputStream(f));
|
||||
progress = "0.0";
|
||||
@ -43,15 +43,15 @@ public class VideoTranscodeThread {
|
||||
outputFileName="video_"+UUID.randomUUID().toString()+".mp4";
|
||||
encoder.encode(mo, new File(ConfigureReader.instance().getTemporaryfilePath(), outputFileName),
|
||||
ea, new EncoderProgressListener() {
|
||||
public void sourceInfo(MultimediaInfo arg0) {
|
||||
}
|
||||
|
||||
public void progress(int arg0) {
|
||||
progress = (arg0 / 10.00) + "";
|
||||
}
|
||||
|
||||
public void message(String arg0) {
|
||||
}
|
||||
|
||||
public void sourceInfo(MultimediaInfo info) {
|
||||
}
|
||||
});
|
||||
progress = "FIN";
|
||||
} catch (Exception e) {
|
||||
|
@ -201,7 +201,7 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
|
||||
if (f.getFileName().equals(originalFileName)) {
|
||||
try {
|
||||
// 首先先将该节点中必须覆盖的信息更新
|
||||
f.setFileSize(fbu.getFileSize(file));
|
||||
f.setFileSize(fbu.getFileSize(file.getSize()));
|
||||
f.setFileCreationDate(ServerTimeUtil.accurateToDay());
|
||||
if (account != null) {
|
||||
f.setFileCreator(account);
|
||||
@ -267,7 +267,7 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
|
||||
if (block == null) {
|
||||
return UPLOADERROR;
|
||||
}
|
||||
final String fsize = this.fbu.getFileSize(file);
|
||||
final String fsize = this.fbu.getFileSize(file.getSize());
|
||||
Node newNode = fbu.insertNewNode(fileName, account, block.getName(), fsize, folderId);
|
||||
if (newNode != null) {
|
||||
// 存入成功,则写入日志并返回成功提示
|
||||
@ -388,7 +388,8 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
|
||||
return "cannotRenameFile";
|
||||
}
|
||||
}
|
||||
this.lu.writeRenameFileEvent(request, file, newFileName);
|
||||
this.lu.writeRenameFileEvent(account, idg.getIpAddr(request), file.getFileParentFolder(), file.getFileName(),
|
||||
newFileName);
|
||||
return "renameFileSuccess";
|
||||
}
|
||||
|
||||
@ -807,7 +808,7 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// 不是,直接删除冲突文件夹的所有子文件夹
|
||||
// 不是,直接删除冲突文件夹及其所有子文件夹
|
||||
fu.deleteAllChildFolder(f.getFolderId());
|
||||
// 再将原文件夹移入目标文件夹内
|
||||
folder.setFolderParent(locationpath);
|
||||
@ -1087,8 +1088,8 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
|
||||
// 开始文件夹命名冲突检查,若无重名则允许上传。否则检查该文件夹是否具备覆盖条件(具备该文件夹的访问权限且具备删除权限),如无则可选择保留两者或取消
|
||||
final List<Folder> folders = flm.queryByParentId(folderId);
|
||||
try {
|
||||
Folder testFolder = folders.stream().parallel()
|
||||
.filter((n) -> n.getFolderName().equals(folderName)).findAny().get();
|
||||
Folder testFolder = folders.stream().parallel().filter((n) -> n.getFolderName().equals(folderName))
|
||||
.findAny().get();
|
||||
if (ConfigureReader.instance().accessFolder(testFolder, account) && ConfigureReader.instance()
|
||||
.authorized(account, AccountAuth.DELETE_FILE_OR_FOLDER, fu.getAllFoldersId(folderId))) {
|
||||
cifr.setResult("repeatFolder_coverOrBoth");
|
||||
@ -1204,7 +1205,7 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
|
||||
if (block == null) {
|
||||
return UPLOADERROR;
|
||||
}
|
||||
final String fsize = this.fbu.getFileSize(file);
|
||||
final String fsize = this.fbu.getFileSize(file.getSize());
|
||||
Node newNode = fbu.insertNewNode(fileName, account, block.getName(), fsize, folderId);
|
||||
if (newNode != null) {
|
||||
// 成功,则记录日志并返回成功提示
|
||||
@ -1234,8 +1235,7 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path
|
||||
* java.lang.String 原路径字符串
|
||||
* @param path java.lang.String 原路径字符串
|
||||
* @return java.lang.String[] 解析出的目录层级
|
||||
*/
|
||||
private String[] getParentPath(String path) {
|
||||
@ -1260,8 +1260,7 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param java.lang.String
|
||||
* 需要解析的相对路径
|
||||
* @param java.lang.String 需要解析的相对路径
|
||||
* @return java.lang.String 文件名
|
||||
*/
|
||||
private String getFileNameFormPath(String path) {
|
||||
|
@ -30,6 +30,8 @@ public class FolderServiceImpl implements FolderService {
|
||||
private LogUtil lu;
|
||||
@Resource
|
||||
private Gson gson;
|
||||
@Resource
|
||||
private IpAddrGetter idg;
|
||||
|
||||
public String newFolder(final HttpServletRequest request) {
|
||||
final String parentId = request.getParameter("parentId");
|
||||
@ -91,7 +93,7 @@ public class FolderServiceImpl implements FolderService {
|
||||
final int r = this.fm.insertNewFolder(f);
|
||||
if (r > 0) {
|
||||
if (fu.isValidFolder(f)) {
|
||||
this.lu.writeCreateFolderEvent(request, f);
|
||||
this.lu.writeCreateFolderEvent(account, idg.getIpAddr(request), f);
|
||||
return "createFolderSuccess";
|
||||
} else {
|
||||
return "cannotCreateFolder";
|
||||
@ -193,7 +195,8 @@ public class FolderServiceImpl implements FolderService {
|
||||
return "errorParameter";
|
||||
}
|
||||
}
|
||||
this.lu.writeRenameFolderEvent(request, folder, newName, folderConstraint);
|
||||
this.lu.writeRenameFolderEvent(account, idg.getIpAddr(request), folder.getFolderId(),
|
||||
folder.getFolderName(), newName, folder.getFolderConstraint() + "", folderConstraint);
|
||||
return "renameFolderSuccess";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -313,7 +316,7 @@ public class FolderServiceImpl implements FolderService {
|
||||
final int r = this.fm.insertNewFolder(f);
|
||||
if (r > 0) {
|
||||
if (fu.isValidFolder(f)) {
|
||||
this.lu.writeCreateFolderEvent(request, f);
|
||||
this.lu.writeCreateFolderEvent(account, idg.getIpAddr(request), f);
|
||||
cnfbnr.setResult("success");
|
||||
cnfbnr.setNewName(f.getFolderName());
|
||||
return gson.toJson(cnfbnr);
|
||||
|
@ -50,7 +50,7 @@ public class PlayVideoServiceImpl implements PlayVideoService {
|
||||
final String suffix = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
|
||||
switch (suffix) {
|
||||
case "mp4":
|
||||
if (kfl.getFFMPEGExecutablePath() != null) {
|
||||
if (kfl.getExecutablePath() != null) {
|
||||
// 因此对于mp4后缀的视频,进一步检查其编码是否为h264,如果是,则允许直接播放
|
||||
File target = fbu.getFileFromBlocks(f);
|
||||
if (target == null || !target.isFile()) {
|
||||
@ -79,7 +79,7 @@ public class PlayVideoServiceImpl implements PlayVideoService {
|
||||
case "avi":
|
||||
case "wmv":
|
||||
case "flv":
|
||||
if (kfl.getFFMPEGExecutablePath() != null) {
|
||||
if (kfl.getExecutablePath() != null) {
|
||||
vi.setNeedEncode("Y");
|
||||
} else {
|
||||
vi.setNeedEncode("N");
|
||||
|
@ -100,7 +100,7 @@ public class ResourceServiceImpl implements ResourceService {
|
||||
case ".wmv":
|
||||
case ".mkv":
|
||||
case ".flv":
|
||||
if (kfl.getFFMPEGExecutablePath() != null) {
|
||||
if (kfl.getExecutablePath() != null) {
|
||||
contentType = "video/mp4";
|
||||
synchronized (VideoTranscodeUtil.videoTranscodeThreads) {
|
||||
VideoTranscodeThread vtt = VideoTranscodeUtil.videoTranscodeThreads.get(fid);
|
||||
@ -369,7 +369,7 @@ public class ResourceServiceImpl implements ResourceService {
|
||||
|
||||
@Override
|
||||
public String getVideoTranscodeStatus(HttpServletRequest request) {
|
||||
if (kfl.getFFMPEGExecutablePath() != null) {
|
||||
if (kfl.getExecutablePath() != null) {
|
||||
String fId = request.getParameter("fileId");
|
||||
if (fId != null) {
|
||||
try {
|
||||
|
@ -85,6 +85,7 @@ public class ConfigureReader {
|
||||
public static final int INVALID_IP_XFF_SETTING = 13;
|
||||
public static final int INVALID_FFMPEG_SETTING = 14;
|
||||
public static final int INVALID_MUST_LOGIN_SETTING = 15;
|
||||
public static final int INVALID_WEBDAV_SETTING = 16;
|
||||
public static final int LEGAL_PROPERTIES = 0;
|
||||
private static Thread accountPropertiesUpdateDaemonThread;
|
||||
private String timeZone;
|
||||
@ -99,6 +100,7 @@ public class ConfigureReader {
|
||||
private boolean ipXFFAnalysis = true;// 是否启用XFF解析
|
||||
private boolean enableFFMPEG = true;// 是否启用视频播放的在线解码功能
|
||||
private boolean enableDownloadByZip = true;// 是否启用“打包下载”功能
|
||||
private boolean enableWebDAV = false;// 是否启用WebDAV功能
|
||||
|
||||
private static final int MAX_EXTENDSTORES_NUM = 255;// 扩展存储区最大数目
|
||||
private static final String[] SYS_ACCOUNTS = { "SYS_IN", "Anonymous", "匿名用户" };// 一些系统的特殊账户
|
||||
@ -184,13 +186,10 @@ public class ConfigureReader {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param account
|
||||
* java.lang.String 账户的ID,如果是匿名访问可传入null
|
||||
* @param auth
|
||||
* kohgylw.kiftd.server.enumeration.AccountAuth
|
||||
* 要判断的操作类型,使用枚举类中定义的各种操作作为参数传入
|
||||
* @param folders
|
||||
* 该操作所发生的文件夹序列,其中应包含该操作对应的文件夹和其所有上级文件夹的ID
|
||||
* @param account java.lang.String 账户的ID,如果是匿名访问可传入null
|
||||
* @param auth kohgylw.kiftd.server.enumeration.AccountAuth
|
||||
* 要判断的操作类型,使用枚举类中定义的各种操作作为参数传入
|
||||
* @param folders 该操作所发生的文件夹序列,其中应包含该操作对应的文件夹和其所有上级文件夹的ID
|
||||
* @return boolean 是否具备该操作的权限,若具备返回true,否则返回false
|
||||
*/
|
||||
public boolean authorized(final String account, final AccountAuth auth, List<String> folders) {
|
||||
@ -851,6 +850,23 @@ public class ConfigureReader {
|
||||
} else {
|
||||
enableDownloadByZip = true;
|
||||
}
|
||||
// 是否启用WebDAV功能
|
||||
String webdavConf = serverp.getProperty("webdav");
|
||||
if (webdavConf != null) {
|
||||
switch (webdavConf) {
|
||||
case "disable":
|
||||
enableWebDAV = false;
|
||||
break;
|
||||
case "enable":
|
||||
enableWebDAV = true;
|
||||
break;
|
||||
default:
|
||||
Printer.instance.print("错误:WebDAV功能的配置不正确(只能设置为“disable”或“enable”),请重新检查。");
|
||||
return INVALID_WEBDAV_SETTING;
|
||||
}
|
||||
} else {
|
||||
enableWebDAV = false;
|
||||
}
|
||||
Printer.instance.print("检查完毕。");
|
||||
return LEGAL_PROPERTIES;
|
||||
}
|
||||
@ -976,10 +992,8 @@ public class ConfigureReader {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param f
|
||||
* Folder 要访问的文件夹对象
|
||||
* @param account
|
||||
* String 要访问的账户
|
||||
* @param f Folder 要访问的文件夹对象
|
||||
* @param account String 要访问的账户
|
||||
* @return boolean true允许访问,false不允许访问
|
||||
*/
|
||||
public boolean accessFolder(Folder f, String account) {
|
||||
@ -1108,8 +1122,7 @@ public class ConfigureReader {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param account
|
||||
* java.lang.String 需要检查的账户名
|
||||
* @param account java.lang.String 需要检查的账户名
|
||||
* @return long 以byte为单位的最大阈值,若返回0则设置错误,若小于0则不限制。
|
||||
*/
|
||||
public long getUploadFileSize(String account) {
|
||||
@ -1142,8 +1155,7 @@ public class ConfigureReader {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param in
|
||||
* java.lang.String 要转换的字符串内容,格式应为“{数值}{存储单位(可选)}”,例如“1024KB”或“10mb”。
|
||||
* @param in java.lang.String 要转换的字符串内容,格式应为“{数值}{存储单位(可选)}”,例如“1024KB”或“10mb”。
|
||||
* @return long 以Byte为单位计算的体积值,若为0则代表设置错误,若为负数则代表无限制
|
||||
*/
|
||||
private long getMaxSizeByString(String in) {
|
||||
@ -1193,9 +1205,8 @@ public class ConfigureReader {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param account
|
||||
* java.lang.String 需要获取限制的账户名
|
||||
* @return long 最大下载速度限制,以KB/s为单位
|
||||
* @param account java.lang.String 需要获取限制的账户名
|
||||
* @return long 最大下载速度限制,以B/s为单位
|
||||
*/
|
||||
public long getDownloadMaxRate(String account) {
|
||||
String defaultMaxRateP = accountp.getProperty("defaultMaxRate");
|
||||
@ -1211,7 +1222,7 @@ public class ConfigureReader {
|
||||
*
|
||||
* <h2>下载限速设置值转化方法</h2>
|
||||
* <p>
|
||||
* 该方法用于将配置文件中的下载速度限制设置值转化为long类型的数值,例如当输入字符串“1”时,输出1,输入“2MB”时,输出2048。
|
||||
* 该方法用于将配置文件中的下载速度限制设置值转化为long类型的数值,例如当输入字符串“1”时,输出1024,输入“2MB”时,输出2097152。
|
||||
* </p>
|
||||
* <p>
|
||||
* 输入字符串格式规则:{数值}{速率单位(可选)}。其中,速率单位可使用下列字符串之一指代(不区分大小写):
|
||||
@ -1225,9 +1236,8 @@ public class ConfigureReader {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param in
|
||||
* java.lang.String 要转换的字符串内容,格式应为“{数值}{速率单位(可选)}”,例如“1024”或“10 mb”。
|
||||
* @return long 以KB/s为单位计算的下载速度,若为0则代表设置错误,若为负数则代表无限制
|
||||
* @param in java.lang.String 要转换的字符串内容,格式应为“{数值}{速率单位(可选)}”,例如“1024”或“10 mb”。
|
||||
* @return long 以B/s为单位计算的下载速度,若为0则代表设置错误,若为负数则代表无限制
|
||||
*/
|
||||
private long getMaxRateByString(String in) {
|
||||
long r = 0L;
|
||||
@ -1248,17 +1258,17 @@ public class ConfigureReader {
|
||||
}
|
||||
switch (unit) {
|
||||
case "m":
|
||||
r = Integer.parseInt(value) * 1024L;
|
||||
break;
|
||||
case "g":
|
||||
r = Integer.parseInt(value) * 1048576L;
|
||||
break;
|
||||
case "g":
|
||||
r = Integer.parseInt(value) * 1073741824L;
|
||||
break;
|
||||
default:
|
||||
r = Integer.parseInt(in.trim());
|
||||
r = Integer.parseInt(value) * 1024L;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
r = Integer.parseInt(in.trim());
|
||||
r = Integer.parseInt(in.trim()) * 1024L;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
@ -1354,10 +1364,8 @@ public class ConfigureReader {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param account
|
||||
* java.lang.String 账户ID,该账户必须已经存在,否则会导致修改失败
|
||||
* @param newPassword
|
||||
* java.lang.String 新密码,必须不为null,否则会导致修改失败
|
||||
* @param account java.lang.String 账户ID,该账户必须已经存在,否则会导致修改失败
|
||||
* @param newPassword java.lang.String 新密码,必须不为null,否则会导致修改失败
|
||||
* @return boolean 操作是否成功,成功则返回true
|
||||
*/
|
||||
public boolean changePassword(String account, String newPassword) throws Exception {
|
||||
@ -1386,8 +1394,7 @@ public class ConfigureReader {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param ipAddr
|
||||
* java.lang.String 要判断的IP地址
|
||||
* @param ipAddr java.lang.String 要判断的IP地址
|
||||
* @return boolean 该地址是否被禁用,被禁用则返回true
|
||||
*/
|
||||
public boolean filterAccessIP(String ipAddr) {
|
||||
@ -1451,13 +1458,10 @@ public class ConfigureReader {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param newAccount
|
||||
* java.lang.String 要写入的新账户名
|
||||
* @param newPassword
|
||||
* java.lang.String 新账户的密码
|
||||
* @param newAccount java.lang.String 要写入的新账户名
|
||||
* @param newPassword java.lang.String 新账户的密码
|
||||
* @return boolean 是否写入成功。仅当账户名不存在且写入成功时返回true,否则返回false
|
||||
* @throws Exception
|
||||
* 写入时发生的异常
|
||||
* @throws Exception 写入时发生的异常
|
||||
*/
|
||||
public boolean createNewAccount(String newAccount, String newPassword) throws Exception {
|
||||
if (newAccount != null && newPassword != null) {
|
||||
@ -1538,4 +1542,18 @@ public class ConfigureReader {
|
||||
public boolean isEnableDownloadByZip() {
|
||||
return enableDownloadByZip;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>判断用户是否启用了WebDAV功能</h2>
|
||||
* <p>
|
||||
* 判断用户是否启用了WebDAV功能。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @return boolean 启用则返回true,否则返回false
|
||||
*/
|
||||
public boolean isEnableWebDAV() {
|
||||
return enableWebDAV;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import java.io.*;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
import kohgylw.kiftd.server.model.*;
|
||||
import kohgylw.kiftd.server.pojo.ExtendStores;
|
||||
@ -79,13 +80,143 @@ public class FileBlockUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param f
|
||||
* MultipartFile 上传文件对象
|
||||
* @param f MultipartFile 上传文件对象
|
||||
* @return java.io.File 生成的文件块,如果保存失败则返回null
|
||||
*/
|
||||
public File saveToFileBlocks(final MultipartFile f) {
|
||||
// 如果存在扩展存储区,则优先在已有文件块数目最少的扩展存储区中存放文件(避免占用主文件系统)
|
||||
List<ExtendStores> ess = ConfigureReader.instance().getExtendStores();// 得到全部扩展存储区
|
||||
// 得到全部扩展存储区
|
||||
List<ExtendStores> ess = getExtendStoresBySort();
|
||||
if (ess.size() > 0) {
|
||||
// 从文件块最少的开始遍历这些扩展存储区
|
||||
for (ExtendStores es : ess) {
|
||||
if (es.getPath().getFreeSpace() > f.getSize()) {
|
||||
// 如果该存储区的空余容量大于待上传文件的体积
|
||||
File file = null;
|
||||
try {
|
||||
// 则尝试在该存储区中生成一个空文件块
|
||||
file = createNewBlock(es.getIndex() + "_", es.getPath());
|
||||
if (file != null) {
|
||||
// 生成成功,尝试存入数据
|
||||
f.transferTo(file);
|
||||
return file;
|
||||
} else {
|
||||
continue;// 如果本处无法生成新文件块,那么在其他路径下继续尝试
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 出现IO异常,则删除残留文件并继续尝试其他扩展存储区
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
continue;
|
||||
} catch (Exception e) {
|
||||
// 出现其他异常则记录日志
|
||||
lu.writeException(e);
|
||||
Printer.instance.print(e.getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果不存在扩展存储区或者最大的扩展存储区的剩余容量依旧小于指定大小,则尝试在主文件系统路径下生成新文件块
|
||||
File file = null;
|
||||
try {
|
||||
file = createNewBlock("file_", new File(ConfigureReader.instance().getFileBlockPath()));
|
||||
if (file != null) {
|
||||
// 生成成功,则尝试存入数据
|
||||
f.transferTo(file);
|
||||
return file;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 出现异常则记录日志,则删除残留数据并返回null
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
lu.writeException(e);
|
||||
Printer.instance.print("错误:文件块生成失败,无法存入新的文件数据。详细信息:" + e.getMessage());
|
||||
}
|
||||
// 因其他原因生成失败也返回null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>将新上传的文件存入文件系统</h2>
|
||||
* <p>
|
||||
* 将一个java.io.File类型的文件对象存入节点,并返回保存的路径名称。其中,路径名称使用“file_{UUID}.block”
|
||||
* (存放于主文件系统中)或“{存储区编号}_{UUID}.block”(存放在指定编号的扩展存储区中)的形式。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param f 要存入的文件对象
|
||||
* @return java.io.File 生成的文件块,如果保存失败则返回null
|
||||
*/
|
||||
public File saveToFileBlocks(final File f) {
|
||||
// 得到全部扩展存储区
|
||||
List<ExtendStores> ess = getExtendStoresBySort();
|
||||
if (ess.size() > 0) {
|
||||
// 从文件块最少的开始遍历这些扩展存储区
|
||||
for (ExtendStores es : ess) {
|
||||
if (es.getPath().getFreeSpace() > f.length()) {
|
||||
// 如果该存储区的空余容量大于待上传文件的体积
|
||||
File file = null;
|
||||
try {
|
||||
// 则尝试在该存储区中生成一个空文件块
|
||||
file = createNewBlock(es.getIndex() + "_", es.getPath());
|
||||
if (file != null) {
|
||||
// 生成成功,尝试存入数据
|
||||
Files.move(f.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
return file;
|
||||
} else {
|
||||
continue;// 如果本处无法生成新文件块,那么在其他路径下继续尝试
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 出现IO异常,则删除残留文件并继续尝试其他扩展存储区
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
continue;
|
||||
} catch (Exception e) {
|
||||
// 出现其他异常则记录日志
|
||||
lu.writeException(e);
|
||||
Printer.instance.print(e.getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果不存在扩展存储区或者最大的扩展存储区的剩余容量依旧小于指定大小,则尝试在主文件系统路径下生成新文件块
|
||||
File file = null;
|
||||
try {
|
||||
file = createNewBlock("file_", new File(ConfigureReader.instance().getFileBlockPath()));
|
||||
if (file != null) {
|
||||
// 生成成功,则尝试存入数据
|
||||
Files.move(f.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
return file;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 出现异常则记录日志,则删除残留数据并返回null
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
lu.writeException(e);
|
||||
Printer.instance.print("错误:文件块生成失败,无法存入新的文件数据。详细信息:" + e.getMessage());
|
||||
}
|
||||
// 因其他原因生成失败也返回null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>以剩余容量从小到大排序获取扩展存储区列表</h2>
|
||||
* <p>
|
||||
* 该方法用于获取所有扩展存储区的列表,并按照剩余容量从小到大排序。如果没有扩展存储区,则返回一个长度为0的列表。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @return java.util.List<ExtendStores&rt; 排序好的扩展存储区列表。
|
||||
*/
|
||||
private List<ExtendStores> getExtendStoresBySort() {
|
||||
List<ExtendStores> ess = ConfigureReader.instance().getExtendStores();
|
||||
if (ess.size() > 0) {
|
||||
// 将所有扩展存储区按照已存储文件块的数目从小到大进行排序
|
||||
Collections.sort(ess, new Comparator<ExtendStores>() {
|
||||
@ -106,41 +237,8 @@ public class FileBlockUtil {
|
||||
}
|
||||
}
|
||||
});
|
||||
// 排序完毕后,从文件块最少的开始遍历这些扩展存储区,并尝试将新文件存入一个容量足够的扩展存储区中
|
||||
for (ExtendStores es : ess) {
|
||||
// 如果该存储区的空余容量大于要存放的文件
|
||||
if (es.getPath().getFreeSpace() > f.getSize()) {
|
||||
try {
|
||||
File file = createNewBlock(es.getIndex() + "_", es.getPath());
|
||||
if (file != null) {
|
||||
f.transferTo(file);// 则执行存放,并将文件命名为“{存储区编号}_{UUID}.block”的形式
|
||||
return file;
|
||||
} else {
|
||||
continue;// 如果本处无法生成新的文件块,那么在其他路径下继续尝试
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 如果无法存入(由于体积过大或其他问题),那么继续尝试其他扩展存储区
|
||||
continue;
|
||||
} catch (Exception e) {
|
||||
lu.writeException(e);
|
||||
Printer.instance.print(e.getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果不存在扩展存储区或者最大的扩展存储区无法存放目标文件,则尝试将其存放至主文件系统路径下
|
||||
try {
|
||||
final File file = createNewBlock("file_", new File(ConfigureReader.instance().getFileBlockPath()));
|
||||
if (file != null) {
|
||||
f.transferTo(file);// 执行存放,并肩文件命名为“file_{UUID}.block”的形式
|
||||
return file;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
lu.writeException(e);
|
||||
Printer.instance.print("错误:文件块生成失败,无法存入新的文件数据。详细信息:" + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
return ess;
|
||||
}
|
||||
|
||||
// 生成创建一个在指定路径下名称(编号)绝对不重复的新文件块
|
||||
@ -174,12 +272,10 @@ public class FileBlockUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param f
|
||||
* org.springframework.web.multipart.MultipartFile 上传文件对象
|
||||
* @param size 文件的体积,以Byte为单位
|
||||
* @return java.lang.String 计算出来的体积,以MB为单位
|
||||
*/
|
||||
public String getFileSize(final MultipartFile f) {
|
||||
final long size = f.getSize();
|
||||
public String getFileSize(final long size) {
|
||||
final int mb = (int) (size / 1048576L);
|
||||
return "" + mb;
|
||||
}
|
||||
@ -193,8 +289,7 @@ public class FileBlockUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param f
|
||||
* kohgylw.kiftd.server.model.Node 要删除的文件节点对象
|
||||
* @param f kohgylw.kiftd.server.model.Node 要删除的文件节点对象
|
||||
* @return boolean 删除结果,true为成功
|
||||
*/
|
||||
public boolean deleteFromFileBlocks(Node f) {
|
||||
@ -224,8 +319,7 @@ public class FileBlockUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param f
|
||||
* kohgylw.kiftd.server.model.Node 要获得的文件节点对象
|
||||
* @param f kohgylw.kiftd.server.model.Node 要获得的文件节点对象
|
||||
* @return java.io.File 对应的文件块抽象路径,获取失败则返回null
|
||||
*/
|
||||
public File getFileFromBlocks(Node f) {
|
||||
@ -314,12 +408,9 @@ public class FileBlockUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param idList
|
||||
* java.util.List<String> 要压缩的文件节点目标ID列表
|
||||
* @param fidList
|
||||
* java.util.List<String> 要压缩的文件夹目标ID列表,迭代压缩
|
||||
* @param account
|
||||
* java.lang.String 用户ID,用于判断压缩文件夹是否有效
|
||||
* @param idList java.util.List<String> 要压缩的文件节点目标ID列表
|
||||
* @param fidList java.util.List<String> 要压缩的文件夹目标ID列表,迭代压缩
|
||||
* @param account java.lang.String 用户ID,用于判断压缩文件夹是否有效
|
||||
* @return java.lang.String
|
||||
* 压缩后产生的文件名称,命名规则为“tf_{UUID}.zip”,存放于文件系统中的temporaryfiles目录下
|
||||
*/
|
||||
@ -465,21 +556,20 @@ public class FileBlockUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param block
|
||||
* java.io.File 需要生成的文件块对象,应为文件,但也支持文件夹,或者是null
|
||||
* @param block java.io.File 需要生成的文件块对象,应为文件,但也支持文件夹,或者是null
|
||||
* @return java.lang.String 生成的ETag值。当传入的block是null或其不存在时,返回空字符串
|
||||
*/
|
||||
public String getETag(File block) {
|
||||
if (block != null && block.exists()) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("\"");
|
||||
sb.append("W\"");
|
||||
sb.append(block.length());
|
||||
sb.append("-");
|
||||
sb.append(block.lastModified());
|
||||
sb.append("_");
|
||||
sb.append(block.hashCode());
|
||||
sb.append("\"");
|
||||
return sb.toString().trim();
|
||||
return sb.toString();
|
||||
}
|
||||
return "\"0\"";
|
||||
return "W\"0-0\"";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -490,16 +580,11 @@ public class FileBlockUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param fileName
|
||||
* java.lang.String 文件名称
|
||||
* @param account
|
||||
* java.lang.String 创建者账户,若传入null则按匿名创建者处理
|
||||
* @param filePath
|
||||
* java.lang.String 文件节点对应的文件块索引
|
||||
* @param fileSize
|
||||
* java.lang.String 文件体积
|
||||
* @param fileParentFolder
|
||||
* java.lang.String 文件的父文件夹ID
|
||||
* @param fileName java.lang.String 文件名称
|
||||
* @param account java.lang.String 创建者账户,若传入null则按匿名创建者处理
|
||||
* @param filePath java.lang.String 文件节点对应的文件块索引
|
||||
* @param fileSize java.lang.String 文件体积
|
||||
* @param fileParentFolder java.lang.String 文件的父文件夹ID
|
||||
* @return kohgylw.kiftd.server.model.Node 操作成功则返回节点对象,否则返回null
|
||||
*/
|
||||
public Node insertNewNode(String fileName, String account, String filePath, String fileSize,
|
||||
@ -547,8 +632,7 @@ public class FileBlockUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param n
|
||||
* kohgylw.kiftd.server.model.Node 待检查的节点
|
||||
* @param n kohgylw.kiftd.server.model.Node 待检查的节点
|
||||
* @return boolean 通过检查则返回true,否则返回false并删除此节点
|
||||
*/
|
||||
public boolean isValidNode(Node n) {
|
||||
@ -575,8 +659,7 @@ public class FileBlockUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param n
|
||||
* kohgylw.kiftd.server.model.Node 要获取路径的节点
|
||||
* @param n kohgylw.kiftd.server.model.Node 要获取路径的节点
|
||||
* @return java.lang.String 指定节点的逻辑路径,包含其完整的上级文件夹路径和自身的文件名,各级之间以“/”分割。
|
||||
*/
|
||||
public String getNodePath(Node n) {
|
||||
|
@ -32,7 +32,7 @@ public class FileNodeUtil {
|
||||
private static String url;// 当前链接的节点数据库位置
|
||||
|
||||
/**
|
||||
* 但文件夹内允许存放的最大文件和文件夹数目
|
||||
* 单文件夹内允许存放的最大文件和文件夹数目
|
||||
*/
|
||||
public static final int MAXIMUM_NUM_OF_SINGLE_FOLDER = Integer.MAX_VALUE;
|
||||
|
||||
|
@ -12,19 +12,17 @@ import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import kohgylw.kiftd.printer.Printer;
|
||||
import ws.schild.jave.FFMPEGLocator;
|
||||
import ws.schild.jave.Version;
|
||||
import ws.schild.jave.process.ProcessLocator;
|
||||
import ws.schild.jave.process.ProcessWrapper;
|
||||
import ws.schild.jave.process.ffmpeg.FFMPEGProcess;
|
||||
|
||||
@Component
|
||||
public class KiftdFFMPEGLocator extends FFMPEGLocator {
|
||||
public class KiftdFFMPEGLocator implements ProcessLocator {
|
||||
|
||||
@Resource
|
||||
private LogUtil lu;
|
||||
|
||||
/**
|
||||
* 内置的ffmpeg引擎的版本号,应该与jave整合资源本身自带的ffmpeg引擎版本对应
|
||||
*/
|
||||
private static final String MY_EXE_VERSION = "2.5.0";
|
||||
|
||||
private boolean enableFFmpeg;
|
||||
|
||||
private String suffix;
|
||||
@ -64,10 +62,9 @@ public class KiftdFFMPEGLocator extends FFMPEGLocator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFFMPEGExecutablePath() {
|
||||
public String getExecutablePath() {
|
||||
// 每次获得路径时再次初始化ffmpeg执行路径
|
||||
return initFFMPEGExecutablePath();// 这里的目的在于避免运行中ffmpeg被删掉从而导致程序找不到它,
|
||||
// 同时更新enableFFmpeg属性。
|
||||
return initFFMPEGExecutablePath();// 这里的目的在于避免运行中ffmpeg被删掉从而导致程序找不到它,同时更新enableFFmpeg属性。
|
||||
}
|
||||
|
||||
// 初始化ffmpeg执行路径并返回,过程包括:
|
||||
@ -100,7 +97,7 @@ public class KiftdFFMPEGLocator extends FFMPEGLocator {
|
||||
} else {
|
||||
// 否则,使用内置的ffmpeg文件。
|
||||
// 临时文件中是否已经拷贝好了ffmpeg可执行文件了?
|
||||
ffmpegFile = new File(dirFolder, "ffmpeg-" + arch + "-" + MY_EXE_VERSION + suffix);
|
||||
ffmpegFile = new File(dirFolder, "ffmpeg-" + arch + "-" + Version.getVersion() + suffix);
|
||||
if (!ffmpegFile.exists()) {
|
||||
// 没有?那将自带的、对应操作系统的ffmpeg文件拷贝到临时目录中,如果没有对应自带的ffmpeg,那么会抛出异常
|
||||
// 如果抛出异常,那么直接结束构造
|
||||
@ -115,7 +112,6 @@ public class KiftdFFMPEGLocator extends FFMPEGLocator {
|
||||
}
|
||||
// 已经有了?那么它应该准备好了
|
||||
}
|
||||
|
||||
// 对于类Unix系统而言,还要确保临时目录授予可运行权限,以便jave运行时调用ffmpeg
|
||||
if (!isWindows) {
|
||||
if (!ffmpegFile.canExecute()) {
|
||||
@ -129,7 +125,6 @@ public class KiftdFFMPEGLocator extends FFMPEGLocator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 上述工作都做好了,就可以将ffmpeg的路径返回给jave调用了。
|
||||
// 如果到不了这里,说明初始化失败,该方法返回null,那么应该禁用jave的在线转码功能
|
||||
enableFFmpeg = true;
|
||||
@ -138,13 +133,28 @@ public class KiftdFFMPEGLocator extends FFMPEGLocator {
|
||||
|
||||
// 把文件从自带的jar包中拷贝出来,移入指定文件夹
|
||||
private void copyFile(String path, File dest) {
|
||||
String resourceName = "/ws/schild/jave/native/" + path;
|
||||
String resourceName = "nativebin/" + path;
|
||||
try {
|
||||
if (!copy(getClass().getResourceAsStream(resourceName), dest.getAbsolutePath())) {
|
||||
throw new NullPointerException();
|
||||
InputStream is = getClass().getResourceAsStream(resourceName);
|
||||
if (is == null) {
|
||||
resourceName = "ws/schild/jave/nativebin/" + path;
|
||||
is = ClassLoader.getSystemResourceAsStream(resourceName);
|
||||
}
|
||||
if (is == null) {
|
||||
resourceName = "ws/schild/jave/nativebin/" + path;
|
||||
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
|
||||
is = classloader.getResourceAsStream(resourceName);
|
||||
}
|
||||
if (is != null) {
|
||||
copy(is, dest.getAbsolutePath());
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ioex) {
|
||||
lu.writeException(ioex);
|
||||
}
|
||||
}
|
||||
} catch (NullPointerException ex) {
|
||||
throw ex;
|
||||
lu.writeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,4 +173,8 @@ public class KiftdFFMPEGLocator extends FFMPEGLocator {
|
||||
return enableFFmpeg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessWrapper createExecutor() {
|
||||
return new FFMPEGProcess(getExecutablePath());
|
||||
}
|
||||
}
|
||||
|
@ -66,8 +66,7 @@ public class LogUtil {
|
||||
* 创建日志文件并写入异常信息,当同日期的日志文件存在时,则在其后面追加该信息
|
||||
* </p>
|
||||
*
|
||||
* @param e
|
||||
* Exception 需要记录的异常对象
|
||||
* @param e Exception 需要记录的异常对象
|
||||
*/
|
||||
public void writeException(Exception e) {
|
||||
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Runtime_Exception)) {
|
||||
@ -91,14 +90,12 @@ public class LogUtil {
|
||||
* 写入新建文件夹信息,包括操作者、路劲及新文件夹名称
|
||||
* </p>
|
||||
*/
|
||||
public void writeCreateFolderEvent(HttpServletRequest request, Folder f) {
|
||||
public void writeCreateFolderEvent(String account, String ip, Folder f) {
|
||||
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
|
||||
String account = (String) request.getSession().getAttribute("ACCOUNT");
|
||||
if (account == null || account.length() == 0) {
|
||||
account = "Anonymous";
|
||||
}
|
||||
String a = account;// 方便下方使用终态操作
|
||||
String ip = idg.getIpAddr(request);
|
||||
writerThread.execute(() -> {
|
||||
List<Folder> l = fu.getParentList(f.getFolderId());
|
||||
String pl = new String();
|
||||
@ -118,23 +115,22 @@ public class LogUtil {
|
||||
* 写入重命名文件夹信息
|
||||
* </p>
|
||||
*/
|
||||
public void writeRenameFolderEvent(HttpServletRequest request, Folder f, String newName, String newConstraint) {
|
||||
public void writeRenameFolderEvent(String account, String ip, String folderId, String oldName, String newName,
|
||||
String oldConstraint, String newConstraint) {
|
||||
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
|
||||
String account = (String) request.getSession().getAttribute("ACCOUNT");
|
||||
if (account == null || account.length() == 0) {
|
||||
account = "Anonymous";
|
||||
}
|
||||
String a = account;
|
||||
String ip = idg.getIpAddr(request);
|
||||
writerThread.execute(() -> {
|
||||
List<Folder> l = fu.getParentList(f.getFolderId());
|
||||
List<Folder> l = fu.getParentList(folderId);
|
||||
String pl = new String();
|
||||
for (Folder i : l) {
|
||||
pl = pl + i.getFolderName() + "/";
|
||||
}
|
||||
String content = ">IP [" + ip + "]\r\n>ACCOUNT [" + a + "]\r\n>OPERATE [Edit folder]\r\n>PATH [" + pl
|
||||
+ "]\r\n>NAME [" + f.getFolderName() + "]->[" + newName + "],CONSTRAINT ["
|
||||
+ f.getFolderConstraint() + "]->[" + newConstraint + "]";
|
||||
+ "]\r\n>NAME [" + oldName + "]->[" + newName + "],CONSTRAINT [" + oldConstraint + "]->["
|
||||
+ newConstraint + "]";
|
||||
writeToLog("Event", content);
|
||||
});
|
||||
}
|
||||
@ -281,8 +277,7 @@ public class LogUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param f
|
||||
* kohgylw.kiftd.server.model.Node 下载目标
|
||||
* @param f kohgylw.kiftd.server.model.Node 下载目标
|
||||
*/
|
||||
public void writeDownloadFileByKeyEvent(HttpServletRequest request, Node f) {
|
||||
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
|
||||
@ -339,23 +334,21 @@ public class LogUtil {
|
||||
* 写入重命名文件信息
|
||||
* </p>
|
||||
*/
|
||||
public void writeRenameFileEvent(HttpServletRequest request, Node f, String newName) {
|
||||
public void writeRenameFileEvent(String account, String ip, String parentFolderId, String oldName, String newName) {
|
||||
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
|
||||
String account = (String) request.getSession().getAttribute("ACCOUNT");
|
||||
if (account == null || account.length() == 0) {
|
||||
account = "Anonymous";
|
||||
}
|
||||
String a = account;
|
||||
String ip = idg.getIpAddr(request);
|
||||
writerThread.execute(() -> {
|
||||
Folder folder = fm.queryById(f.getFileParentFolder());
|
||||
Folder folder = fm.queryById(parentFolderId);
|
||||
List<Folder> l = fu.getParentList(folder.getFolderId());
|
||||
String pl = new String();
|
||||
for (Folder i : l) {
|
||||
pl = pl + i.getFolderName() + "/";
|
||||
}
|
||||
String content = ">IP [" + ip + "]\r\n>ACCOUNT [" + a + "]\r\n>OPERATE [Rename file]\r\n>PATH [" + pl
|
||||
+ folder.getFolderName() + "]\r\n>NAME [" + f.getFileName() + "]->[" + newName + "]";
|
||||
+ folder.getFolderName() + "]\r\n>NAME [" + oldName + "]->[" + newName + "]";
|
||||
writeToLog("Event", content);
|
||||
});
|
||||
}
|
||||
@ -369,16 +362,11 @@ public class LogUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param account
|
||||
* java.lang.String 操作者账户名
|
||||
* @param ip
|
||||
* java.lang.String 操作者IP地址
|
||||
* @param finalPath
|
||||
* java.lang.String 被操作后的节点完整路径
|
||||
* @param originPath
|
||||
* java.lang.String 未操作前的节点完整路径
|
||||
* @param isCopy
|
||||
* boolean 是否为复制模式
|
||||
* @param account java.lang.String 操作者账户名
|
||||
* @param ip java.lang.String 操作者IP地址
|
||||
* @param finalPath java.lang.String 被操作后的节点完整路径
|
||||
* @param originPath java.lang.String 未操作前的节点完整路径
|
||||
* @param isCopy boolean 是否为复制模式
|
||||
*/
|
||||
public void writeMoveFileEvent(String account, String ip, String originPath, String finalPath, boolean isCopy) {
|
||||
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
|
||||
@ -403,16 +391,11 @@ public class LogUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param account
|
||||
* java.lang.String 操作者账户名
|
||||
* @param ip
|
||||
* java.lang.String 操作者IP地址
|
||||
* @param finalPath
|
||||
* java.lang.String 被操作后的文件夹完整路径
|
||||
* @param originPath
|
||||
* java.lang.String 未操作前的文件夹完整路径
|
||||
* @param isCopy
|
||||
* boolean 是否为复制模式
|
||||
* @param account java.lang.String 操作者账户名
|
||||
* @param ip java.lang.String 操作者IP地址
|
||||
* @param finalPath java.lang.String 被操作后的文件夹完整路径
|
||||
* @param originPath java.lang.String 未操作前的文件夹完整路径
|
||||
* @param isCopy boolean 是否为复制模式
|
||||
*/
|
||||
public void writeMoveFolderEvent(String account, String ip, String originPath, String finalPath, boolean isCopy) {
|
||||
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
|
||||
@ -428,7 +411,7 @@ public class LogUtil {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 将文本信息以格式化标准写入日志文件中
|
||||
private void writeToLog(String type, String content) {
|
||||
String t = ServerTimeUtil.accurateToLogName();
|
||||
|
@ -12,8 +12,8 @@ import javax.servlet.http.HttpServletResponse;
|
||||
*
|
||||
* <h2>断点式文件输出流写出工具</h2>
|
||||
* <p>
|
||||
* 该工具负责处理断点下载请求并以相应规则写出文件流。需要提供断点续传服务,请继承该类并调用writeRangeFileStream方法,
|
||||
* 该操作已换回较为简单的RandomAccessFile实现(效率与NIO相近,更节省内存)。
|
||||
* 该工具负责处理断点下载请求并以相应规则写出文件流。需要提供断点续传服务,请继承该类并调用writeRangeFileStream方法。
|
||||
* 若无法继承,也可以直接静态调用此方法。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
@ -31,26 +31,64 @@ public class RangeFileStreamWriter {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param request
|
||||
* javax.servlet.http.HttpServletRequest 请求对象
|
||||
* @param response
|
||||
* javax.servlet.http.HttpServletResponse 响应对象
|
||||
* @param fo
|
||||
* java.io.File 需要写出的文件
|
||||
* @param fname
|
||||
* java.lang.String 文件名
|
||||
* @param contentType
|
||||
* java.lang.String HTTP Content-Type类型(用于控制客户端行为)
|
||||
* @param maxRate
|
||||
* long 最大输出速率,以KB/s为单位,若为负数则不限制输出速率(用于限制客户端的下载速度)
|
||||
* @param eTag
|
||||
* java.lang.String 资源的唯一性标识,例如"aabbcc"
|
||||
* @param isAttachment
|
||||
* boolean 是否作为附件回传,若希望用户下载则应设置为true
|
||||
* @param request javax.servlet.http.HttpServletRequest 请求对象
|
||||
* @param response javax.servlet.http.HttpServletResponse 响应对象
|
||||
* @param fo java.io.File 需要写出的文件
|
||||
* @param fname java.lang.String 文件名
|
||||
* @param contentType java.lang.String HTTP Content-Type类型(用于控制客户端行为)
|
||||
* @param maxRate long 最大输出速率,以B/s为单位,若为负数则不限制输出速率(用于限制客户端的下载速度)
|
||||
* @param eTag java.lang.String 资源的唯一性标识,例如"aabbcc"
|
||||
* @param isAttachment boolean 是否作为附件回传,若希望用户下载(而非预览)则应设置为true
|
||||
* @return int 操作结束时返回的状态码
|
||||
*/
|
||||
protected int writeRangeFileStream(HttpServletRequest request, HttpServletResponse response, File fo, String fname,
|
||||
String contentType, long maxRate, String eTag, boolean isAttachment) {
|
||||
public static int writeRangeFileStream(HttpServletRequest request, HttpServletResponse response, File fo,
|
||||
String fname, String contentType, long maxRate, String eTag, boolean isAttachment) {
|
||||
return writeRangeFile(request, response, fo, fname, contentType, maxRate, eTag, isAttachment, true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>发送文件信息,但仅返回响应头,而不返回具体内容</h2>
|
||||
* <p>
|
||||
* 处理普通的或带有断点续传参数的下载请求,并按照请求方式提供响应头信息,不提供具体的文件内容。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param request javax.servlet.http.HttpServletRequest 请求对象
|
||||
* @param response javax.servlet.http.HttpServletResponse 响应对象
|
||||
* @param fo java.io.File 需要写出的文件
|
||||
* @param fname java.lang.String 文件名
|
||||
* @param contentType java.lang.String HTTP Content-Type类型(用于控制客户端行为)
|
||||
* @param eTag java.lang.String 资源的唯一性标识,例如"aabbcc"
|
||||
* @param isAttachment boolean 是否作为附件回传,若希望用户下载(而非预览)则应设置为true
|
||||
* @return int 操作结束时返回的状态码
|
||||
*/
|
||||
public static int writeRangeFileHead(HttpServletRequest request, HttpServletResponse response, File fo,
|
||||
String fname, String contentType, String eTag, boolean isAttachment) {
|
||||
return writeRangeFile(request, response, fo, fname, contentType, -1, eTag, isAttachment, false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>回传文件数据,可选择是否发送具体的文件内容</h2>
|
||||
* <p>
|
||||
* 该方法用于提供对文件下载请求的处理,并按照请求方式提供相应的输出流写出。当选择发送具体的文件内容时将会正常返回文件内容, 否则仅返回响应头而无响应体。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param request javax.servlet.http.HttpServletRequest 请求对象
|
||||
* @param response javax.servlet.http.HttpServletResponse 响应对象
|
||||
* @param fo java.io.File 需要写出的文件
|
||||
* @param fname java.lang.String 文件名
|
||||
* @param contentType java.lang.String HTTP Content-Type类型(用于控制客户端行为)
|
||||
* @param maxRate long 最大输出速率,以B/s为单位,若为负数则不限制输出速率(用于限制客户端的下载速度)
|
||||
* @param eTag java.lang.String 资源的唯一性标识,例如"aabbcc"
|
||||
* @param isAttachment boolean 是否作为附件回传,若希望用户下载则应设置为true
|
||||
* @param sendBody boolean 是否发送具体的文件内容,若设置为false,则仅返回响应头
|
||||
* @return int 操作结束时返回的状态码
|
||||
*/
|
||||
private static int writeRangeFile(HttpServletRequest request, HttpServletResponse response, File fo, String fname,
|
||||
String contentType, long maxRate, String eTag, boolean isAttachment, boolean sendBody) {
|
||||
long fileLength = fo.length();// 文件总大小
|
||||
long startOffset = 0; // 起始偏移量
|
||||
boolean hasEnd = false;// 请求区间是否存在结束标识
|
||||
@ -144,45 +182,50 @@ public class RangeFileStreamWriter {
|
||||
} else { // 从开始进行下载
|
||||
contentLength = fileLength; // 客户端要求全文下载
|
||||
}
|
||||
response.setHeader("Content-Length", "" + contentLength);// 设置请求体长度
|
||||
// 写出缓冲
|
||||
byte[] buf = new byte[ConfigureReader.instance().getBuffSize()];
|
||||
// 读取文件并写处至输出流
|
||||
try (RandomAccessFile raf = new RandomAccessFile(fo, "r")) {
|
||||
BufferedOutputStream out = maxRate >= 0
|
||||
? new VariableSpeedBufferedOutputStream(response.getOutputStream(), maxRate, request.getSession())
|
||||
: new BufferedOutputStream(response.getOutputStream());
|
||||
raf.seek(startOffset);
|
||||
if (!hasEnd) {
|
||||
// 无结束偏移量时,将其从起始偏移量开始写到文件整体结束,如果从头开始下载,起始偏移量为0
|
||||
int n = 0;
|
||||
while ((n = raf.read(buf)) != -1) {
|
||||
out.write(buf, 0, n);
|
||||
}
|
||||
} else {
|
||||
// 有结束偏移量时,将其从起始偏移量开始写至指定偏移量结束。
|
||||
int n = 0;
|
||||
long readLength = 0;// 写出量,用于确定结束位置
|
||||
while (readLength < contentLength) {
|
||||
n = raf.read(buf);
|
||||
readLength += n;
|
||||
out.write(buf, 0, n);
|
||||
}
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
return status;
|
||||
} catch (IOException ex) {
|
||||
// 针对任何IO异常忽略,传输失败不处理
|
||||
status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
return status;
|
||||
} catch (IllegalArgumentException e) {
|
||||
status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
try {
|
||||
response.sendError(status);
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
return status;
|
||||
if (sendBody) {
|
||||
response.setHeader("Content-Length", "" + contentLength);// 设置请求体长度
|
||||
} else {
|
||||
response.setHeader("Content-Length", "0");
|
||||
}
|
||||
if (sendBody) {
|
||||
// 写出缓冲
|
||||
byte[] buf = new byte[ConfigureReader.instance().getBuffSize()];
|
||||
// 读取文件并写处至输出流
|
||||
try (RandomAccessFile raf = new RandomAccessFile(fo, "r")) {
|
||||
BufferedOutputStream out = maxRate >= 0
|
||||
? new VariableSpeedBufferedOutputStream(response.getOutputStream(), maxRate,
|
||||
request.getSession())
|
||||
: new BufferedOutputStream(response.getOutputStream());
|
||||
raf.seek(startOffset);
|
||||
if (!hasEnd) {
|
||||
// 无结束偏移量时,将其从起始偏移量开始写到文件整体结束,如果从头开始下载,起始偏移量为0
|
||||
int n = 0;
|
||||
while ((n = raf.read(buf)) != -1) {
|
||||
out.write(buf, 0, n);
|
||||
}
|
||||
} else {
|
||||
// 有结束偏移量时,将其从起始偏移量开始写至指定偏移量结束。
|
||||
int n = 0;
|
||||
long readLength = 0;// 写出量,用于确定结束位置
|
||||
while (readLength < contentLength) {
|
||||
n = raf.read(buf);
|
||||
readLength += n;
|
||||
out.write(buf, 0, (int) (readLength <= contentLength ? n : n - (readLength - contentLength)));
|
||||
}
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (IOException ex) {
|
||||
// 针对任何IO异常忽略,传输失败不处理
|
||||
status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
} catch (IllegalArgumentException e) {
|
||||
status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
try {
|
||||
response.sendError(status);
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,14 @@ package kohgylw.kiftd.server.util;
|
||||
import java.util.*;
|
||||
import java.io.File;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.TemporalQueries;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -55,8 +59,7 @@ public class ServerTimeUtil {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param block
|
||||
* java.io.File 要生成的文件块对象,应该是文件,但也支持文件夹,或者null
|
||||
* @param block java.io.File 要生成的文件块对象,应该是文件,但也支持文件夹,或者null
|
||||
* @return java.lang.String 记录最后修改日期的时间截,格式类似于“Wed, 29 Apr 2020 08:18:43
|
||||
* GMT”,若传入文件不存在或为null,则返回当前时间
|
||||
*/
|
||||
@ -69,6 +72,29 @@ public class ServerTimeUtil {
|
||||
}
|
||||
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.US)
|
||||
.withZone(ZoneId.of("GMT"));
|
||||
return longToTime.format(dtf).trim();
|
||||
return longToTime.format(dtf);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>将日期字符串(精确到日)转化为时间值</h2>
|
||||
* <p>
|
||||
* 该方法用于将本工具生成的日期字符串(精确到日)转化成以毫秒为单位的long型时间,
|
||||
* 该时间从1970-01-01T00:00:00Z开始计数。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param date 要转化的日期字符串(精确到日),该字符串应由本类中的String accurateToDay()方法生成。
|
||||
* @return long 时间,以毫秒为单位,从1970-01-01T00:00:00Z开始计数。如果无法转化(例如传入“--”),则返回0。
|
||||
*/
|
||||
public static long getTimeFromDateAccurateToDay(String date) {
|
||||
try {
|
||||
DateTimeFormatter dtfDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
|
||||
LocalDate ld = dtfDateTimeFormatter.parse(date, TemporalQueries.localDate());
|
||||
Instant instant = ld.atTime(LocalTime.MIDNIGHT).atZone(ZoneId.systemDefault()).toInstant();
|
||||
return instant.toEpochMilli();
|
||||
} catch (DateTimeParseException e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,10 @@ import javax.servlet.http.HttpSession;
|
||||
*/
|
||||
public class VariableSpeedBufferedOutputStream extends BufferedOutputStream {
|
||||
|
||||
private long maxRate;// 该实例的最大输出限速,以KB/s为单位。
|
||||
private long maxRate;// 该实例的最大输出限速,以B/s为单位。
|
||||
private HttpSession session;// 该实例所用的用户会话对象,用于线程锁。
|
||||
private long writtenLength;// 一秒之内已经写出的数据长度,以B为单位
|
||||
private long startTime;// 计时起始时间,以毫秒为单位
|
||||
|
||||
/**
|
||||
*
|
||||
@ -33,18 +35,17 @@ public class VariableSpeedBufferedOutputStream extends BufferedOutputStream {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param out
|
||||
* java.io.OutputStream 原始输出流,传入方法与普通的BufferedOutputStream构造器相同
|
||||
* @param maxRate
|
||||
* long 每秒最大可输出的数据数量,以KB为单位。例如:传入100就代表该实例的最大输出限速为100 KB/s
|
||||
* @param session
|
||||
* javax.servlet.http.HttpSession 传入用户会话。该对象用于锁定输出操作,从而确保当用户开启多个
|
||||
* 下载任务时,总的最大下载速率仍不会超过限速值
|
||||
* @param out java.io.OutputStream 原始输出流,传入方法与普通的BufferedOutputStream构造器相同
|
||||
* @param maxRate long 每秒最大可输出的数据数量,以B为单位。例如:传入1024就代表该实例的最大输出限速为1KB/s
|
||||
* @param session javax.servlet.http.HttpSession 传入用户会话。该对象用于锁定输出操作,从而确保当用户开启多个
|
||||
* 下载任务时,总的最大下载速率仍不会超过限速值
|
||||
*/
|
||||
public VariableSpeedBufferedOutputStream(OutputStream out, long maxRate, HttpSession session) {
|
||||
super(out);
|
||||
this.maxRate = maxRate;
|
||||
this.session = session;
|
||||
this.writtenLength = 0;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,37 +57,51 @@ public class VariableSpeedBufferedOutputStream extends BufferedOutputStream {
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param b
|
||||
* byte[] 数据数组
|
||||
* @param off
|
||||
* int 数据的起始下标
|
||||
* @param len
|
||||
* int 数据在数组中的长度
|
||||
* @param b byte[] 数据数组
|
||||
* @param off int 数据的起始下标
|
||||
* @param len int 数据在数组中的长度
|
||||
*/
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
if (maxRate > 0) {
|
||||
// 如果限速值为正数,那么进行限速输出,输出时要求独占Session对象以确保和其它下载任务共享最大限速。
|
||||
synchronized (session) {
|
||||
// 记录写出前的纳秒值
|
||||
long startNano = System.nanoTime();
|
||||
super.write(b, off, len);// 执行写出……
|
||||
// 记录写出后的纳秒值
|
||||
long endNano = System.nanoTime();
|
||||
int n = len - off;// 计算本次写出的数据长度(byte数)
|
||||
if (n > 0) {// 确实写出了数据?
|
||||
long consumeNano = endNano - startNano;// 记录写出的耗时(纳秒)
|
||||
long shouldNano = 976562L * (long)((double) n / maxRate);// 计算最大应耗时(纳秒)
|
||||
if (consumeNano < shouldNano) {// 如果实际耗时小于应耗时,则代表写出过快,应进行延迟
|
||||
// 计算还需要再延迟的毫秒数
|
||||
long shouldSleep = (shouldNano - consumeNano) / 1000000;
|
||||
if (shouldSleep > 0) {// 延迟毫秒数大于0?
|
||||
int startIndex = off;// 记录当前应读数组的起始位置
|
||||
int surplusLength = len;// 记录数组中应写的剩余数据量
|
||||
while (surplusLength > 0) {
|
||||
synchronized (session) {
|
||||
// 如果数组内仍有数据存留,则执行写出操作
|
||||
// 如果尚未开始计量一秒内的写出量,则记录写出前的毫秒值
|
||||
if (writtenLength == 0) {
|
||||
startTime = System.currentTimeMillis();
|
||||
}
|
||||
// 计算此秒之内最多还能写出多少数据
|
||||
long shouldWriteLength = maxRate - writtenLength;
|
||||
if (surplusLength > shouldWriteLength) {
|
||||
// 如果数组内数据的剩余量大于此秒允许写出量,则写出允许写出量的数据
|
||||
super.write(b, startIndex, (int) shouldWriteLength);
|
||||
startIndex += shouldWriteLength;
|
||||
writtenLength += shouldWriteLength;
|
||||
surplusLength -= shouldWriteLength;
|
||||
} else {
|
||||
// 如果数组内数据的剩余量小于等于此秒的应写量,则将剩余量的数据全部写出
|
||||
super.write(b, startIndex, surplusLength);
|
||||
startIndex += surplusLength;
|
||||
writtenLength += surplusLength;
|
||||
surplusLength -= surplusLength;
|
||||
}
|
||||
if (writtenLength >= maxRate) {
|
||||
// 如果已写出量达到了每秒允许的最大写出量,则计算实际耗时
|
||||
long consumeTime = System.currentTimeMillis() - startTime;
|
||||
if (consumeTime < 1000) {
|
||||
// 如果实际耗时小于1秒,那么睡够一秒
|
||||
try {
|
||||
Thread.sleep(shouldSleep);// 执行延迟
|
||||
Thread.sleep(1000 - consumeTime);
|
||||
} catch (InterruptedException e) {
|
||||
// 如果收到中断指令,那么就响应中断
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
// 写出量计数归0,以便下次写出时重新计量
|
||||
writtenLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,5 +112,4 @@ public class VariableSpeedBufferedOutputStream extends BufferedOutputStream {
|
||||
throw new IllegalArgumentException("Error:invalid maximum download rate value.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,9 +13,9 @@ import org.springframework.stereotype.Component;
|
||||
import kohgylw.kiftd.server.mapper.NodeMapper;
|
||||
import kohgylw.kiftd.server.model.Node;
|
||||
import kohgylw.kiftd.server.pojo.VideoTranscodeThread;
|
||||
import ws.schild.jave.AudioAttributes;
|
||||
import ws.schild.jave.EncodingAttributes;
|
||||
import ws.schild.jave.VideoAttributes;
|
||||
import ws.schild.jave.encode.AudioAttributes;
|
||||
import ws.schild.jave.encode.EncodingAttributes;
|
||||
import ws.schild.jave.encode.VideoAttributes;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -49,7 +49,7 @@ public class VideoTranscodeUtil {
|
||||
VideoAttributes video = new VideoAttributes();
|
||||
video.setCodec("libx264");
|
||||
ea = new EncodingAttributes();
|
||||
ea.setFormat("MP4");
|
||||
ea.setOutputFormat("MP4");
|
||||
ea.setVideoAttributes(video);
|
||||
ea.setAudioAttributes(audio);
|
||||
}
|
||||
|
3123
src/main/java/kohgylw/kiftd/server/webdav/KiftdWebDAVServlet.java
Normal file
3123
src/main/java/kohgylw/kiftd/server/webdav/KiftdWebDAVServlet.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package kohgylw.kiftd.server.webdav.date;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
* A thread safe wrapper around {@link SimpleDateFormat} that does not make use
|
||||
* of ThreadLocal and - broadly - only creates enough SimpleDateFormat objects
|
||||
* to satisfy the concurrency requirements.
|
||||
*/
|
||||
public class ConcurrentDateFormat {
|
||||
|
||||
private final String format;
|
||||
private final Locale locale;
|
||||
private final TimeZone timezone;
|
||||
private final Queue<SimpleDateFormat> queue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public ConcurrentDateFormat(String format, Locale locale, TimeZone timezone) {
|
||||
this.format = format;
|
||||
this.locale = locale;
|
||||
this.timezone = timezone;
|
||||
SimpleDateFormat initial = createInstance();
|
||||
queue.add(initial);
|
||||
}
|
||||
|
||||
public String format(Date date) {
|
||||
SimpleDateFormat sdf = queue.poll();
|
||||
if (sdf == null) {
|
||||
sdf = createInstance();
|
||||
}
|
||||
String result = sdf.format(date);
|
||||
queue.add(sdf);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Date parse(String source) throws ParseException {
|
||||
SimpleDateFormat sdf = queue.poll();
|
||||
if (sdf == null) {
|
||||
sdf = createInstance();
|
||||
}
|
||||
Date result = sdf.parse(source);
|
||||
sdf.setTimeZone(timezone);
|
||||
queue.add(sdf);
|
||||
return result;
|
||||
}
|
||||
|
||||
private SimpleDateFormat createInstance() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
|
||||
sdf.setTimeZone(timezone);
|
||||
return sdf;
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package kohgylw.kiftd.server.webdav.date;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Utility class to generate HTTP dates.
|
||||
*
|
||||
* @author Remy Maucherat
|
||||
*/
|
||||
public final class FastHttpDateFormat {
|
||||
|
||||
|
||||
// -------------------------------------------------------------- Variables
|
||||
|
||||
|
||||
private static final int CACHE_SIZE =
|
||||
Integer.parseInt(System.getProperty("org.apache.tomcat.util.http.FastHttpDateFormat.CACHE_SIZE", "1000"));
|
||||
|
||||
|
||||
/**
|
||||
* The only date format permitted when generating HTTP headers.
|
||||
*
|
||||
* @deprecated Unused. This will be removed in Tomcat 10.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String RFC1123_DATE = "EEE, dd MMM yyyy HH:mm:ss zzz";
|
||||
|
||||
// HTTP date formats
|
||||
private static final String DATE_RFC5322 = "EEE, dd MMM yyyy HH:mm:ss z";
|
||||
private static final String DATE_OBSOLETE_RFC850 = "EEEEEE, dd-MMM-yy HH:mm:ss zzz";
|
||||
private static final String DATE_OBSOLETE_ASCTIME = "EEE MMMM d HH:mm:ss yyyy";
|
||||
|
||||
private static final ConcurrentDateFormat FORMAT_RFC5322;
|
||||
private static final ConcurrentDateFormat FORMAT_OBSOLETE_RFC850;
|
||||
private static final ConcurrentDateFormat FORMAT_OBSOLETE_ASCTIME;
|
||||
|
||||
private static final ConcurrentDateFormat[] httpParseFormats;
|
||||
|
||||
static {
|
||||
// All the formats that use a timezone use GMT
|
||||
TimeZone tz = TimeZone.getTimeZone("GMT");
|
||||
|
||||
FORMAT_RFC5322 = new ConcurrentDateFormat(DATE_RFC5322, Locale.US, tz);
|
||||
FORMAT_OBSOLETE_RFC850 = new ConcurrentDateFormat(DATE_OBSOLETE_RFC850, Locale.US, tz);
|
||||
FORMAT_OBSOLETE_ASCTIME = new ConcurrentDateFormat(DATE_OBSOLETE_ASCTIME, Locale.US, tz);
|
||||
|
||||
httpParseFormats = new ConcurrentDateFormat[] {
|
||||
FORMAT_RFC5322, FORMAT_OBSOLETE_RFC850, FORMAT_OBSOLETE_ASCTIME };
|
||||
}
|
||||
|
||||
/**
|
||||
* Instant on which the currentDate object was generated.
|
||||
*/
|
||||
private static volatile long currentDateGenerated = 0L;
|
||||
|
||||
|
||||
/**
|
||||
* Current formatted date.
|
||||
*/
|
||||
private static String currentDate = null;
|
||||
|
||||
|
||||
/**
|
||||
* Formatter cache.
|
||||
*/
|
||||
private static final Map<Long, String> formatCache = new ConcurrentHashMap<>(CACHE_SIZE);
|
||||
|
||||
|
||||
/**
|
||||
* Parser cache.
|
||||
*/
|
||||
private static final Map<String, Long> parseCache = new ConcurrentHashMap<>(CACHE_SIZE);
|
||||
|
||||
|
||||
// --------------------------------------------------------- Public Methods
|
||||
|
||||
|
||||
/**
|
||||
* Get the current date in HTTP format.
|
||||
* @return the HTTP date
|
||||
*/
|
||||
public static final String getCurrentDate() {
|
||||
long now = System.currentTimeMillis();
|
||||
// Handle case where time moves backwards (e.g. system time corrected)
|
||||
if (Math.abs(now - currentDateGenerated) > 1000) {
|
||||
currentDate = FORMAT_RFC5322.format(new Date(now));
|
||||
currentDateGenerated = now;
|
||||
}
|
||||
return currentDate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the HTTP format of the specified date.
|
||||
* @param value The date
|
||||
* @param threadLocalformat Ignored. The local ConcurrentDateFormat will
|
||||
* always be used.
|
||||
* @return the HTTP date
|
||||
*
|
||||
* @deprecated Unused. This will be removed in Tomcat 10
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String formatDate(long value, DateFormat threadLocalformat) {
|
||||
return formatDate(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the HTTP format of the specified date.
|
||||
* @param value The date
|
||||
* @return the HTTP date
|
||||
*/
|
||||
public static final String formatDate(long value) {
|
||||
Long longValue = Long.valueOf(value);
|
||||
String cachedDate = formatCache.get(longValue);
|
||||
if (cachedDate != null) {
|
||||
return cachedDate;
|
||||
}
|
||||
|
||||
String newDate = FORMAT_RFC5322.format(new Date(value));
|
||||
updateFormatCache(longValue, newDate);
|
||||
return newDate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to parse the given date as an HTTP date.
|
||||
* @param value The HTTP date
|
||||
* @param threadLocalformats Ignored. The local array of
|
||||
* ConcurrentDateFormat will always be used.
|
||||
* @return the date as a long
|
||||
*
|
||||
* @deprecated Unused. This will be removed in Tomcat 10
|
||||
* Use {@link #parseDate(String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static final long parseDate(String value, DateFormat[] threadLocalformats) {
|
||||
return parseDate(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to parse the given date as an HTTP date.
|
||||
* @param value The HTTP date
|
||||
* @return the date as a long or <code>-1</code> if the value cannot be
|
||||
* parsed
|
||||
*/
|
||||
public static final long parseDate(String value) {
|
||||
|
||||
Long cachedDate = parseCache.get(value);
|
||||
if (cachedDate != null) {
|
||||
return cachedDate.longValue();
|
||||
}
|
||||
|
||||
long date = -1;
|
||||
for (int i = 0; (date == -1) && (i < httpParseFormats.length); i++) {
|
||||
try {
|
||||
date = httpParseFormats[i].parse(value).getTime();
|
||||
updateParseCache(value, Long.valueOf(date));
|
||||
} catch (ParseException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update cache.
|
||||
*/
|
||||
private static void updateFormatCache(Long key, String value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (formatCache.size() > CACHE_SIZE) {
|
||||
formatCache.clear();
|
||||
}
|
||||
formatCache.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update cache.
|
||||
*/
|
||||
private static void updateParseCache(String key, Long value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (parseCache.size() > CACHE_SIZE) {
|
||||
parseCache.clear();
|
||||
}
|
||||
parseCache.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
}
|
164
src/main/java/kohgylw/kiftd/server/webdav/dom/DOMWriter.java
Normal file
164
src/main/java/kohgylw/kiftd/server/webdav/dom/DOMWriter.java
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package kohgylw.kiftd.server.webdav.dom;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.w3c.dom.Attr;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
/**
|
||||
* A DOM writer optimised for use by WebDAV.
|
||||
*/
|
||||
public class DOMWriter {
|
||||
|
||||
private final PrintWriter out;
|
||||
|
||||
|
||||
public DOMWriter(Writer writer) {
|
||||
out = new PrintWriter(writer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prints the specified node, recursively.
|
||||
* @param node The node to output
|
||||
*/
|
||||
public void print(Node node) {
|
||||
|
||||
// is there anything to do?
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int type = node.getNodeType();
|
||||
switch (type) {
|
||||
// print document
|
||||
case Node.DOCUMENT_NODE:
|
||||
print(((Document) node).getDocumentElement());
|
||||
out.flush();
|
||||
break;
|
||||
|
||||
// print element with attributes
|
||||
case Node.ELEMENT_NODE:
|
||||
out.print('<');
|
||||
out.print(node.getLocalName());
|
||||
Attr attrs[] = sortAttributes(node.getAttributes());
|
||||
for (Attr attr : attrs) {
|
||||
out.print(' ');
|
||||
out.print(attr.getLocalName());
|
||||
|
||||
out.print("=\"");
|
||||
out.print(Escape.xml("", true, attr.getNodeValue()));
|
||||
out.print('"');
|
||||
}
|
||||
out.print('>');
|
||||
printChildren(node);
|
||||
break;
|
||||
|
||||
// handle entity reference nodes
|
||||
case Node.ENTITY_REFERENCE_NODE:
|
||||
printChildren(node);
|
||||
break;
|
||||
|
||||
// print cdata sections
|
||||
case Node.CDATA_SECTION_NODE:
|
||||
out.print(Escape.xml("", true, node.getNodeValue()));
|
||||
break;
|
||||
|
||||
// print text
|
||||
case Node.TEXT_NODE:
|
||||
out.print(Escape.xml("", true, node.getNodeValue()));
|
||||
break;
|
||||
|
||||
// print processing instruction
|
||||
case Node.PROCESSING_INSTRUCTION_NODE:
|
||||
out.print("<?");
|
||||
out.print(node.getLocalName());
|
||||
|
||||
String data = node.getNodeValue();
|
||||
if (data != null && data.length() > 0) {
|
||||
out.print(' ');
|
||||
out.print(data);
|
||||
}
|
||||
out.print("?>");
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == Node.ELEMENT_NODE) {
|
||||
out.print("</");
|
||||
out.print(node.getLocalName());
|
||||
out.print('>');
|
||||
}
|
||||
|
||||
out.flush();
|
||||
|
||||
} // print(Node)
|
||||
|
||||
|
||||
private void printChildren(Node node) {
|
||||
NodeList children = node.getChildNodes();
|
||||
if (children != null) {
|
||||
int len = children.getLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
print(children.item(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a sorted list of attributes.
|
||||
* @param attrs The map to sort
|
||||
* @return a sorted attribute array
|
||||
*/
|
||||
private Attr[] sortAttributes(NamedNodeMap attrs) {
|
||||
if (attrs == null) {
|
||||
return new Attr[0];
|
||||
}
|
||||
|
||||
int len = attrs.getLength();
|
||||
Attr array[] = new Attr[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
array[i] = (Attr) attrs.item(i);
|
||||
}
|
||||
for (int i = 0; i < len - 1; i++) {
|
||||
String name = null;
|
||||
name = array[i].getLocalName();
|
||||
int index = i;
|
||||
for (int j = i + 1; j < len; j++) {
|
||||
String curName = null;
|
||||
curName = array[j].getLocalName();
|
||||
if (curName.compareTo(name) < 0) {
|
||||
name = curName;
|
||||
index = j;
|
||||
}
|
||||
}
|
||||
if (index != i) {
|
||||
Attr temp = array[i];
|
||||
array[i] = array[index];
|
||||
array[index] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
165
src/main/java/kohgylw/kiftd/server/webdav/dom/Escape.java
Normal file
165
src/main/java/kohgylw/kiftd/server/webdav/dom/Escape.java
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package kohgylw.kiftd.server.webdav.dom;
|
||||
|
||||
/**
|
||||
* Provides utility methods to escape content for different contexts. It is
|
||||
* critical that the escaping used is correct for the context in which the data
|
||||
* is to be used.
|
||||
*/
|
||||
public class Escape {
|
||||
|
||||
private Escape() {
|
||||
// Hide default constructor for this utility class
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Escape content for use in HTML. This escaping is suitable for the
|
||||
* following uses:
|
||||
* <ul>
|
||||
* <li>Element content when the escaped data will be placed directly inside
|
||||
* tags such as <p>, <td> etc.</li>
|
||||
* <li>Attribute values when the attribute value is quoted with " or
|
||||
* '.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param content The content to escape
|
||||
*
|
||||
* @return The escaped content or {@code null} if the content was
|
||||
* {@code null}
|
||||
*/
|
||||
public static String htmlElementContent(String content) {
|
||||
if (content == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < content.length(); i++) {
|
||||
char c = content.charAt(i);
|
||||
if (c == '<') {
|
||||
sb.append("<");
|
||||
} else if (c == '>') {
|
||||
sb.append(">");
|
||||
} else if (c == '\'') {
|
||||
sb.append("'");
|
||||
} else if (c == '&') {
|
||||
sb.append("&");
|
||||
} else if (c == '"') {
|
||||
sb.append(""");
|
||||
} else if (c == '/') {
|
||||
sb.append("/");
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return (sb.length() > content.length()) ? sb.toString() : content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert the object to a string via {@link Object#toString()} and HTML
|
||||
* escape the resulting string for use in HTML content.
|
||||
*
|
||||
* @param obj The object to convert to String and then escape
|
||||
*
|
||||
* @return The escaped content or <code>"?"</code> if obj is
|
||||
* {@code null}
|
||||
*/
|
||||
public static String htmlElementContent(Object obj) {
|
||||
if (obj == null) {
|
||||
return "?";
|
||||
}
|
||||
|
||||
try {
|
||||
return htmlElementContent(obj.toString());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Escape content for use in XML.
|
||||
*
|
||||
* @param content The content to escape
|
||||
*
|
||||
* @return The escaped content or {@code null} if the content was
|
||||
* {@code null}
|
||||
*/
|
||||
public static String xml(String content) {
|
||||
return xml(null, content);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Escape content for use in XML.
|
||||
*
|
||||
* @param ifNull The value to return if content is {@code null}
|
||||
* @param content The content to escape
|
||||
*
|
||||
* @return The escaped content or the value of {@code ifNull} if the
|
||||
* content was {@code null}
|
||||
*/
|
||||
public static String xml(String ifNull, String content) {
|
||||
return xml(ifNull, false, content);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Escape content for use in XML.
|
||||
*
|
||||
* @param ifNull The value to return if content is {@code null}
|
||||
* @param escapeCRLF Should CR and LF also be escaped?
|
||||
* @param content The content to escape
|
||||
*
|
||||
* @return The escaped content or the value of ifNull if the content was
|
||||
* {@code null}
|
||||
*/
|
||||
public static String xml(String ifNull, boolean escapeCRLF, String content) {
|
||||
if (content == null) {
|
||||
return ifNull;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < content.length(); i++) {
|
||||
char c = content.charAt(i);
|
||||
if (c == '<') {
|
||||
sb.append("<");
|
||||
} else if (c == '>') {
|
||||
sb.append(">");
|
||||
} else if (c == '\'') {
|
||||
sb.append("'");
|
||||
} else if (c == '&') {
|
||||
sb.append("&");
|
||||
} else if (c == '"') {
|
||||
sb.append(""");
|
||||
} else if (escapeCRLF && c == '\r') {
|
||||
sb.append(" ");
|
||||
} else if (escapeCRLF && c == '\n') {
|
||||
sb.append(" ");
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return (sb.length() > content.length()) ? sb.toString(): content;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package kohgylw.kiftd.server.webdav.exception;
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>未授权异常</h2>
|
||||
* <p>该异常用于表名在WebDAV操作中未进行合适的授权。</p>
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @version 1.0
|
||||
*/
|
||||
public class UnAuthorizedException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
}
|
@ -0,0 +1,327 @@
|
||||
package kohgylw.kiftd.server.webdav.pojo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
|
||||
import kohgylw.kiftd.server.model.Folder;
|
||||
import kohgylw.kiftd.server.model.Node;
|
||||
import kohgylw.kiftd.server.util.ServerTimeUtil;
|
||||
import kohgylw.kiftd.server.webdav.date.FastHttpDateFormat;
|
||||
import kohgylw.kiftd.server.webdav.url.HttpPathUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>封装WebDAV请求的文件或文件夹的资源类</h2>
|
||||
* <p>
|
||||
* 该类用于将kiftd中的文件或文件夹封装为一个抽象的“资源”以便进行统一处理,避免文件和文件夹区分处理带来的代码混乱。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @version 1.0
|
||||
*/
|
||||
public class KiftdWebDAVResource implements WebResource {
|
||||
|
||||
private String path;// 该资源的路径
|
||||
private boolean isExists;// 该资源是否存在?
|
||||
private boolean isDirectory;// 该资源是一个文件夹?
|
||||
private long creation;// 创建日期
|
||||
private String name;// 资源名称
|
||||
private String mimeType;// Mime Type
|
||||
private volatile String weakETag;// 存储ETag声明
|
||||
|
||||
private Node node;// 如果资源是个文件,那么该属性对应文件节点对象
|
||||
private File fileBlock;// 如果资源是个文件,那么该属性对应文件块对象
|
||||
private Folder folder;// 如果资源是个文件夹,那么该属性对应文件夹对象
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>构造一个文件夹资源</h2>
|
||||
* <p>
|
||||
* 该方法将构造一个文件夹对应的资源。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path 资源路径,必须输入完整的路径名称,以“/”开头,例如“/foo”或“/foo/bar”
|
||||
* @param folder 文件夹对象,如果文件夹不存在则可传入null
|
||||
*/
|
||||
public KiftdWebDAVResource(String path, Folder folder) {
|
||||
this.path = path;
|
||||
this.folder = folder;
|
||||
if (this.path != null && this.folder != null) {
|
||||
if (!path.endsWith("/")) {
|
||||
// 规范化路径名,文件夹路径统一以“/”结尾以符合URL标准
|
||||
this.path = path + "/";
|
||||
}
|
||||
isExists = true;
|
||||
isDirectory = true;
|
||||
name = folder.getFolderName();
|
||||
creation = ServerTimeUtil.getTimeFromDateAccurateToDay(this.folder.getFolderCreationDate());
|
||||
} else {
|
||||
this.name = HttpPathUtil.getResourceName(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>构造一个文件资源</h2>
|
||||
* <p>
|
||||
* 该方法将构造一个文件对应的资源。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path 资源路径,必须输入完整的路径名称,以“/”开头,并以“/”结尾。例如“/foo/”
|
||||
* @param node 文件节点对象,如果文件节点不存在则可传入null
|
||||
* @param block 文件块对象,如果文件节点不存在则也应传入null(但不会作为文件节点存在与否的判断条件)
|
||||
*/
|
||||
public KiftdWebDAVResource(String path, Node node, File block) {
|
||||
this.path = path;
|
||||
this.node = node;
|
||||
this.fileBlock = block;
|
||||
if (this.path != null && this.node != null && this.fileBlock != null) {
|
||||
isExists = true;
|
||||
name = this.node.getFileName();
|
||||
try {
|
||||
BasicFileAttributes attrs = Files.readAttributes(fileBlock.toPath(), BasicFileAttributes.class);
|
||||
creation = attrs.creationTime().toMillis();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
} else {
|
||||
this.name = HttpPathUtil.getResourceName(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return creation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastModifiedHttp() {
|
||||
return FastHttpDateFormat.formatDate(getLastModified());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return isExists;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return isDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return isExists && !isDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
if (isExists) {
|
||||
if (isDirectory) {
|
||||
return true;
|
||||
}
|
||||
return fileBlock.delete();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLength() {
|
||||
if (isExists) {
|
||||
if (isDirectory) {
|
||||
return -1;
|
||||
}
|
||||
return fileBlock.length();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
if (isExists) {
|
||||
if (isDirectory) {
|
||||
return true;
|
||||
}
|
||||
return fileBlock.canRead();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebappPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getETag() {
|
||||
// 使用懒加载模式,如果第一次调用时该值尚未初始化,则临时初始化其值
|
||||
if (weakETag == null) {
|
||||
synchronized (this) {
|
||||
if (weakETag == null) {
|
||||
long contentLength = getContentLength();
|
||||
long lastModified = getLastModified();
|
||||
if ((contentLength >= 0) || (lastModified >= 0)) {
|
||||
weakETag = "W/\"" + contentLength + "-" + lastModified + "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 已经初始化过了,则直接返回
|
||||
return weakETag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
if (isDirectory) {
|
||||
// 文件夹没有输入流
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new FileInputStream(fileBlock);
|
||||
} catch (IOException | NullPointerException e) {
|
||||
// 文件不存在
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent() {
|
||||
long len = getContentLength();
|
||||
if (len > Integer.MAX_VALUE) {
|
||||
// 文件内容过大
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
if (isDirectory || len < 0) {
|
||||
// 如果是文件夹或不存在的文件,则无内容返回
|
||||
return null;
|
||||
}
|
||||
int size = (int) len;
|
||||
byte[] result = new byte[size];
|
||||
int pos = 0;
|
||||
try (InputStream is = new FileInputStream(fileBlock)) {
|
||||
while (pos < size) {
|
||||
int n = is.read(result, pos, size - pos);
|
||||
if (n < 0) {
|
||||
break;
|
||||
}
|
||||
pos += n;
|
||||
}
|
||||
} catch (IOException | NullPointerException e) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreation() {
|
||||
return creation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getURL() {
|
||||
if (isExists) {
|
||||
try {
|
||||
return new URI("file:" + path).toURL();
|
||||
} catch (MalformedURLException | URISyntaxException e) {
|
||||
// 路径格式有错时,也返回null
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getCodeBase() {
|
||||
// 不存在解析代码问题,因此全部视为普通文件
|
||||
return getURL();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceRoot getWebResourceRoot() {
|
||||
// 由于使用的是虚拟文件系统,因此不基于Servlet容器进行文件操作
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getCertificates() {
|
||||
// 不提供签名验证服务
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() {
|
||||
// 不提供资源关联服务
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>获取该资源对应的文件节点</h2>
|
||||
* <p>
|
||||
* 当该资源对应的是一个文件时,返回文件节点对象。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @return kohgylw.kiftd.server.model.Node 该资源对应的文件节点对象,
|
||||
* 当该资源不存在或者该资源对应的是一个文件夹时,返回null
|
||||
*/
|
||||
public Node getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>获取该资源对应的文件夹</h2>
|
||||
* <p>
|
||||
* 当该资源对应的是一个文件夹时,返回文件夹对象。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @return kohgylw.kiftd.server.model.Folder 该资源对应的文件夹对象,
|
||||
* 当该资源不存在或者该资源对应的是一个文件时,返回null
|
||||
*/
|
||||
public Folder getFolder() {
|
||||
return folder;
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package kohgylw.kiftd.server.webdav.range;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class ContentRange {
|
||||
|
||||
private final String units;
|
||||
private final long start;
|
||||
private final long end;
|
||||
private final long length;
|
||||
|
||||
|
||||
public ContentRange(String units, long start, long end, long length) {
|
||||
this.units = units;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
|
||||
public String getUnits() {
|
||||
return units;
|
||||
}
|
||||
|
||||
|
||||
public long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
|
||||
public long getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a Content-Range header from an HTTP header.
|
||||
*
|
||||
* @param input a reader over the header text
|
||||
*
|
||||
* @return the range parsed from the input, or null if not valid
|
||||
*
|
||||
* @throws IOException if there was a problem reading the input
|
||||
*/
|
||||
public static ContentRange parse(StringReader input) throws IOException {
|
||||
// Units (required)
|
||||
String units = HttpParser.readToken(input);
|
||||
if (units == null || units.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Must be followed by SP. Parser is lenient and accepts any LWS here.
|
||||
// No need for explicit check as something must have terminated the
|
||||
// token and if that something was anything other than LWS the following
|
||||
// call to readLong() will fail.
|
||||
|
||||
// Start
|
||||
long start = HttpParser.readLong(input);
|
||||
|
||||
// Must be followed by '-'
|
||||
if (HttpParser.skipConstant(input, "-") == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// End
|
||||
long end = HttpParser.readLong(input);
|
||||
|
||||
// Must be followed by '/'
|
||||
if (HttpParser.skipConstant(input, "/") == SkipResult.NOT_FOUND) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Length
|
||||
long length = HttpParser.readLong(input);
|
||||
|
||||
// Doesn't matter what we look for, result should be EOF
|
||||
SkipResult skipResult = HttpParser.skipConstant(input, "X");
|
||||
|
||||
if (skipResult != SkipResult.EOF) {
|
||||
// Invalid range
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ContentRange(units, start, end, length);
|
||||
}
|
||||
}
|
1008
src/main/java/kohgylw/kiftd/server/webdav/range/HttpParser.java
Normal file
1008
src/main/java/kohgylw/kiftd/server/webdav/range/HttpParser.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package kohgylw.kiftd.server.webdav.range;
|
||||
|
||||
enum SkipResult {
|
||||
FOUND,
|
||||
NOT_FOUND,
|
||||
EOF
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package kohgylw.kiftd.server.webdav.url;
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>URL路径处理工具类</h2>
|
||||
* <p>
|
||||
* 该工具类用于提供各种URL路径的处理方法,所有方法皆为静态,因此无需实例化本类,直接使用静态方式调用即可。 各个方法的具体功能详见方法注释。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @version 1.0
|
||||
*/
|
||||
public class HttpPathUtil {
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>获取URL路径中的资源名称</h2>
|
||||
* <p>
|
||||
* 该方法将解析出URL路径中包含的逻辑资源名称,例如“/foo”的逻辑资源名称为“foo”,“/foo/bar/”的逻辑资源名称为“bar”。以此类推。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path URL路径,该路径应为以“/”起始的字符串。
|
||||
* @return java.lang.String 逻辑资源名称。当路径格式不正确时(例如不以“/”起始),则返回空字符串("")
|
||||
*/
|
||||
public static String getResourceName(String path) {
|
||||
if (!isAvailablePath(path)) {
|
||||
return "";
|
||||
} else {
|
||||
if (path.equals("/")) {
|
||||
// 如果是根目录,直接返回根目录名称
|
||||
return path;
|
||||
} else {
|
||||
String currentPath = path;
|
||||
if (currentPath.endsWith("/")) {
|
||||
// 如果是文件夹(以“/”结尾),则先去掉结束的“/”
|
||||
currentPath = currentPath.substring(0, currentPath.lastIndexOf('/'));
|
||||
}
|
||||
// 取最后一个“/”后面的名称
|
||||
int indexOfSep = currentPath.lastIndexOf('/');
|
||||
return currentPath.substring(indexOfSep + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>获取URL路径中的父路径</h2>
|
||||
* <p>
|
||||
* 该方法将解析出URL路径中所包含的逻辑父路径,例如“/foo”的逻辑父路径为“/”,“/foo/bar/”的逻辑父路径为“/foo/”,以此类推。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path URL路径,该路径应为以“/”起始的字符串。
|
||||
* @return java.lang.String 逻辑父路径,必定以“/”结尾。
|
||||
* 当路径格式不正确时(例如不以“/”起始)或者路径为“/”时,则返回空字符串("")
|
||||
*/
|
||||
public static String getParentPath(String path) {
|
||||
if (!isAvailablePath(path) || "/".equals(path)) {
|
||||
return "";
|
||||
} else {
|
||||
String currentPath = path.endsWith("/") ? path.substring(0, path.lastIndexOf('/')) : path;
|
||||
int index = currentPath.lastIndexOf('/');
|
||||
return currentPath.substring(0, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>判断是否为合法路径</h2>
|
||||
* <p>
|
||||
* 该方法用于判断一个URL路径是否符合:1,不为null;2,不为空串;3,以“/”起始。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path URL路径
|
||||
* @return boolean 判断结果,若满足条件,则返回true。
|
||||
*/
|
||||
private static boolean isAvailablePath(String path) {
|
||||
return (path != null && !path.isEmpty() && path.startsWith("/"));
|
||||
}
|
||||
|
||||
}
|
194
src/main/java/kohgylw/kiftd/server/webdav/url/URLEncoder.java
Normal file
194
src/main/java/kohgylw/kiftd/server/webdav/url/URLEncoder.java
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package kohgylw.kiftd.server.webdav.url;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
*
|
||||
* This class is very similar to the java.net.URLEncoder class.
|
||||
*
|
||||
* Unfortunately, with java.net.URLEncoder there is no way to specify to the
|
||||
* java.net.URLEncoder which characters should NOT be encoded.
|
||||
*
|
||||
* This code was moved from MyDefaultServlet.java
|
||||
*
|
||||
* @author Craig R. McClanahan
|
||||
* @author Remy Maucherat
|
||||
*/
|
||||
public final class URLEncoder implements Cloneable {
|
||||
|
||||
private static final char[] hexadecimal =
|
||||
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||
|
||||
public static final URLEncoder DEFAULT = new URLEncoder();
|
||||
public static final URLEncoder QUERY = new URLEncoder();
|
||||
|
||||
static {
|
||||
/*
|
||||
* Encoder for URI paths, so from the spec:
|
||||
*
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
*
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
*
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
*/
|
||||
// ALPHA and DIGIT are always treated as safe characters
|
||||
// Add the remaining unreserved characters
|
||||
DEFAULT.addSafeCharacter('-');
|
||||
DEFAULT.addSafeCharacter('.');
|
||||
DEFAULT.addSafeCharacter('_');
|
||||
DEFAULT.addSafeCharacter('~');
|
||||
// Add the sub-delims
|
||||
DEFAULT.addSafeCharacter('!');
|
||||
DEFAULT.addSafeCharacter('$');
|
||||
DEFAULT.addSafeCharacter('&');
|
||||
DEFAULT.addSafeCharacter('\'');
|
||||
DEFAULT.addSafeCharacter('(');
|
||||
DEFAULT.addSafeCharacter(')');
|
||||
DEFAULT.addSafeCharacter('*');
|
||||
DEFAULT.addSafeCharacter('+');
|
||||
DEFAULT.addSafeCharacter(',');
|
||||
DEFAULT.addSafeCharacter(';');
|
||||
DEFAULT.addSafeCharacter('=');
|
||||
// Add the remaining literals
|
||||
DEFAULT.addSafeCharacter(':');
|
||||
DEFAULT.addSafeCharacter('@');
|
||||
// Add '/' so it isn't encoded when we encode a path
|
||||
DEFAULT.addSafeCharacter('/');
|
||||
|
||||
/*
|
||||
* Encoder for query strings
|
||||
* https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm
|
||||
* 0x20 ' ' -> '+'
|
||||
* 0x2A, 0x2D, 0x2E, 0x30 to 0x39, 0x41 to 0x5A, 0x5F, 0x61 to 0x7A as-is
|
||||
* '*', '-', '.', '0' to '9', 'A' to 'Z', '_', 'a' to 'z'
|
||||
* Also '=' and '&' are not encoded
|
||||
* Everything else %nn encoded
|
||||
*/
|
||||
// Special encoding for space
|
||||
QUERY.setEncodeSpaceAsPlus(true);
|
||||
// Alpha and digit are safe by default
|
||||
// Add the other permitted characters
|
||||
QUERY.addSafeCharacter('*');
|
||||
QUERY.addSafeCharacter('-');
|
||||
QUERY.addSafeCharacter('.');
|
||||
QUERY.addSafeCharacter('_');
|
||||
QUERY.addSafeCharacter('=');
|
||||
QUERY.addSafeCharacter('&');
|
||||
}
|
||||
|
||||
//Array containing the safe characters set.
|
||||
private final BitSet safeCharacters;
|
||||
|
||||
private boolean encodeSpaceAsPlus = false;
|
||||
|
||||
|
||||
public URLEncoder() {
|
||||
this(new BitSet(256));
|
||||
|
||||
for (char i = 'a'; i <= 'z'; i++) {
|
||||
addSafeCharacter(i);
|
||||
}
|
||||
for (char i = 'A'; i <= 'Z'; i++) {
|
||||
addSafeCharacter(i);
|
||||
}
|
||||
for (char i = '0'; i <= '9'; i++) {
|
||||
addSafeCharacter(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private URLEncoder(BitSet safeCharacters) {
|
||||
this.safeCharacters = safeCharacters;
|
||||
}
|
||||
|
||||
|
||||
public void addSafeCharacter(char c) {
|
||||
safeCharacters.set(c);
|
||||
}
|
||||
|
||||
|
||||
public void removeSafeCharacter(char c) {
|
||||
safeCharacters.clear(c);
|
||||
}
|
||||
|
||||
|
||||
public void setEncodeSpaceAsPlus(boolean encodeSpaceAsPlus) {
|
||||
this.encodeSpaceAsPlus = encodeSpaceAsPlus;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* URL encodes the provided path using the given character set.
|
||||
*
|
||||
* @param path The path to encode
|
||||
* @param charset The character set to use to convert the path to bytes
|
||||
*
|
||||
* @return The encoded path
|
||||
*/
|
||||
public String encode(String path, Charset charset) {
|
||||
|
||||
int maxBytesPerChar = 10;
|
||||
StringBuilder rewrittenPath = new StringBuilder(path.length());
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
|
||||
OutputStreamWriter writer = new OutputStreamWriter(buf, charset);
|
||||
|
||||
for (int i = 0; i < path.length(); i++) {
|
||||
int c = path.charAt(i);
|
||||
if (safeCharacters.get(c)) {
|
||||
rewrittenPath.append((char)c);
|
||||
} else if (encodeSpaceAsPlus && c == ' ') {
|
||||
rewrittenPath.append('+');
|
||||
} else {
|
||||
// convert to external encoding before hex conversion
|
||||
try {
|
||||
writer.write((char)c);
|
||||
writer.flush();
|
||||
} catch(IOException e) {
|
||||
buf.reset();
|
||||
continue;
|
||||
}
|
||||
byte[] ba = buf.toByteArray();
|
||||
for (byte toEncode : ba) {
|
||||
// Converting each byte in the buffer
|
||||
rewrittenPath.append('%');
|
||||
int low = toEncode & 0x0f;
|
||||
int high = (toEncode & 0xf0) >> 4;
|
||||
rewrittenPath.append(hexadecimal[high]);
|
||||
rewrittenPath.append(hexadecimal[low]);
|
||||
}
|
||||
buf.reset();
|
||||
}
|
||||
}
|
||||
return rewrittenPath.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
URLEncoder result = new URLEncoder((BitSet) safeCharacters.clone());
|
||||
result.setEncodeSpaceAsPlus(encodeSpaceAsPlus);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,265 @@
|
||||
package kohgylw.kiftd.server.webdav.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import kohgylw.kiftd.server.enumeration.AccountAuth;
|
||||
import kohgylw.kiftd.server.mapper.FolderMapper;
|
||||
import kohgylw.kiftd.server.mapper.NodeMapper;
|
||||
import kohgylw.kiftd.server.model.Folder;
|
||||
import kohgylw.kiftd.server.model.Node;
|
||||
import kohgylw.kiftd.server.util.ConfigureReader;
|
||||
import kohgylw.kiftd.server.util.FileBlockUtil;
|
||||
import kohgylw.kiftd.server.util.FileNodeUtil;
|
||||
import kohgylw.kiftd.server.util.FolderUtil;
|
||||
import kohgylw.kiftd.server.util.LogUtil;
|
||||
import kohgylw.kiftd.server.util.ServerTimeUtil;
|
||||
import kohgylw.kiftd.server.util.TextFormateUtil;
|
||||
import kohgylw.kiftd.server.webdav.exception.UnAuthorizedException;
|
||||
import kohgylw.kiftd.server.webdav.pojo.KiftdWebDAVResource;
|
||||
import kohgylw.kiftd.server.webdav.url.HttpPathUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>kiftd虚拟文件系统资源操作工具</h2>
|
||||
* <p>
|
||||
* 该工具用于实现基于逻辑路径的各类操作。例如根据逻辑路径获取文件、文件夹以及封装为KiftdResource的资源对象等。 具体功能详见其中各个方法的注释。
|
||||
* 该工具使用Spring IOC容器进行管理。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @version 1.0
|
||||
*/
|
||||
@Component
|
||||
public class KiftdWebDAVResourcesUtil {
|
||||
|
||||
@Resource
|
||||
private FolderMapper fm;
|
||||
@Resource
|
||||
private NodeMapper nm;
|
||||
@Resource
|
||||
private FileBlockUtil fbu;
|
||||
@Resource
|
||||
private FolderUtil fu;
|
||||
@Resource
|
||||
private LogUtil lu;
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>根据逻辑路径获取一个文件夹</h2>
|
||||
* <p>
|
||||
* 该方法用于根据逻辑路径获得一个文件夹对象,当逻辑路径正确时,返回其对应的文件夹,否则返回null。
|
||||
* 注意,此方法在性能上相较于直接根据ID获取文件夹对象要差。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path 逻辑路径,必须以“/”起始。例如“/”、“/foo/”或“/foo/bar”均合法。
|
||||
* @return kohgylw.kiftd.server.model.Folder
|
||||
* 逻辑路径对应的文件夹对象。如果文件夹不存在或逻辑路径不合法,则返回null
|
||||
*/
|
||||
public Folder getFolderByPath(String path) {
|
||||
if (path != null && path.startsWith("/")) {
|
||||
// 如果以“/”结尾且不为“/”,则去掉最后的“/”
|
||||
String currentPath = path.endsWith("/") && path.length() > 1 ? path.substring(0, path.lastIndexOf('/'))
|
||||
: path;
|
||||
currentPath = currentPath.substring(1);
|
||||
// 从根目录开始按照声明的逻辑路径逐级搜索文件夹
|
||||
String[] folders = currentPath.split("/");
|
||||
Folder currentFolder = fm.queryById("root");
|
||||
for (String f : folders) {
|
||||
if (!f.isEmpty()) {
|
||||
Map<String, String> queryMap = new HashMap<>();
|
||||
queryMap.put("parentId", currentFolder.getFolderId());
|
||||
queryMap.put("folderName", f);
|
||||
currentFolder = fm.queryByParentIdAndFolderName(queryMap);
|
||||
if (currentFolder == null) {
|
||||
// 如果找到某一级就不存在了,则视为找不到该文件夹
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果能顺利找到,则返回此文件夹对象
|
||||
return currentFolder;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>根据逻辑路径获取一个文件</h2>
|
||||
* <p>
|
||||
* 该方法用于根据逻辑路径获得一个文件节点对象,当逻辑路径正确时,返回其对应的文件节点,否则返回null。
|
||||
* 注意,此方法在性能上相较于直接根据ID获取文件节点对象要差。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path 逻辑路径,必须以“/”起始,但不能以“/”结尾。例如“/foo”或“/foo/bar”均合法。
|
||||
* @return kohgylw.kiftd.server.model.Node 逻辑路径对应的文件节点对象。如果文件不存在或逻辑路径不合法,则返回null
|
||||
*/
|
||||
public Node getNodeByPath(String path) {
|
||||
if (path != null && path.startsWith("/")) {
|
||||
// 首先,根据文件的父路径获取其所在的文件夹
|
||||
String parentPath = path.substring(0, path.lastIndexOf('/') + 1);
|
||||
Folder parentFolder = getFolderByPath(parentPath);
|
||||
if (parentFolder != null) {
|
||||
// 之后,再在这个文件夹中查找到文件
|
||||
String fileName = path.substring(path.lastIndexOf('/') + 1);
|
||||
try {
|
||||
Node file = nm.queryByParentFolderId(parentFolder.getFolderId()).parallelStream()
|
||||
.filter((e) -> e.getFileName().equals(fileName)).findAny().get();
|
||||
return file;
|
||||
} catch (NoSuchElementException e2) {
|
||||
// 如果没找到则说明逻辑路径有错,返回null即可
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>根据逻辑路径获取资源对象</h2>
|
||||
* <p>
|
||||
* 该方法通过逻辑路径返回一个封装好的资源对象,该资源对象是逻辑路径对应的存储在kiftd的一个虚拟文件。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path 逻辑路径,必须以“/”起始。例如“/foo”或“/foo/bar/”均合法。
|
||||
* @return kohgylw.kiftd.server.webdav.pojo.KiftdWebDAVResource 资源对象,封装了kiftd虚拟文件系统中文件或文件夹。
|
||||
* 该方法永远不会返回null,当路径不正确时,将会返回一个不存在的WebDAVResource对象。
|
||||
*/
|
||||
public KiftdWebDAVResource getResource(String path) {
|
||||
if (path != null && !path.endsWith("/")) {
|
||||
// 从文件(或文件夹)中获取资源对象,Windows客户端在请求文件夹时并不会以“/”结尾,因此无法判定是否为文件夹
|
||||
kohgylw.kiftd.server.model.Node node = getNodeByPath(path);
|
||||
if (node != null) {
|
||||
// 如果该逻辑路径确实对应一个文件,则返回文件节点的资源对象
|
||||
return new KiftdWebDAVResource(path, node, fbu.getFileFromBlocks(node));
|
||||
}
|
||||
}
|
||||
// 否则尝试获取文件夹的资源对象
|
||||
Folder folder = getFolderByPath(path);
|
||||
return new KiftdWebDAVResource(path, folder);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>获取指定逻辑路径下全部文件和文件夹的名称</h2>
|
||||
* <p>
|
||||
* 该方法能够获取指定逻辑路径(应对应一个文件夹)下所有允许指定账户访问的文件和文件夹名称,并以字符串数组的形式返回。 其中,文件夹的名称会以“/”结尾。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path 逻辑路径,必须以“/”起始,应当对应一个文件夹。
|
||||
* @return java.lang.String[] 指定逻辑路径下所有允许指定用户访问的文件和文件夹名称数组。其中文件夹名称会以“/”结尾。
|
||||
* 若指定逻辑路径未指向一个合法的文件夹或该文件夹不允许指定用户访问,则会返回一个长度为0的数组。
|
||||
*/
|
||||
public String[] list(String path, String account) {
|
||||
Folder f = getFolderByPath(path);
|
||||
// 这个文件夹存在么?
|
||||
if (f != null) {
|
||||
// 如果存在,它本身是否可以被访问?
|
||||
if (ConfigureReader.instance().accessFolder(f, account)) {
|
||||
// 如果可以,那么它之下有多少文件夹可以被访问?
|
||||
String[] folders = fm.queryByParentId(f.getFolderId()).parallelStream()
|
||||
.filter((e) -> ConfigureReader.instance().accessFolder(e, account))
|
||||
.map((e) -> e.getFolderName() + "/").toArray(String[]::new);
|
||||
String[] files = nm.queryByParentFolderId(f.getFolderId()).parallelStream().map((e) -> e.getFileName())
|
||||
.toArray(String[]::new);
|
||||
String[] result = new String[folders.length + files.length];
|
||||
System.arraycopy(folders, 0, result, 0, folders.length);
|
||||
System.arraycopy(files, 0, result, folders.length, files.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>在指定逻辑路径上创建新文件夹</h2>
|
||||
* <p>
|
||||
* 该方法将尝试在指定逻辑路径上创建一个新的文件夹,其访问限制将设置为默认等级(父级文件夹允许的最高访问等级)。
|
||||
* 此方法同时还会记录创建文件夹日志(如果创建成功的话)。
|
||||
* </p>
|
||||
*
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param path 逻辑路径,必须以“/”起始,例如“/foo”或“/bar/”均合法。
|
||||
* @param account 操作账户,可以传入null(匿名访问者)或合法的账户名称。
|
||||
* @return kohgylw.kiftd.server.model.Folder 如果创建成功则返回文件夹对象,否则返回null。
|
||||
* @throws UnAuthorizedException 操作未被授权。
|
||||
*/
|
||||
public Folder mkdir(String path, String account) throws UnAuthorizedException {
|
||||
final String folderName = HttpPathUtil.getResourceName(path);// 逻辑路径中文件夹的预期名称
|
||||
// 检查逻辑路径是否合法
|
||||
if (folderName == null || folderName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// 检查名称是否合法
|
||||
if (!TextFormateUtil.instance().matcherFolderName(folderName) || folderName.indexOf(".") == 0) {
|
||||
return null;
|
||||
}
|
||||
// 判断父文件夹是否存在
|
||||
final Folder parentFolder = getFolderByPath(HttpPathUtil.getParentPath(path));
|
||||
if (parentFolder == null) {
|
||||
return null;
|
||||
}
|
||||
// 检查父文件夹否能被账户访问
|
||||
if (!ConfigureReader.instance().accessFolder(parentFolder, account)) {
|
||||
throw new UnAuthorizedException();
|
||||
}
|
||||
final String parentId = parentFolder.getFolderId();
|
||||
// 判断账户在父文件夹内是否有创建文件夹权限
|
||||
if (!ConfigureReader.instance().authorized(account, AccountAuth.CREATE_NEW_FOLDER,
|
||||
fu.getAllFoldersId(parentId))) {
|
||||
throw new UnAuthorizedException();
|
||||
}
|
||||
// 判断父文件夹中的文件数量是否超限
|
||||
if (fm.countByParentId(parentFolder.getFolderId()) >= FileNodeUtil.MAXIMUM_NUM_OF_SINGLE_FOLDER) {
|
||||
return null;
|
||||
}
|
||||
// 尝试创建新文件夹
|
||||
Folder f = new Folder();
|
||||
if (fm.queryByParentId(parentId).parallelStream().anyMatch((e) -> e.getFolderName().equals(folderName))) {
|
||||
return null;// 若有重名文件夹,则无法继续创建
|
||||
} else {
|
||||
f.setFolderName(folderName);
|
||||
}
|
||||
f.setFolderConstraint(parentFolder.getFolderConstraint());
|
||||
f.setFolderId(UUID.randomUUID().toString());
|
||||
f.setFolderCreationDate(ServerTimeUtil.accurateToDay());
|
||||
if (account != null) {
|
||||
f.setFolderCreator(account);
|
||||
} else {
|
||||
f.setFolderCreator("匿名用户");
|
||||
}
|
||||
f.setFolderParent(parentId);
|
||||
int i = 0;
|
||||
while (true) {
|
||||
try {
|
||||
final int r = this.fm.insertNewFolder(f);
|
||||
if (r > 0) {
|
||||
if (fu.isValidFolder(f)) {
|
||||
return f;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
f.setFolderId(UUID.randomUUID().toString());
|
||||
i++;
|
||||
}
|
||||
if (i >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,51 +1,49 @@
|
||||
Manifest-Version: 1.0
|
||||
Implementation-Title: kiftd
|
||||
Implementation-Version: 1.0.35-RELEASE
|
||||
Built-By: kohgylw
|
||||
Build-Jdk: 15.0.2
|
||||
Class-Path: libs/spring-boot-starter-web-2.0.2.RELEASE.jar libs/spring-b
|
||||
oot-starter-2.0.2.RELEASE.jar libs/spring-boot-2.0.2.RELEASE.jar libs/s
|
||||
pring-boot-autoconfigure-2.0.2.RELEASE.jar libs/spring-boot-starter-log
|
||||
ging-2.0.2.RELEASE.jar libs/logback-classic-1.2.3.jar libs/logback-core
|
||||
-1.2.3.jar libs/log4j-to-slf4j-2.10.0.jar libs/log4j-api-2.10.0.jar lib
|
||||
s/jul-to-slf4j-1.7.25.jar libs/javax.annotation-api-1.3.2.jar libs/snak
|
||||
eyaml-1.19.jar libs/spring-boot-starter-json-2.0.2.RELEASE.jar libs/jac
|
||||
kson-databind-2.9.5.jar libs/jackson-annotations-2.9.0.jar libs/jackson
|
||||
-core-2.9.5.jar libs/jackson-datatype-jdk8-2.9.5.jar libs/jackson-datat
|
||||
ype-jsr310-2.9.5.jar libs/jackson-module-parameter-names-2.9.5.jar libs
|
||||
/spring-boot-starter-tomcat-2.0.2.RELEASE.jar libs/tomcat-embed-core-8.
|
||||
5.31.jar libs/tomcat-embed-el-8.5.31.jar libs/tomcat-embed-websocket-8.
|
||||
5.31.jar libs/hibernate-validator-6.0.9.Final.jar libs/spring-web-5.0.6
|
||||
.RELEASE.jar libs/spring-webmvc-5.0.6.RELEASE.jar libs/spring-aop-5.0.6
|
||||
.RELEASE.jar libs/spring-context-5.0.6.RELEASE.jar libs/spring-expressi
|
||||
on-5.0.6.RELEASE.jar libs/gson-2.8.4.jar libs/commons-fileupload-1.3.3.
|
||||
jar libs/commons-io-2.4.jar libs/mybatis-3.4.1.jar libs/mybatis-spring-
|
||||
1.3.1.jar libs/h2-1.4.197.jar libs/spring-jdbc-5.0.6.RELEASE.jar libs/s
|
||||
pring-beans-5.0.6.RELEASE.jar libs/spring-core-5.0.6.RELEASE.jar libs/s
|
||||
pring-jcl-5.0.6.RELEASE.jar libs/spring-tx-5.0.6.RELEASE.jar libs/mysql
|
||||
-connector-java-8.0.16.jar libs/protobuf-java-3.6.1.jar libs/zt-zip-1.1
|
||||
3.jar libs/slf4j-api-1.7.25.jar libs/thumbnailator-0.4.8.jar libs/image
|
||||
io-jpeg-3.3.2.jar libs/imageio-core-3.3.2.jar libs/imageio-metadata-3.3
|
||||
.2.jar libs/common-lang-3.3.2.jar libs/common-io-3.3.2.jar libs/common-
|
||||
image-3.3.2.jar libs/imageio-tiff-3.3.2.jar libs/freemarker-2.3.28.jar
|
||||
libs/itext-2.1.7.jar libs/bcmail-jdk14-138.jar libs/bcprov-jdk14-138.ja
|
||||
r libs/bctsp-jdk14-1.38.jar libs/bcprov-jdk14-1.38.jar libs/bcmail-jdk1
|
||||
4-1.38.jar libs/org.apache.poi.xwpf.converter.pdf-1.0.6.jar libs/org.ap
|
||||
ache.poi.xwpf.converter.core-1.0.6.jar libs/poi-ooxml-3.10-FINAL.jar li
|
||||
bs/dom4j-1.6.1.jar libs/xml-apis-1.4.01.jar libs/ooxml-schemas-1.1.jar
|
||||
libs/xmlbeans-2.3.0.jar libs/stax-api-1.0.1.jar libs/fr.opensagres.xdoc
|
||||
report.itext.extension-1.0.6.jar libs/jchardet-1.0.jar libs/poi-scratch
|
||||
pad-3.10-FINAL.jar libs/poi-3.10-FINAL.jar libs/jave-all-deps-3.3.1.jar
|
||||
libs/jave-core-3.3.1.jar libs/jave-nativebin-win32-3.3.1.jar libs/jave
|
||||
-nativebin-win64-3.3.1.jar libs/jave-nativebin-linux32-3.3.1.jar libs/j
|
||||
ave-nativebin-linux64-3.3.1.jar libs/jave-nativebin-osx64-3.3.1.jar lib
|
||||
s/jave-nativebin-osxm1-3.3.1.jar libs/jave-nativebin-linux-arm32-3.3.1.
|
||||
jar libs/jave-nativebin-linux-arm64-3.3.1.jar libs/commons-codec-1.11.j
|
||||
ar libs/flexmark-0.50.44.jar libs/flexmark-util-0.50.44.jar
|
||||
Implementation-Title: kiftd
|
||||
Implementation-Version: 1.0.36-SNAPSHOT
|
||||
Implementation-Vendor-Id: kohgylw
|
||||
Class-Path: libs/spring-boot-starter-web-2.0.2.RELEASE.jar libs/spring
|
||||
-boot-starter-2.0.2.RELEASE.jar libs/spring-boot-2.0.2.RELEASE.jar li
|
||||
bs/spring-boot-autoconfigure-2.0.2.RELEASE.jar libs/spring-boot-start
|
||||
er-logging-2.0.2.RELEASE.jar libs/logback-classic-1.2.3.jar libs/logb
|
||||
ack-core-1.2.3.jar libs/log4j-to-slf4j-2.10.0.jar libs/log4j-api-2.10
|
||||
.0.jar libs/jul-to-slf4j-1.7.25.jar libs/javax.annotation-api-1.3.2.j
|
||||
ar libs/snakeyaml-1.19.jar libs/spring-boot-starter-json-2.0.2.RELEAS
|
||||
E.jar libs/jackson-databind-2.9.5.jar libs/jackson-annotations-2.9.0.
|
||||
jar libs/jackson-core-2.9.5.jar libs/jackson-datatype-jdk8-2.9.5.jar
|
||||
libs/jackson-datatype-jsr310-2.9.5.jar libs/jackson-module-parameter-
|
||||
names-2.9.5.jar libs/spring-boot-starter-tomcat-2.0.2.RELEASE.jar lib
|
||||
s/tomcat-embed-core-8.5.31.jar libs/tomcat-embed-el-8.5.31.jar libs/t
|
||||
omcat-embed-websocket-8.5.31.jar libs/hibernate-validator-6.0.9.Final
|
||||
.jar libs/validation-api-2.0.1.Final.jar libs/jboss-logging-3.3.2.Fin
|
||||
al.jar libs/classmate-1.3.4.jar libs/spring-web-5.0.6.RELEASE.jar lib
|
||||
s/spring-webmvc-5.0.6.RELEASE.jar libs/spring-aop-5.0.6.RELEASE.jar l
|
||||
ibs/spring-context-5.0.6.RELEASE.jar libs/spring-expression-5.0.6.REL
|
||||
EASE.jar libs/gson-2.8.4.jar libs/commons-fileupload-1.3.3.jar libs/c
|
||||
ommons-io-2.4.jar libs/mybatis-3.4.1.jar libs/mybatis-spring-1.3.1.ja
|
||||
r libs/h2-1.4.197.jar libs/spring-jdbc-5.0.6.RELEASE.jar libs/spring-
|
||||
beans-5.0.6.RELEASE.jar libs/spring-core-5.0.6.RELEASE.jar libs/sprin
|
||||
g-jcl-5.0.6.RELEASE.jar libs/spring-tx-5.0.6.RELEASE.jar libs/mysql-c
|
||||
onnector-java-8.0.16.jar libs/protobuf-java-3.6.1.jar libs/zt-zip-1.1
|
||||
3.jar libs/slf4j-api-1.7.25.jar libs/thumbnailator-0.4.8.jar libs/ima
|
||||
geio-jpeg-3.3.2.jar libs/imageio-core-3.3.2.jar libs/imageio-metadata
|
||||
-3.3.2.jar libs/common-lang-3.3.2.jar libs/common-io-3.3.2.jar libs/c
|
||||
ommon-image-3.3.2.jar libs/imageio-tiff-3.3.2.jar libs/freemarker-2.3
|
||||
.28.jar libs/itext-2.1.7.jar libs/bcmail-jdk14-138.jar libs/bcprov-jd
|
||||
k14-138.jar libs/bctsp-jdk14-1.38.jar libs/bcprov-jdk14-1.38.jar libs
|
||||
/bcmail-jdk14-1.38.jar libs/org.apache.poi.xwpf.converter.pdf-1.0.6.j
|
||||
ar libs/org.apache.poi.xwpf.converter.core-1.0.6.jar libs/poi-ooxml-3
|
||||
.10-FINAL.jar libs/dom4j-1.6.1.jar libs/xml-apis-1.4.01.jar libs/ooxm
|
||||
l-schemas-1.1.jar libs/xmlbeans-2.3.0.jar libs/stax-api-1.0.1.jar lib
|
||||
s/fr.opensagres.xdocreport.itext.extension-1.0.6.jar libs/jchardet-1.
|
||||
0.jar libs/poi-scratchpad-3.10-FINAL.jar libs/poi-3.10-FINAL.jar libs
|
||||
/jave-all-deps-2.5.0.jar libs/jave-core-2.5.0.jar libs/commons-loggin
|
||||
g-api-1.1.jar libs/jave-nativebin-win32-2.5.0.jar libs/jave-nativebin
|
||||
-win64-2.5.0.jar libs/jave-nativebin-linux32-2.5.0.jar libs/jave-nati
|
||||
vebin-linux64-2.5.0.jar libs/jave-nativebin-osx64-2.5.0.jar libs/comm
|
||||
ons-codec-1.11.jar libs/flexmark-0.50.44.jar libs/flexmark-util-0.50.
|
||||
44.jar
|
||||
Build-Jdk: 1.8.0_131
|
||||
Implementation-URL: https://kohgylw.gitee.io
|
||||
Created-By: Maven Integration for Eclipse
|
||||
Main-Class: kohgylw.kiftd.mc.MC
|
||||
Created-By: Maven Integration for Eclipse
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#Generated by Maven Integration for Eclipse
|
||||
#Mon Jul 06 16:32:47 CST 2020
|
||||
version=1.0.35-RELEASE
|
||||
groupId=kohgylw
|
||||
m2e.projectName=kiftd
|
||||
#Thu Jun 02 06:20:06 CST 2022
|
||||
m2e.projectLocation=/Users/kohgylw/program/java-workspace/kiftd
|
||||
m2e.projectName=kiftd
|
||||
groupId=kohgylw
|
||||
artifactId=kiftd
|
||||
version=1.0.36-SNAPSHOT
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<groupId>kohgylw</groupId>
|
||||
<artifactId>kiftd</artifactId>
|
||||
<version>1.0.35-RELEASE</version>
|
||||
<version>1.0.36-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>kiftd</name>
|
||||
@ -150,7 +150,7 @@
|
||||
<dependency>
|
||||
<groupId>ws.schild</groupId>
|
||||
<artifactId>jave-all-deps</artifactId>
|
||||
<version>2.5.0</version>
|
||||
<version>3.3.1</version>
|
||||
</dependency>
|
||||
<!-- end JAVE -->
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
target/classes/kohgylw/kiftd/server/webdav/WebdavStatus.class
Normal file
BIN
target/classes/kohgylw/kiftd/server/webdav/WebdavStatus.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
target/classes/kohgylw/kiftd/server/webdav/dom/DOMWriter.class
Normal file
BIN
target/classes/kohgylw/kiftd/server/webdav/dom/DOMWriter.class
Normal file
Binary file not shown.
BIN
target/classes/kohgylw/kiftd/server/webdav/dom/Escape.class
Normal file
BIN
target/classes/kohgylw/kiftd/server/webdav/dom/Escape.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
target/classes/kohgylw/kiftd/server/webdav/url/URLEncoder.class
Normal file
BIN
target/classes/kohgylw/kiftd/server/webdav/url/URLEncoder.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user