移除检查任务信息主动抛出的异常

pull/617/head
laoyuyu 5 years ago
parent fb0b95bd07
commit 2b0fcbb98f
  1. 12
      Aria/src/main/java/com/arialyy/aria/core/Aria.java
  2. 143
      Aria/src/main/java/com/arialyy/aria/core/AriaManager.java
  3. 18
      Aria/src/main/java/com/arialyy/aria/core/WidgetLiftManager.java
  4. 8
      Aria/src/main/java/com/arialyy/aria/core/download/CheckDEntityUtil.java
  5. 3
      Aria/src/main/java/com/arialyy/aria/core/download/CheckDGEntityUtil.java
  6. 3
      Aria/src/main/java/com/arialyy/aria/core/download/CheckFtpDirEntityUtil.java
  7. 40
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java
  8. 14
      Aria/src/main/java/com/arialyy/aria/core/download/target/AbsGroupConfigHandler.java
  9. 12
      Aria/src/main/java/com/arialyy/aria/core/download/target/DNormalConfigHandler.java
  10. 3
      Aria/src/main/java/com/arialyy/aria/core/download/target/FtpBuilderTarget.java
  11. 2
      Aria/src/main/java/com/arialyy/aria/core/scheduler/FailureTaskHandler.java
  12. 40
      Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskSchedulers.java
  13. 5
      Aria/src/main/java/com/arialyy/aria/core/upload/CheckUEntityUtil.java
  14. 6
      Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java
  15. 18
      Aria/src/main/java/com/arialyy/aria/core/upload/target/UNormalConfigHandler.java
  16. 2
      HttpComponent/src/main/java/com/arialyy/aria/http/HttpFileInfoThread.java
  17. 2
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8InfoThread.java
  18. 7
      PublicComponent/src/main/java/com/arialyy/aria/core/common/ErrorCode.java
  19. 14
      PublicComponent/src/main/java/com/arialyy/aria/core/download/AbsGroupTaskWrapper.java
  20. 4
      PublicComponent/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java
  21. 1
      PublicComponent/src/main/java/com/arialyy/aria/core/task/DownloadGroupTask.java
  22. 12
      PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/AbsTaskWrapper.java
  23. 2
      PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/ITaskWrapper.java
  24. 2
      PublicComponent/src/main/java/com/arialyy/aria/orm/ActionPolicy.java
  25. 2
      PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateCommon.java
  26. 105
      PublicComponent/src/main/java/com/arialyy/aria/util/CheckUtil.java
  27. 1
      app/src/main/java/com/arialyy/simple/MainActivity.java
  28. 6
      app/src/main/java/com/arialyy/simple/core/download/FtpDownloadActivity.java
  29. 2
      app/src/main/java/com/arialyy/simple/core/download/fragment/DownloadFragment.java
  30. 1
      app/src/main/java/com/arialyy/simple/core/download/fragment/FragmentActivity.java

