Merge remote-tracking branch 'remotes/origin/master' into hechu1

# Conflicts:
#	src/core/operation/Select.json
#	src/core/process/Process.java
zgl
何楚 6 years ago
commit d6350f17d4

@ -4,12 +4,17 @@ import core.user.User;
import dao.DBManagement;
import error.GExcptSQL;
import java.util.HashMap;
import java.util.Map;
public class Delete extends Operation {
@Override
public void execute(User subject) throws GExcptSQL {
String id = (String)options.get("id");
String sql = "DELETE FROM graduation_design WHERE id = \'"+
id+"\'";
DBManagement.update(sql);
public Map<String, Object> execute(User subject) throws GExcptSQL {
Map<String, String> map = new HashMap<>();
map.put("id",(String)this.getOptions().get("id"));
for(String table:DBManagement.graduationDesignTables){
DBManagement.delete(table,map);
}
return options;
}
}

@ -1,3 +1,6 @@
{
"id": "java.lang.String"
"options":{
"id": "String"
},
"return":{}
}

@ -1,4 +1,12 @@
package core.operation;
public class DownloadFile {
import core.user.User;
import java.util.Map;
public class DownloadFile extends Operation{
@Override
public Map<String, Object> execute(User subject) throws Exception {
return this.getOptions();
}
}

@ -0,0 +1,6 @@
{
"options":{
},
"return":{}
}

@ -1,4 +1,18 @@
package core.operation;
public class FillInformation {
import core.user.User;
import dao.DBManagement;
import java.util.Map;
public class FillInformation extends Operation{
@Override
public Map<String, Object> execute(User subject) throws Exception {
String table = (String) this.getOptions().get("table");
Map<String,String> vMap = (Map<String, String>) this.getOptions().get("vMap");
Map<String,String> limits = (Map<String, String>) this.getOptions().get("limits");
DBManagement.update(table,vMap,limits);
return this.getOptions();
}
}

@ -0,0 +1,8 @@
{
"options":{
"table":"String",
"vMap":"Map<String,String>",
"limits":"Map<String,String>"
},
"return":{}
}

@ -2,7 +2,7 @@ package core.operation;
import core.user.User;
import error.GExcptSQL;
import error.GExcptFormat;
import gdms.Mode;
import java.util.Map;
@ -14,19 +14,21 @@ public abstract class Operation {
Operation(){
super();
}
public abstract Map<String, Object> execute(User subject) throws Exception;
public void setSubject(User subject) {
this.subject = subject;
}
public User getSubject() {
return subject;
}
public void setSubject(User subject) {
this.subject = subject;
public Map<String, Object> getOptions() {
return options;
}
public void setOptions(String options) {
public void setOptions(String options) throws GExcptFormat {
this.options = core.operation.utils.util.string2MapOptions(options);
}
public void setProcessOperations(String processOptions){
setOptions(processOptions);
}
public void addUserOperations(String key, Object value){
public void addOptions(String key, Object value){
if ( Mode.strict == 1){
if(!this.options.containsKey(key)) {
try {
@ -38,6 +40,7 @@ public abstract class Operation {
}
this.options.put(key,value);
}
public abstract void execute(User subject) throws Exception;
public void setOptions(Map<String, Object> options) {
this.options = options;
}
}

@ -4,19 +4,31 @@ import core.user.User;
import dao.DBManagement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Search extends Operation {
@Override
public void execute(User subject) throws Exception {
String table = (String)options.get("table");
String field = (String)options.get("field");
String value = (String)options.get("value");
int start = (int)options.get("start");
int end = (int)options.get("end");
String sql = "SELECT * FROM "+table+
" WHERE "+field+" = \'"+value+"\'"+
" limit "+start+", "+end;
ResultSet rs = DBManagement.query(sql);
//todo
public Map<String, Object> execute(User subject) throws Exception {
String table = (String)this.getOptions().get("table");
List<String> fields = (List<String>)this.getOptions().get("fields");
Map<String, String> limits = (Map<String, String>)this.getOptions().get("limits");
int start = (int)this.getOptions().get("start");
int end = (int)this.getOptions().get("end");
ResultSet rs = DBManagement.select(fields,table,limits,start,end);
ResultSetMetaData rsm = rs.getMetaData() ;
int columnCount = rsm.getColumnCount();
List<String[]> ls = new ArrayList<>();
while(rs.next()){
String[] s = new String[columnCount];
for(int i=0;i<columnCount;i++){
s[i] = rs.getString(i);
}
ls.add(s);
}
this.getOptions().put("result",ls);
return this.getOptions();
}
}

@ -1,8 +1,12 @@
{
"table": "java.lang.String",
"field": "java.lang.String",
"value": "java.lang.String",
"options":{
"table": "String",
"fields": "List",
"value": "String",
"start": "int",
"end": "int",
"return": "java.lang.String[][]"
"end": "int"
},
"return": {
"result":"List<String[]>"
}
}

@ -4,19 +4,25 @@ import core.user.User;
import dao.DBManagement;
import error.GExcptSQL;
import java.util.HashMap;
import java.util.Map;
public class Select extends Operation {
@Override
public void execute(User subject) throws GExcptSQL {
String student_id = (String)options.get("student_id");
String teacher_id = (String)options.get("teacher_id");
String id = student_id;
String sql = "INSERT INTO graduation_design VALUES (\'"+
id+"\', "+
student_id+"\', "+
teacher_id+"\', \'m\', \'m\')";
DBManagement.update(sql);
public Map<String, Object> execute(User subject) throws GExcptSQL {
Map<String, String> vMap = new HashMap<>();
String id = (String)this.getOptions().get("student_id");
String teacher_id = (String)this.getOptions().get("teacher_id");
vMap.put("id",id);
vMap.put("student_id",id);
vMap.put("teacher_id",teacher_id);
DBManagement.insert("graduation_design", vMap);
vMap.remove("student_id");
vMap.remove("teacher_id");
for(int i=1;i<DBManagement.graduationDesignTables.length;i++){
DBManagement.insert(DBManagement.graduationDesignTables[i],vMap);
}
return this.getOptions();
}
}

@ -1,4 +1,7 @@
{
"student_'id": "java.lang.String",
"teacher_id": "java.lang.String"
"options":{
"student_id": "String",
"teacher_id": "String"
},
"return":{}
}

@ -6,10 +6,8 @@ import error.GExcptSQL;
import java.util.Map;
public class UploadFileOperation extends Operation {
Map<String, Object> options;
@Override
public void execute(User subject) throws GExcptSQL {
public Map<String, Object> execute(User subject) throws GExcptSQL {
return this.getOptions();
}
}

@ -1,4 +1,6 @@
{
"java.lang.String": "activity",
"lava.io.File": "file"
"options":{
"file": "File"
},
"return":{}
}

@ -1,5 +1,8 @@
package core.operation.utils;
import error.GExcptFormat;
import gdms.Mode;
import java.util.HashMap;
import java.util.Map;
@ -7,12 +10,16 @@ public interface util {
static String stringOptionsFormat(String options){
return options.replaceAll(" {2,}", " ") ;
}
static Map<String, Object> string2MapOptions(String sOptions){
static Map<String, Object> string2MapOptions(String sOptions) throws GExcptFormat {
sOptions = stringOptionsFormat(sOptions);
Map<String, Object> options = new HashMap<>();
String[] sOpts = sOptions.split(" ");
for(int i=0;i<sOpts.length;i++){
options.put(sOpts[i],sOpts[++i]);
if(Mode.strict==1){
if(sOpts[i].indexOf(0)=='-')
throw new GExcptFormat("options format error");
}
options.put(sOpts[i].substring(1),sOpts[++i]);
}
return options;
}

@ -1,7 +0,0 @@
package core.process;
public class AuxiliaryProcess extends Process {
public AuxiliaryProcess(String permission, String operationName, String processOptions, String info) throws Exception {
super(permission, operationName, processOptions, info);
}
}

@ -1,8 +0,0 @@
package core.process;
public class LastProcess extends Process{
public LastProcess(String permission, String operationName, String processOptions, String info) throws Exception {
super(permission, operationName, processOptions, info);
}
}

@ -1,9 +1,6 @@
package core.process;
import core.operation.Operation;
import core.user.User;
import error.GExcptFactory;
import error.GExcptSQL;
public abstract class Process implements Cloneable{
@ -11,48 +8,12 @@ public abstract class Process implements Cloneable{
private Operation operation;
private String buttonName;
private String info;
private String processOptions;
private String UserOptions;
@Override
public Process clone(){
//todo
return null;
}
public Process(String permission, String operationName, String processOptions, String info) throws Exception {
super();
this.setPermission(permission);
this.setOperation(operationName);
this.setInfo(info);
this.setProcessOptions(processOptions);
operation.setProcessOperations(processOptions);
}
public void setUserOperations(String key,Object value){
this.operation.addUserOperations(key,value);
}
public void execute(User subject) throws Exception {
this.operation.execute(subject);
}
private void setOperation(String operationName) throws GExcptFactory {
try {
this.operation = (Operation) core.utils.GetObjectByName.getOperationByName(operationName);
} catch (Exception e) {
throw new GExcptFactory("create"+operationName+"failure");
}
}
public void setInfo(String info) {
this.info = info;
}
public String getButtonName() {
return buttonName;
}
public void setButtonName(String buttonName) {
this.buttonName = buttonName;
}
public String getInfo() {
return info;
}
public String getPermission() {
return permission;
}
@ -69,12 +30,19 @@ public abstract class Process implements Cloneable{
this.operation = operation;
}
public String getProcessOptions() {
return processOptions;
public String getButtonName() {
return buttonName;
}
public void setButtonName(String buttonName) {
this.buttonName = buttonName;
}
public void setProcessOptions(String processOptions) {
this.processOptions = processOptions;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}

@ -1,25 +1,9 @@
package core.process;
import java.io.File;
import java.util.List;
import java.util.Map;
public class ProcessManagement {
private List<TemporaryProcess> temporaryProcesses;
private Map<String, List<Process>> lastProcesses;
private List<AuxiliaryProcess> auxiliaryProcesses;
public void setProcessesByJson(File file){
//todo
}
public Process getTemporaryProcess(int index){
return temporaryProcesses.get(index).clone();
}
public Condition getCondition(String userType, List<Integer> index){
Condition condition = new Condition(this.lastProcesses.get(userType));
for(int i:index){
condition.add(getTemporaryProcess(i));
}
return condition;
return null;
}
}

@ -1,9 +0,0 @@
package core.process;
public class TemporaryProcess extends Process {
public TemporaryProcess(String permission, String operationName, String processOptions, String info) throws Exception {
super(permission, operationName, processOptions, info);
}
}

@ -1,4 +1,10 @@
package core.user;
import java.util.Map;
public class Administrator extends User {
@Override
public void setAttr(Map<String, String> vMap){
super.setAttr(vMap);
}
}

@ -1,7 +1,31 @@
package core.user;
import java.io.File;
import java.util.Map;
public class Student extends User {
private String grade;
private String profession_code;
public void setAttr(Map<String, String> vMap){
super.setAttr(vMap);
this.setGrade(vMap.get("grade"));
this.setProfession_code(vMap.get("profession_code"));
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
public String getProfession_code() {
return profession_code;
}
public void setProfession_code(String profession_code) {
this.profession_code = profession_code;
}
}

@ -1,7 +1,41 @@
package core.user;
import java.io.File;
import java.util.Map;
public class Teacher extends User {
String job_title;
String education;
String profession_code;
@Override
public void setAttr(Map<String, String> vMap){
super.setAttr(vMap);
this.setEducation(vMap.get("education"));
this.setJob_title(vMap.get("job_title"));
this.setProfession_code(vMap.get("profession_code"));
}
public String getJob_title() {
return job_title;
}
public void setJob_title(String job_title) {
this.job_title = job_title;
}
public String getEducation() {
return education;
}
public void setEducation(String education) {
this.education = education;
}
public String getProfession_code() {
return profession_code;
}
public void setProfession_code(String profession_code) {
this.profession_code = profession_code;
}
}

@ -5,6 +5,7 @@ import core.user.userFun.AccountManageable;
import core.user.userFun.ProcessConfigurable;
import java.util.List;
import java.util.Map;
public abstract class User implements AccountManageable, ProcessConfigurable{
@ -109,4 +110,11 @@ public abstract class User implements AccountManageable, ProcessConfigurable{
public void syncFromDatabase() {
}
public void setAttr(Map<String, String> vMap){
this.setId(vMap.get("id"));
this.setName(vMap.get("name"));
this.setE_mail_location(vMap.get("e_mail_location"));
this.setPhone_number(vMap.get("phone_number"));
}
}

@ -8,26 +8,28 @@ import utils.Utils;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static dao.DBManagement.userTables;
public interface AccountManagement {
static String[] userTables = {
"administrator",
"student",
"teacher"
};
static User login(String id, String password) throws GExcptSQL, GExcptAccount, SQLException {
ResultSet rs = null;
String sql = null;
String userType = null;
for(String userTable:userTables){
sql = "SELECT * FROM "+userTable+" WHERE id=\'"+id+"\'";
List<String> columns = new ArrayList<>();
columns.add("*");
Map<String,String> limits = new HashMap<>();
limits.put("id",id);
try {
rs = DBManagement.query(sql);
rs = DBManagement.select(columns,userTable,limits,1,2);
} catch (Exception e) {
throw new GExcptSQL("QUERY\n\t"+sql+"\nfailure");
throw new GExcptSQL("QUERY\n\t"+id+"\nfailure");
}
if(rs!=null){
userType = userTable;
@ -41,25 +43,21 @@ public interface AccountManagement {
try {
if(!rs.getString(2).equals(password))
throw new GExcptAccount("password wrong");
List<String> necessaryInfo = new ArrayList<>();
for(int i=0;i<4;i++){
necessaryInfo.add(rs.getString(i));
Map<String, String> vMap = new HashMap<>();
ResultSetMetaData rsm = rs.getMetaData();
rs.next();
for(int i=0;i<rsm.getColumnCount();i++){
vMap.put(rsm.getCatalogName(i),rs.getString(i));
}
return createUser(userType,necessaryInfo);
return createUser(userType,vMap);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
static User register(String userType, List<String> necessaryInfo) throws GExcptSQL {
necessaryInfo = Utils.formatUserInfo(userType,necessaryInfo);
String sql = "INSERT INTO "+userType+" VALUES(\'";
for(String info:necessaryInfo){
sql+=info+"\', \'";
}
sql =sql.substring(0,sql.length()-3)+")";
DBManagement.update(sql);
return createUser(userType, necessaryInfo);
static User register(String userType, Map<String, String> vMap) throws GExcptSQL {
DBManagement.insert(userType,vMap);
return createUser(userType, vMap);
}
static void logout(User user){
//todo
@ -70,25 +68,14 @@ public interface AccountManagement {
static User getUser(String userType){
try {
return (User) Class.forName("core.user."+ Utils.toUpperFirstChar(userType)).getDeclaredConstructor().newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
static User createUser(String userType, List<String> necessaryInfo) {
static User createUser(String userType, Map<String, String> vMap) {
User user = getUser(userType);
user.setId(necessaryInfo.get(0));
user.setName(necessaryInfo.get(1));
user.setE_mail_location(necessaryInfo.get(2));
user.setPhone_number(necessaryInfo.get(3));
user.setAttr(vMap);
return user;
}
}

@ -1,6 +1,8 @@
package dao;
import java.sql.*;
import java.util.List;
import java.util.Map;
import error.GExcptSQL;
import org.apache.tomcat.jdbc.pool.DataSource;
@ -9,6 +11,24 @@ import org.apache.tomcat.jdbc.pool.PoolProperties;
public class DBManagement {
public static String[] userTables = {
"administrator",
"student",
"teacher"
};
public static String[] graduationDesignTables = {
"graduation_design",
"graduation_design_finished_product",
"graduation_design_finished_product_mentor_score",
"graduation_design_finished_product_reviewer_score",
"graduation_design_information",
"graduation_design_opening_report",
"graduation_design_opening_report_opinion_record",
"graduation_design_reply",
"graduation_design_reply_opinion_record_score"
};
static final String driverClassName="org.mariadb.jdbc.Driver";
static final String url="jdbc:mariadb://localhost:3306/gdms";
static final String username="gdms";
@ -42,6 +62,26 @@ public class DBManagement {
return null;
}
public static void delete(String table, Map<String,String> limits) throws GExcptSQL {
Delete delete = new Delete(table, limits);
String sql = delete.getSQL();
update(sql);
}
public static void insert(String table, Map<String, String> vMap) throws GExcptSQL {
Insert insert = new Insert(table, vMap);
String sql = insert.getSQL();
update(sql);
}
public static ResultSet select(List<String> columns, String table, Map<String,String> limits, int startRow, int endRow) throws Exception {
Select select = new Select(columns,table,limits,startRow,endRow);
String sql = select.getSQL();
return query(sql);
}
public static void update(String table, Map<String,String> vMap, Map<String,String> limits) throws GExcptSQL {
Update update = new Update(table,vMap,limits);
String sql = update.getSQL();
update(sql);
}
public static ResultSet query(String sql) throws Exception {
if(!ifInit) return null;
ResultSet rs = null;

@ -0,0 +1,31 @@
package dao;
import java.util.Map;
public class Delete extends SQLStmt {
private static final String fs = "DELETE ";
private Map<String,String> limits;
Delete(String table, Map<String, String> limits) {
super(table);
this.setLimits(limits);
}
@Override
public String getSQL() {
return this.getFs()+this.getLs();
}
private String getLs(){
return dao.Utils.whereAnd(this.getLimits());
}
public String getFs(){
return fs+this.getTable()+" ";
}
private Map<String, String> getLimits() {
return limits;
}
private void setLimits(Map<String, String> limits) {
this.limits = limits;
}
}

@ -0,0 +1,43 @@
package dao;
import utils.Utils;
import java.util.Map;
public class Insert extends SQLStmt {
private static final String fs ="INSERT INTO ";
private Map<String, String> vMap;
Insert(String table, Map<String, String> vMap){
super(table);
this.setVMap(vMap);
}
@Override
public String getSQL() {
return this.getFs()+this.getVs();
}
@Override
public String getFs() {
return fs+this.getTable()+" ";
}
private String getVs(){
StringBuilder sql = new StringBuilder("(");
for(String key: this.getVMap().keySet()){
sql.append(key).append(", ");
}
sql = new StringBuilder(Utils.cutTail(sql.toString(), 2) + ") ");
sql.append("VALUES (\'");
for(String value: vMap.values()){
sql.append(value).append("\', \'");
}
sql = new StringBuilder(Utils.cutTail(sql.toString(), 3) + ")");
return sql.toString();
}
private Map<String, String> getVMap() {
return vMap;
}
private void setVMap(Map<String, String> vMap) {
this.vMap = vMap;
}
}

@ -0,0 +1,16 @@
package dao;
public abstract class SQLStmt {
private String table;
SQLStmt(String table) {
this.setTable(table);
}
public abstract String getSQL();
public abstract String getFs();
String getTable() {
return table;
}
public void setTable(String table) {
this.table = table;
}
}

@ -0,0 +1,62 @@
package dao;
import java.util.List;
import java.util.Map;
public class Select extends SQLStmt {
private static final String fs ="SELECT ";
private Map<String,String> limits;
private List<String> columns;
private int startRow;
private int endRow;
public Select(List<String> columns, String table, Map<String,String> limits, int startRow, int endRow){
super(table);
this.setColumns(columns);
this.setLimits(limits);
this.setStartRow(startRow);
this.setEndRow(endRow);
}
@Override
public String getSQL() {
return this.getFs()+this.getTs()+this.getLs();
}
private String getLs(){
String sql = dao.Utils.whereAnd(this.getLimits())+" ";
sql+="LIMIT "+this.getStartRow()+", "+this.getEndRow();
return sql;
}
@Override
public String getFs() {
return fs+dao.Utils.linkColumn(this.getColumns())+" ";
}
private String getTs(){
return "FROM "+this.getTable()+" ";
}
private Map<String, String> getLimits() {
return limits;
}
public void setLimits(Map<String, String> limits) {
this.limits = limits;
}
private List<String> getColumns() {
return columns;
}
public void setColumns(List<String> columns) {
this.columns = columns;
}
private int getStartRow() {
return startRow;
}
private void setStartRow(int startRow) {
this.startRow = startRow;
}
private int getEndRow() {
return endRow;
}
private void setEndRow(int endRow) {
this.endRow = endRow;
}
}

@ -0,0 +1,52 @@
package dao;
import java.util.Map;
public class Update extends SQLStmt {
private static final String fs ="UPDATE ";
private Map<String,String> limits;
private Map<String,String> vMap;
Update(String table, Map<String, String> vMap, Map<String, String> limits) {
super(table);
this.setVMap(vMap);
this.setLimits(limits);
}
@Override
public String getSQL() {
return this.getFs()+this.getVs()+this.getLs();
}
@Override
public String getFs() {
return fs+this.getTable()+" ";
}
private String getLs(){
return dao.Utils.whereAnd(this.getLimits());
}
private String getVs(){
String sql = "SET ";
sql+=Utils.linkColumn(Utils.linkKeyValue(this.getVMap()))+" ";
return sql;
}
private Map<String, String> getLimits() {
return limits;
}
public void setLimits(Map<String, String> limits) {
this.limits = limits;
}
public Map<String, String> getVMap() {
return vMap;
}
public void setVMap(Map<String, String> vMap) {
this.vMap = vMap;
}
}

@ -1,6 +1,33 @@
package dao;
public interface Utils {
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public interface Utils {
static String whereAnd(Map<String,String> map){
List<String> list = linkKeyValue(map);
String sql = "WHERE ";
for(String s:list){
sql+=s+" AND ";
}
sql = utils.Utils.cutTail(sql,5)+"";
return sql;
}
//with ", "
static String linkColumn(List<String> list){
String sql = "";
for(String c:list){
sql+=c+", ";
}
return utils.Utils.cutTail(sql,2);
}
//with " = "
static List<String> linkKeyValue(Map<String, String> map){
List<String> list = new ArrayList<>();
for(Map.Entry<String,String> entry:map.entrySet()){
list.add(entry.getKey()+" = \'"+entry.getValue()+"\'");
}
return list;
}
}

@ -0,0 +1,7 @@
package error;
public class GExcptFormat extends GExcpt {
public GExcptFormat(String info) {
super(info);
}
}

@ -0,0 +1,21 @@
package gdms;
import dao.Select;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class test {
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("q");
list.add("w");
Map<String,String> map=new HashMap<>();
map.put("a","b");
map.put("c","d");
Select sql = new Select(list,"zx",map,1,2);
System.out.println(sql.getSQL());
}
}

@ -5,24 +5,26 @@ import java.util.List;
import java.util.Map;
public interface Utils {
public static Map<String,Integer> userAttrNum =new HashMap<>()
Map<String,Integer> userAttrNum =new HashMap<>()
{
{
userAttrNum.put("administrator",5);
userAttrNum.put("student",7);
userAttrNum.put("teacher",8);
put("administrator",5);
put("student",7);
put("teacher",8);
}
};
public static String toUpperFirstChar(String string) {
static String toUpperFirstChar(String string) {
char[] charArray = string.toCharArray();
charArray[0] -= 32;
return String.valueOf(charArray);
}
public static List<String> formatUserInfo(String userType, List<String> userInfo){
static List<String> formatUserInfo(String userType, List<String> userInfo){
for(int i=0;i<userAttrNum.get(userType)-userInfo.size();i++){
userInfo.add("");
}
return userInfo;
}
static String cutTail(String s,int i){
return s.substring(0,s.length()-i);
}
}

Loading…
Cancel
Save