@ -24,8 +24,6 @@ import android.app.Service;
import android.content.Context;
import android.os.Build;
import android.widget.PopupWindow;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import com.arialyy.aria.core.download.DownloadReceiver;
import com.arialyy.aria.core.upload.UploadReceiver;
import com.arialyy.aria.util.ALog;
@ -136,14 +134,8 @@ import com.arialyy.aria.util.ALog;
return (Service) obj;
} else if (obj instanceof Activity) {
return (Activity) obj;
} else if (obj instanceof DialogFragment) {
return ((DialogFragment) obj).getContext();
} else if (obj instanceof android.app.DialogFragment) {
return ((android.app.DialogFragment) obj).getActivity();
} else if (obj instanceof Fragment) {
return ((Fragment) obj).getContext();
} else if (obj instanceof android.app.Fragment) {
return ((android.app.Fragment) obj).getActivity();
} else if (AriaManager.isFragment(obj.getClass())) {
return AriaManager.getFragmentActivity(obj);
} else if (obj instanceof Dialog) {
return ((Dialog) obj).getContext();
} else if (obj instanceof PopupWindow) {

@ -21,17 +21,11 @@ import android.app.Activity;
import android.app.Application;
import android.app.Dialog;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.PopupWindow;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import com.arialyy.aria.core.command.CommandManager;
import com.arialyy.aria.core.common.QueueMod;
import com.arialyy.aria.core.config.AppConfig;
@ -53,6 +47,8 @@ import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.AriaCrashHandler;
import com.arialyy.aria.util.RecordUtil;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@ -60,13 +56,18 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by lyy on 2016/12/1. https://github.com/AriaLyy/Aria Aria管理器,任务操作在这里执行
* Created by lyy on 2016/12/1. https://github.com/AriaLyy/Aria
* Aria管理器任务操作在这里执行
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public class AriaManager {
private static final String TAG = "AriaManager";
private static final Object LOCK = new Object();
/**
* androidandroidxsupport的fragmentdialogFragment类名
*/
private static List<String> mFragmentClassName = new ArrayList<>();
private static List<String> mDialogFragmentClassName = new ArrayList<>();
@SuppressLint("StaticFieldLeak") private static volatile AriaManager INSTANCE = null;
private Map<String, AbsReceiver> mReceivers = new ConcurrentHashMap<>();
@ -79,6 +80,19 @@ import java.util.concurrent.ConcurrentHashMap;
private DelegateWrapper mDbWrapper;
private AriaConfig mConfig;
static {
mFragmentClassName.add("androidx.fragment.app.Fragment");
mFragmentClassName.add("androidx.fragment.app.DialogFragment");
mFragmentClassName.add("android.app.Fragment");
mFragmentClassName.add("android.app.DialogFragment");
mFragmentClassName.add("android.support.v4.app.Fragment");
mFragmentClassName.add("android.support.v4.app.DialogFragment");
mDialogFragmentClassName.add("androidx.fragment.app.DialogFragment");
mDialogFragmentClassName.add("android.app.DialogFragment");
mDialogFragmentClassName.add("android.support.v4.app.DialogFragment");
}
private AriaManager(Context context) {
APP = context.getApplicationContext();
}
@ -114,8 +128,6 @@ import java.util.concurrent.ConcurrentHashMap;
return APP;
}
/**
* 初始化数据库
*/
@ -166,8 +178,6 @@ import java.util.concurrent.ConcurrentHashMap;
return mAriaHandler;
}
public Map<String, AbsReceiver> getReceiver() {
return mReceivers;
}
@ -303,25 +313,12 @@ import java.util.concurrent.ConcurrentHashMap;
needRmReceiver = widgetLiftManager.handleDialogLift((Dialog) obj);
} else if (obj instanceof PopupWindow) {
needRmReceiver = widgetLiftManager.handlePopupWindowLift((PopupWindow) obj);
} else if (obj instanceof DialogFragment) {
needRmReceiver = widgetLiftManager.handleDialogFragmentLift((DialogFragment) obj);
} else if (obj instanceof android.app.DialogFragment) {
needRmReceiver = widgetLiftManager.handleDialogFragmentLift((android.app.DialogFragment) obj);
} else if (isDialogFragment(obj.getClass())) {
needRmReceiver = widgetLiftManager.handleDialogFragmentLift(getDialog(obj));
}
if (receiver == null) {
AbsReceiver absReceiver;
switch (type) {
case ReceiverType.DOWNLOAD:
absReceiver = new DownloadReceiver();
break;
case ReceiverType.UPLOAD:
absReceiver = new UploadReceiver();
break;
default:
absReceiver = new DownloadReceiver();
break;
}
AbsReceiver absReceiver =
type.equals(ReceiverType.DOWNLOAD) ? new DownloadReceiver() : new UploadReceiver();
absReceiver.targetName = obj.getClass().getName();
AbsReceiver.OBJ_MAP.put(absReceiver.getKey(), obj);
absReceiver.needRmListener = needRmReceiver;
@ -339,14 +336,8 @@ import java.util.concurrent.ConcurrentHashMap;
* @return {@link #createKey(String, Object)}
*/
private String getKey(@ReceiverType String type, Object obj) {
if (obj instanceof DialogFragment) {
relateSubClass(type, obj, ((DialogFragment) obj).getActivity());
} else if (obj instanceof android.app.DialogFragment) {
relateSubClass(type, obj, ((android.app.DialogFragment) obj).getActivity());
} else if (obj instanceof Fragment) {
relateSubClass(type, obj, ((Fragment) obj).getActivity());
} else if (obj instanceof android.app.Fragment) {
relateSubClass(type, obj, ((android.app.Fragment) obj).getActivity());
if (isFragment(obj.getClass())) {
relateSubClass(type, obj, getFragmentActivity(obj));
} else if (obj instanceof Dialog) {
Activity activity = ((Dialog) obj).getOwnerActivity();
if (activity != null) {
@ -361,10 +352,86 @@ import java.util.concurrent.ConcurrentHashMap;
return createKey(type, obj);
}
/**
* 获取fragment的activity
*
* @return 获取失败返回null
*/
static Activity getFragmentActivity(Object obj) {
try {
Method method = obj.getClass().getMethod("getActivity");
return (Activity) method.invoke(obj);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
/**
* 判断注解对象是否是fragment
*
* @return true 对象是fragment
*/
static boolean isFragment(Class subClazz) {
Class parentClass = subClazz.getSuperclass();
if (parentClass == null) {
return false;
} else {
String parentName = parentClass.getName();
if (mFragmentClassName.contains(parentName)) {
return true;
} else {
return isFragment(parentClass);
}
}
}
/**
* 判断对象是否是DialogFragment
*
* @return true 对象是DialogFragment
*/
private boolean isDialogFragment(Class subClazz) {
Class parentClass = subClazz.getSuperclass();
if (parentClass == null) {
return false;
} else {
String parentName = parentClass.getName();
if (mFragmentClassName.contains(parentName)) {
return true;
} else {
return isDialogFragment(parentClass);
}
}
}
/**
* 获取DialogFragment的dialog
*
* @return 获取失败返回null
*/
private Dialog getDialog(Object obj) {
try {
Method method = obj.getClass().getMethod("getDialog");
return (Dialog) method.invoke(obj);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
/**
* 关联Activity类和Fragment间的关系
*
* @param sub Frgament或dialog类
* @param sub Fragment或dialog类
* @param activity activity寄主类
*/
private void relateSubClass(@ReceiverType String type, Object sub, Activity activity) {

@ -21,7 +21,6 @@ import android.content.DialogInterface;
import android.os.Build;
import android.os.Message;
import android.widget.PopupWindow;
import androidx.fragment.app.DialogFragment;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.lang.reflect.Field;
@ -35,22 +34,9 @@ final class WidgetLiftManager {
/**
* 处理DialogFragment事件
*
* @param dialogFragment {@link android.app.DialogFragment}
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB) boolean handleDialogFragmentLift(
android.app.DialogFragment dialogFragment) {
return handleDialogLift(dialogFragment.getDialog());
}
/**
* 处理DialogFragment事件
*
* @param dialogFragment {@link DialogFragment}
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB) boolean handleDialogFragmentLift(
DialogFragment dialogFragment) {
return handleDialogLift(dialogFragment.getDialog());
@TargetApi(Build.VERSION_CODES.HONEYCOMB) boolean handleDialogFragmentLift(Dialog dialog) {
return handleDialogLift(dialog);
}
/**

@ -16,6 +16,7 @@
package com.arialyy.aria.core.download;
import android.text.TextUtils;
import com.arialyy.aria.core.common.ErrorCode;
import com.arialyy.aria.core.inf.ICheckEntityUtil;
import com.arialyy.aria.core.inf.IOptionConstant;
import com.arialyy.aria.core.inf.IRecordHandler;
@ -24,6 +25,7 @@ import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.RecordUtil;
import java.io.File;
@ -31,7 +33,7 @@ import java.io.File;
* 检查下载任务实体
*/
public class CheckDEntityUtil implements ICheckEntityUtil {
private final String TAG = "CheckDLoadEntity";
private final String TAG = CommonUtil.getClassName(getClass());
private DTaskWrapper mWrapper;
private DownloadEntity mEntity;
@ -47,7 +49,7 @@ public class CheckDEntityUtil implements ICheckEntityUtil {
@Override
public boolean checkEntity() {
if (mWrapper.getErrorEvent() != null) {
ALog.e(TAG, mWrapper.getErrorEvent().errorMsg);
ALog.e(TAG, String.format("下载失败,%s", mWrapper.getErrorEvent().errorMsg));
return false;
}
@ -179,7 +181,7 @@ public class CheckDEntityUtil implements ICheckEntityUtil {
if (TextUtils.isEmpty(url)) {
ALog.e(TAG, "下载失败,url为null");
return false;
} else if (!CheckUtil.checkUrlNotThrow(url)) {
} else if (!CheckUtil.checkUrl(url)) {
ALog.e(TAG, "下载失败,url【" + url + "】错误");
return false;
}

@ -16,6 +16,7 @@
package com.arialyy.aria.core.download;
import android.text.TextUtils;
import com.arialyy.aria.core.common.ErrorCode;
import com.arialyy.aria.core.common.RequestEnum;
import com.arialyy.aria.core.inf.ICheckEntityUtil;
import com.arialyy.aria.core.inf.IOptionConstant;
@ -113,7 +114,7 @@ public class CheckDGEntityUtil implements ICheckEntityUtil {
@Override
public boolean checkEntity() {
if (mWrapper.getErrorEvent() != null) {
ALog.e(TAG, mWrapper.getErrorEvent().errorMsg);
ALog.e(TAG, String.format("下载失败,%s", mWrapper.getErrorEvent().errorMsg));
return false;
}

@ -17,6 +17,7 @@ package com.arialyy.aria.core.download;
import android.text.TextUtils;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.common.ErrorCode;
import com.arialyy.aria.core.inf.ICheckEntityUtil;
import com.arialyy.aria.core.inf.IOptionConstant;
import com.arialyy.aria.orm.DbEntity;
@ -77,7 +78,7 @@ public class CheckFtpDirEntityUtil implements ICheckEntityUtil {
@Override
public boolean checkEntity() {
if (mWrapper.getErrorEvent() != null) {
ALog.e(TAG, mWrapper.getErrorEvent().errorMsg);
ALog.e(TAG, String.format("下载失败,%s", mWrapper.getErrorEvent().errorMsg));
return false;
}

@ -20,9 +20,10 @@ import androidx.annotation.NonNull;
import com.arialyy.annotations.TaskEnum;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.command.CancelAllCmd;
import com.arialyy.aria.core.command.CmdHelper;
import com.arialyy.aria.core.command.NormalCmdFactory;
import com.arialyy.aria.core.common.AbsBuilderTarget;
import com.arialyy.aria.core.common.ErrorCode;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.ProxyHelper;
import com.arialyy.aria.core.download.target.DTargetFactory;
import com.arialyy.aria.core.download.target.FtpBuilderTarget;
@ -34,15 +35,13 @@ import com.arialyy.aria.core.download.target.GroupNormalTarget;
import com.arialyy.aria.core.download.target.HttpBuilderTarget;
import com.arialyy.aria.core.download.target.HttpNormalTarget;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.inf.AbsReceiver;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.inf.ReceiverType;
import com.arialyy.aria.core.scheduler.TaskSchedulers;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.core.command.CmdHelper;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.ComponentUtil;
import com.arialyy.aria.util.DbDataHelper;
@ -77,7 +76,6 @@ public class DownloadReceiver extends AbsReceiver {
@CheckResult
public HttpBuilderTarget load(@NonNull String url) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP);
CheckUtil.checkUrlInvalidThrow(url);
return DTargetFactory.getInstance()
.generateBuilderTarget(HttpBuilderTarget.class, url);
}
@ -91,7 +89,6 @@ public class DownloadReceiver extends AbsReceiver {
@CheckResult
public HttpNormalTarget load(long taskId) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP);
CheckUtil.checkTaskId(taskId);
return DTargetFactory.getInstance()
.generateNormalTarget(HttpNormalTarget.class, taskId);
}
@ -104,7 +101,6 @@ public class DownloadReceiver extends AbsReceiver {
@CheckResult
public GroupBuilderTarget loadGroup(List<String> urls) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP);
CheckUtil.checkDownloadUrls(urls);
return DTargetFactory.getInstance().generateGroupBuilderTarget(urls);
}
@ -117,7 +113,6 @@ public class DownloadReceiver extends AbsReceiver {
@CheckResult
public GroupNormalTarget loadGroup(long taskId) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP);
CheckUtil.checkTaskId(taskId);
return DTargetFactory.getInstance()
.generateNormalTarget(GroupNormalTarget.class, taskId);
}
@ -128,7 +123,6 @@ public class DownloadReceiver extends AbsReceiver {
@CheckResult
public FtpBuilderTarget loadFtp(@NonNull String url) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP);
CheckUtil.checkUrlInvalidThrow(url);
return DTargetFactory.getInstance()
.generateBuilderTarget(FtpBuilderTarget.class, url);
}
@ -142,7 +136,6 @@ public class DownloadReceiver extends AbsReceiver {
@CheckResult
public FtpNormalTarget loadFtp(long taskId) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP);
CheckUtil.checkTaskId(taskId);
return DTargetFactory.getInstance()
.generateNormalTarget(FtpNormalTarget.class, taskId);
}
@ -153,7 +146,6 @@ public class DownloadReceiver extends AbsReceiver {
@CheckResult
public FtpDirBuilderTarget loadFtpDir(@NonNull String dirUrl) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP);
CheckUtil.checkUrlInvalidThrow(dirUrl);
return DTargetFactory.getInstance().generateDirBuilderTarget(dirUrl);
}
@ -166,7 +158,6 @@ public class DownloadReceiver extends AbsReceiver {
@CheckResult
public FtpDirNormalTarget loadFtpDir(long taskId) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP);
CheckUtil.checkTaskId(taskId);
return DTargetFactory.getInstance()
.generateNormalTarget(FtpDirNormalTarget.class, taskId);
}
@ -244,7 +235,10 @@ public class DownloadReceiver extends AbsReceiver {
* @param taskId 任务实体唯一id
*/
public DownloadEntity getDownloadEntity(long taskId) {
CheckUtil.checkTaskId(taskId);
if (taskId < 0) {
ALog.e(TAG, "taskId错误");
return null;
}
return DbEntity.findFirst(DownloadEntity.class, "rowid=?", String.valueOf(taskId));
}
@ -255,7 +249,9 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果url错误或查找不到数据则返回null
*/
public DownloadEntity getFirstDownloadEntity(String downloadUrl) {
CheckUtil.checkUrl(downloadUrl);
if (!CheckUtil.checkUrl(downloadUrl)) {
return null;
}
return DbEntity.findFirst(DownloadEntity.class, "url=? and isGroupChild='false'", downloadUrl);
}
@ -265,7 +261,9 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果url错误或查找不到数据则返回null
*/
public List<DownloadEntity> getDownloadEntity(String downloadUrl) {
CheckUtil.checkUrl(downloadUrl);
if (!CheckUtil.checkUrl(downloadUrl)) {
return null;
}
return DbEntity.findDatas(DownloadEntity.class, "url=? and isGroupChild='false'", downloadUrl);
}
@ -276,7 +274,9 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果实体不存在返回null
*/
public DownloadGroupEntity getGroupEntity(long taskId) {
CheckUtil.checkTaskId(taskId);
if (taskId < 0) {
ALog.e(TAG, "任务Id错误");
}
return DbDataHelper.getDGEntity(taskId);
}
@ -287,7 +287,9 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果实体不存在返回null
*/
public DownloadGroupEntity getGroupEntity(List<String> urls) {
CheckUtil.checkDownloadUrls(urls);
if (CheckUtil.checkDownloadUrlsIsEmpty(urls)){
return null;
}
return DbDataHelper.getDGEntity(CommonUtil.getMd5Code(urls));
}
@ -298,7 +300,9 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果实体不存在返回null
*/
public DownloadGroupEntity getFtpDirEntity(String url) {
CheckUtil.checkUrl(url);
if (!CheckUtil.checkUrl(url)) {
return null;
}
return DbDataHelper.getDGEntity(url);
}

@ -17,15 +17,17 @@ package com.arialyy.aria.core.download.target;
import android.text.TextUtils;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.common.ErrorCode;
import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.task.DownloadGroupTask;
import com.arialyy.aria.core.event.ErrorEvent;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.IConfigHandler;
import com.arialyy.aria.core.manager.SubTaskManager;
import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.core.queue.DGroupTaskQueue;
import com.arialyy.aria.core.task.DownloadGroupTask;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.CommonUtil;
@ -44,9 +46,15 @@ abstract class AbsGroupConfigHandler<TARGET extends AbsTarget> implements IConfi
TAG = CommonUtil.getClassName(getClass());
mTarget = target;
mWrapper = TaskWrapperManager.getInstance().getGroupWrapper(DGTaskWrapper.class, taskId);
if (taskId != -1 && mWrapper.getEntity().getId() == -1) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, String.format("没有id为%s的任务", taskId)));
// 判断已存在的任务
if (mTarget instanceof AbsNormalTarget) {
if (taskId < 0) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, "任务id为空"));
} else if (mWrapper.getEntity().getId() < 0) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, "任务信息不存在"));
}
}
mTarget.setTaskWrapper(mWrapper);
if (getEntity() != null) {
getTaskWrapper().setDirPathTemp(getEntity().getDirPath());

@ -16,6 +16,7 @@
package com.arialyy.aria.core.download.target;
import android.text.TextUtils;
import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.event.ErrorEvent;
@ -47,11 +48,16 @@ class DNormalConfigHandler<TARGET extends AbsTarget> implements IConfigHandler {
private void initTarget(long taskId) {
mWrapper = TaskWrapperManager.getInstance().getNormalTaskWrapper(DTaskWrapper.class, taskId);
if (taskId != -1 && mWrapper.getEntity().getId() == -1) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, String.format("没有id为%s的任务", taskId)));
// 判断已存在的任务
if (mTarget instanceof AbsNormalTarget) {
if (taskId < 0) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, "任务id为空"));
} else if (mWrapper.getEntity().getId() < 0) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, "任务信息不存在"));
}
}
mEntity = mWrapper.getEntity();
mEntity = mWrapper.getEntity();
mTarget.setTaskWrapper(mWrapper);
if (mEntity != null) {
getWrapper().setTempFilePath(mEntity.getFilePath());

@ -39,7 +39,8 @@ public class FtpBuilderTarget extends AbsBuilderTarget<FtpBuilderTarget> {
/**
* 设置登陆字符串编码ftps等参数
*/
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
@com.arialyy.aria.core.common.CheckResult(suggest = Suggest.TASK_CONTROLLER)
//@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public FtpBuilderTarget option(FtpOption option) {
if (option == null) {
throw new NullPointerException("ftp 任务配置为空");

@ -84,7 +84,7 @@ class FailureTaskHandler<TASK extends ITask> {
private void retryTask(final TASK task) {
long interval = task.getTaskWrapper().getConfig().getReTryInterval();
final int num = task.getTaskWrapper().getConfig().getReTryNum();
final ITaskQueue queue = mSchedulers.getQueue(task);
final ITaskQueue queue = mSchedulers.getQueue(task.getTaskType());
mAriaManager.getAriaHandler().postDelayed(new Runnable() {
@Override public void run() {
AbsEntity entity = task.getTaskWrapper().getEntity();

@ -20,21 +20,21 @@ import android.os.Bundle;
import android.os.Message;
import com.arialyy.annotations.TaskEnum;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.task.DownloadGroupTask;
import com.arialyy.aria.core.task.DownloadTask;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.AbsNormalEntity;
import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.group.GroupSendParams;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.listener.ISchedulers;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.listener.ISchedulers;
import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.core.queue.DGroupTaskQueue;
import com.arialyy.aria.core.queue.DTaskQueue;
import com.arialyy.aria.core.queue.ITaskQueue;
import com.arialyy.aria.core.queue.UTaskQueue;
import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.task.DownloadGroupTask;
import com.arialyy.aria.core.task.DownloadTask;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.task.UploadTask;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.NetUtils;
@ -74,10 +74,9 @@ public class TaskSchedulers<TASK extends ITask> implements ISchedulers {
/**
* 通过任务类型获取任务队列
*
* @param curTask 当前接收到的的任务
* @param taskType 任务类型
*/
ITaskQueue getQueue(TASK curTask) {
int taskType = curTask.getTaskType();
ITaskQueue getQueue(int taskType) {
if (taskType == ITask.DOWNLOAD) {
return DTaskQueue.getInstance();
}
@ -171,6 +170,11 @@ public class TaskSchedulers<TASK extends ITask> implements ISchedulers {
}
@Override public boolean handleMessage(Message msg) {
if (msg.what == CHECK_FAIL) {
handlePreFailTask(msg.arg1);
return true;
}
if (msg.arg1 == IS_SUB_TASK) {
return handleSubEvent(msg);
}
@ -180,11 +184,11 @@ public class TaskSchedulers<TASK extends ITask> implements ISchedulers {
}
TASK task = (TASK) msg.obj;
if (task == null && msg.what != ISchedulers.CHECK_FAIL) {
if (task == null) {
ALog.e(TAG, "请传入下载任务");
return true;
}
handleNormalEvent(task, msg.what, msg.arg1);
handleNormalEvent(task, msg.what);
return true;
}
@ -292,8 +296,8 @@ public class TaskSchedulers<TASK extends ITask> implements ISchedulers {
/**
* 处理普通任务和任务组的事件
*/
private void handleNormalEvent(TASK task, int what, int arg1) {
ITaskQueue queue = getQueue(task);
private void handleNormalEvent(TASK task, int what) {
ITaskQueue queue = getQueue(task.getTaskType());
switch (what) {
case STOP:
if (task.getState() == IEntity.STATE_WAIT) {
@ -324,9 +328,6 @@ public class TaskSchedulers<TASK extends ITask> implements ISchedulers {
case FAIL:
handleFailTask(queue, task);
break;
case CHECK_FAIL:
handlePreFailTask(queue, arg1);
break;
}
if (what == FAIL || what == CHECK_FAIL) {
@ -343,8 +344,13 @@ public class TaskSchedulers<TASK extends ITask> implements ISchedulers {
normalTaskCallback(what, task);
}
private void handlePreFailTask(ITaskQueue queue, int taskType) {
startNextTask(queue, TaskSchedulerType.TYPE_DEFAULT);
/**
* 处理what为{@link #CHECK_FAIL}信息错误的任务
*
* @param taskType 任务类型
*/
private void handlePreFailTask(int taskType) {
startNextTask(getQueue(taskType), TaskSchedulerType.TYPE_DEFAULT);
// 发送广播
boolean canSend = mAriaManager.getAppConfig().isUseBroadcast();

@ -16,6 +16,7 @@
package com.arialyy.aria.core.upload;
import android.text.TextUtils;
import com.arialyy.aria.core.common.ErrorCode;
import com.arialyy.aria.core.inf.ICheckEntityUtil;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
@ -38,7 +39,7 @@ public class CheckUEntityUtil implements ICheckEntityUtil {
@Override
public boolean checkEntity() {
if (mWrapper.getErrorEvent() != null) {
ALog.e(TAG, mWrapper.getErrorEvent().errorMsg);
ALog.e(TAG, String.format("上传失败,%s", mWrapper.getErrorEvent().errorMsg));
return false;
}
@ -77,7 +78,7 @@ public class CheckUEntityUtil implements ICheckEntityUtil {
if (TextUtils.isEmpty(url)) {
ALog.e(TAG, "上传失败,url为null");
return false;
} else if (!CheckUtil.checkUrlNotThrow(url)) {
} else if (!CheckUtil.checkUrl(url)) {
ALog.e(TAG, "上传失败,url【" + url + "】错误");
return false;
}

@ -70,7 +70,7 @@ public class UploadReceiver extends AbsReceiver {
@CheckResult
public HttpBuilderTarget load(@NonNull String filePath) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP);
CheckUtil.checkUploadPath(filePath);
CheckUtil.checkUploadPathIsEmpty(filePath);
return UTargetFactory.getInstance()
.generateBuilderTarget(HttpBuilderTarget.class, filePath);
}
@ -84,7 +84,6 @@ public class UploadReceiver extends AbsReceiver {
@CheckResult
public HttpNormalTarget load(long taskId) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP);
CheckUtil.checkTaskId(taskId);
return UTargetFactory.getInstance()
.generateNormalTarget(HttpNormalTarget.class, taskId);
}
@ -97,7 +96,7 @@ public class UploadReceiver extends AbsReceiver {
@CheckResult
public FtpBuilderTarget loadFtp(@NonNull String filePath) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP);
CheckUtil.checkUploadPath(filePath);
CheckUtil.checkUploadPathIsEmpty(filePath);
return UTargetFactory.getInstance()
.generateBuilderTarget(FtpBuilderTarget.class, filePath);
}
@ -111,7 +110,6 @@ public class UploadReceiver extends AbsReceiver {
@CheckResult
public FtpNormalTarget loadFtp(long taskId) {
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP);
CheckUtil.checkTaskId(taskId);
return UTargetFactory.getInstance()
.generateNormalTarget(FtpNormalTarget.class, taskId);
}

@ -15,19 +15,18 @@
*/
package com.arialyy.aria.core.upload.target;
import com.arialyy.aria.core.processor.IFtpUploadInterceptor;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.common.ErrorCode;
import com.arialyy.aria.core.event.ErrorEvent;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.IConfigHandler;
import com.arialyy.aria.core.inf.IOptionConstant;
import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.core.queue.UTaskQueue;
import com.arialyy.aria.core.task.UploadTask;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.task.UploadTask;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
/**
@ -35,7 +34,6 @@ import java.io.File;
* 普通上传任务通用功能处理
*/
class UNormalConfigHandler<TARGET extends AbsTarget> implements IConfigHandler {
private String TAG = "UNormalDelegate";
private UploadEntity mEntity;
private TARGET mTarget;
private UTaskWrapper mWrapper;
@ -47,9 +45,15 @@ class UNormalConfigHandler<TARGET extends AbsTarget> implements IConfigHandler {
private void initTarget(long taskId) {
mWrapper = TaskWrapperManager.getInstance().getNormalTaskWrapper(UTaskWrapper.class, taskId);
if (taskId != -1 && mWrapper.getEntity().getId() == -1) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, String.format("没有id为%s的任务", taskId)));
// 判断已存在的任务
if (mTarget instanceof AbsNormalTarget) {
if (taskId < 0) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, "任务id为空"));
} else if (mWrapper.getEntity().getId() < 0) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, "任务信息不存在"));
}
}
mEntity = mWrapper.getEntity();
mTarget.setTaskWrapper(mWrapper);
getTaskWrapper().setTempUrl(mEntity.getUrl());

@ -296,7 +296,7 @@ public class HttpFileInfoThread implements Runnable {
newUrl = uri.getHost() + newUrl;
}
if (!CheckUtil.checkUrlNotThrow(newUrl)) {
if (!CheckUtil.checkUrl(newUrl)) {
failDownload(new TaskException(TAG, "下载失败,重定向url错误"), false);
return;
}

@ -294,7 +294,7 @@ final public class M3U8InfoThread implements Runnable {
newUrl = uri.getHost() + newUrl;
}
if (!CheckUtil.checkUrlNotThrow(newUrl)) {
if (!CheckUtil.checkUrl(newUrl)) {
failDownload("下载失败,重定向url错误", false);
return;
}

@ -22,10 +22,11 @@ public enum ErrorCode {
ERROR_CODE_URL_NULL(2, "url 为空"), ERROR_CODE_URL_INVALID(3, "url 无效"),
ERROR_CODE_PAGE_NUM(4, "page和num不能小于1"), ERROR_CODE_GROUP_URL_NULL(5, "组合任务url列表为空"),
ERROR_CODE_UPLOAD_FILE_NULL(7, "上传文件不存在"),
ERROR_CODE_MEMBER_WARNING(8, "为了防止内存泄漏,请使用静态的成员类(public static class xxx)或文件类(A.java)");
ERROR_CODE_MEMBER_WARNING(8, "为了防止内存泄漏,请使用静态的成员类(public static class xxx)或文件类(A.java)"),
ERROR_CODE_TASK_NOT_EXIST(9, "任务信息不存在");
int code;
String msg;
public int code;
public String msg;
ErrorCode(int code, String msg) {
this.code = code;

@ -15,7 +15,6 @@
*/
package com.arialyy.aria.core.download;
import com.arialyy.aria.core.event.ErrorEvent;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import java.util.List;
@ -26,19 +25,6 @@ import java.util.List;
public abstract class AbsGroupTaskWrapper<ENTITY extends AbsEntity, SUB extends AbsTaskWrapper>
extends AbsTaskWrapper<ENTITY> {
/**
* 错误信息
*/
private ErrorEvent errorEvent;
public ErrorEvent getErrorEvent() {
return errorEvent;
}
public void setErrorEvent(ErrorEvent errorEvent) {
this.errorEvent = errorEvent;
}
public AbsGroupTaskWrapper(ENTITY entity) {
super(entity);
}

@ -80,7 +80,9 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable {
@Override public int getTaskType() {
int type;
if (getUrl().startsWith("ftp")) {
if (TextUtils.isEmpty(getUrl())) {
type = ITaskWrapper.ERROR;
} else if (getUrl().startsWith("ftp")) {
type = ITaskWrapper.D_FTP;
} else {
M3U8Entity temp = getM3U8Entity();

@ -62,7 +62,6 @@ public class DownloadGroupTask extends AbsGroupTask<DGTaskWrapper> {
Handler outHandler;
public Builder(DGTaskWrapper taskEntity) {
CheckUtil.checkTaskEntity(taskEntity);
this.taskEntity = taskEntity;
}

@ -17,7 +17,6 @@ package com.arialyy.aria.core.wrapper;
import com.arialyy.aria.core.TaskOptionParams;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.ErrorCode;
import com.arialyy.aria.core.config.BaseTaskConfig;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity;
@ -26,8 +25,6 @@ import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.ITaskOption;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.util.ComponentUtil;
import java.util.ArrayList;
import java.util.List;
/**
* Created by lyy on 2017/2/23. 所有任务实体的父类
@ -87,15 +84,6 @@ public abstract class AbsTaskWrapper<ENTITY extends AbsEntity>
*/
private TaskOptionParams optionParams = new TaskOptionParams();
/**
* 初始状态列表
*/
private List<ErrorCode> errorCode = new ArrayList<>();
public List<ErrorCode> getErrorCode() {
return errorCode;
}
public void setTaskOption(ITaskOption option) {
this.taskOption = option;
}

@ -22,7 +22,7 @@ import com.arialyy.aria.core.inf.IEntity;
*/
public interface ITaskWrapper {
int DEFAULT = 0;
int ERROR = 0;
/**
* HTTP单任务载

@ -46,7 +46,7 @@ public enum ActionPolicy {
/**
* 父表有变更时,子表将外键列设置成一个默认的值default配置的值
*/
SET_DEFAULT("SET DEFAULT"),
SET_DEFAULT("SET ERROR"),
/**
* 在父表上update/delete记录时同步update/delete掉子表的匹配记录

@ -184,7 +184,7 @@ class DelegateCommon extends AbsDelegate {
if (SqlUtil.isDefault(field)) {
Default d = field.getAnnotation(Default.class);
if (!TextUtils.isEmpty(d.value())) {
sb.append(" DEFAULT ").append("'").append(d.value()).append("'");
sb.append(" ERROR ").append("'").append(d.value()).append("'");
}
}

@ -17,13 +17,6 @@
package com.arialyy.aria.util;
import android.text.TextUtils;
import com.arialyy.aria.core.common.ErrorCode;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import java.io.File;
import java.lang.reflect.Modifier;
import java.util.List;
@ -54,52 +47,12 @@ public class CheckUtil {
if (page < 1 || num < 1) throw new NullPointerException("page和num不能小于1");
}
/**
* 检测url是否合法如果url不合法将抛异常
*/
public static ErrorCode checkTaskId(long taskId) {
if (taskId < 0) {
return ErrorCode.ERROR_CODE_TASK_ID_NULL;
}
return ErrorCode.ERROR_CODE_NORMAL;
}
/**
* 检测url是否合法如果url不合法将抛异常
*/
public static void checkUrlInvalidThrow(String url) {
if (TextUtils.isEmpty(url)) {
throw new IllegalArgumentException("url不能为null");
} else if (!url.startsWith("http") && !url.startsWith("ftp") && !url.startsWith("sftp")) {
throw new IllegalArgumentException("url错误");
}
int index = url.indexOf("://");
if (index == -1) {
throw new IllegalArgumentException("url不合法");
}
}
/**
* 检测url是否合法如果url不合法将抛出{@link IllegalArgumentException}异常
*/
public static void checkUrl(String url) {
if (TextUtils.isEmpty(url)) {
throw new NullPointerException("url为空");
} else if (!url.startsWith("http") && !url.startsWith("ftp") && !url.startsWith("sftp")) {
throw new IllegalArgumentException(String.format("url【%s】错误", url));
}
int index = url.indexOf("://");
if (index == -1) {
throw new IllegalArgumentException(String.format("url【%s】不合法", url));
}
}
/**
* 检测url是否合法
*
* @return {@code true} 合法{@code false} 非法
*/
public static boolean checkUrlNotThrow(String url) {
public static boolean checkUrl(String url) {
if (TextUtils.isEmpty(url)) {
ALog.e(TAG, "url不能为null");
return false;
@ -116,65 +69,23 @@ public class CheckUtil {
/**
* 检测下载链接组是否为null
*
* @return true 组合任务url为空
*/
public static void checkDownloadUrls(List<String> urls) {
public static boolean checkDownloadUrlsIsEmpty(List<String> urls) {
if (urls == null || urls.isEmpty()) {
throw new IllegalArgumentException("链接组不能为null");
ALog.e(TAG, "链接组不能为null");
return true;
}
return false;
}
/**
* 检测上传地址是否为null
*/
public static void checkUploadPath(String uploadPath) {
public static void checkUploadPathIsEmpty(String uploadPath) {
if (TextUtils.isEmpty(uploadPath)) {
throw new IllegalArgumentException("上传地址不能为null");
}
File file = new File(uploadPath);
if (!file.exists()) {
throw new IllegalArgumentException("上传文件不存在");
}
}
/**
* 检查任务实体
*/
public static void checkTaskEntity(AbsTaskWrapper entity) {
if (entity instanceof DTaskWrapper) {
checkDownloadTaskEntity(((DTaskWrapper) entity).getEntity());
} else if (entity instanceof UTaskWrapper) {
checkUploadTaskEntity(((UTaskWrapper) entity).getEntity());
}
}
/**
* 检查上传实体是否合法
*/
private static void checkUploadTaskEntity(UploadEntity entity) {
if (entity == null) {
throw new NullPointerException("上传实体不能为空");
} else if (TextUtils.isEmpty(entity.getFilePath())) {
throw new IllegalArgumentException("上传文件路径不能为空");
} else if (TextUtils.isEmpty(entity.getFileName())) {
throw new IllegalArgumentException("上传文件名不能为空");
}
}
/**
* 检测下载实体是否合法
* 合法(true)
*
* @param entity 下载实体
*/
private static void checkDownloadTaskEntity(DownloadEntity entity) {
if (entity == null) {
throw new NullPointerException("下载实体不能为空");
} else if (TextUtils.isEmpty(entity.getUrl())) {
throw new IllegalArgumentException("下载链接不能为空");
} else if (TextUtils.isEmpty(entity.getFileName())) {
throw new NullPointerException("文件名不能为null");
} else if (TextUtils.isEmpty(entity.getDownloadPath())) {
throw new NullPointerException("文件保存路径不能为null");
}
}
}

@ -29,6 +29,7 @@ import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.frame.permission.OnPermissionCallback;
import com.arialyy.frame.permission.PermissionManager;
import com.arialyy.frame.util.show.T;

@ -93,10 +93,10 @@ public class FtpDownloadActivity extends BaseActivity<ActivityFtpDownloadBinding
case R.id.start:
if (mTaskId == -1) {
mTaskId = Aria.download(this).loadFtp(mUrl)
Aria.download(this).loadFtp(mUrl)
.setFilePath(mFilePath, true)
.option(getFtpOption())
.create();
.option(getFtpOption());
//.create();
getBinding().setStateStr(getString(R.string.stop));
break;
}

@ -74,7 +74,7 @@ public class DownloadFragment extends AbsFragment<FragmentDownloadBinding>
mTaskId = Aria.download(this)
.load(DOWNLOAD_URL)
.setFilePath(
Environment.getExternalStorageDirectory().getPath() + String.format("%s.apk",
Environment.getExternalStorageDirectory().getPath() + String.format("/%s.apk",
FILE_NAME))
.create();
getBinding().setStateStr(getString(R.string.stop));

@ -38,6 +38,5 @@ public class FragmentActivity extends BaseActivity<FragmentDownloadBinding> {
final NormalTo to = getIntent().getParcelableExtra(MainActivity.KEY_MAIN_DATA);
setTitle(to.title);
Aria.download(this).register();
}
}

Loading…
Cancel
Save