*) cleaned up code for better readability in Netbeans (tabs -> spaces)\n*) no functional changes

git-svn-id: https://svn.berlios.de/svnroot/repos/yacy/trunk@4282 6c8d7289-2bf4-0310-a012-ef5d649a1542
pull/1/head
low012 17 years ago
parent a52681dd49
commit 5aa8d72502

@ -1,4 +1,4 @@
// Blog.java
// Blog.java
// -----------------------
// part of YACY
// (C) by Michael Peter Christen; mc@anomic.de
@ -69,23 +69,23 @@ import de.anomic.yacy.yacyNewsRecord;
public class Blog {
private static final String DEFAULT_PAGE = "blog_default";
private static SimpleDateFormat SimpleFormatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
// TODO: make userdefined date/time-strings (localisation)
private static SimpleDateFormat SimpleFormatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
// TODO: make userdefined date/time-strings (localisation)
public static String dateString(Date date) {
return SimpleFormatter.format(date);
return SimpleFormatter.format(date);
}
public static serverObjects respond(httpHeader header, serverObjects post, serverSwitch env) {
final plasmaSwitchboard switchboard = (plasmaSwitchboard) env;
final serverObjects prop = new serverObjects();
blogBoard.entry page = null;
public static serverObjects respond(httpHeader header, serverObjects post, serverSwitch env) {
final plasmaSwitchboard switchboard = (plasmaSwitchboard) env;
final serverObjects prop = new serverObjects();
blogBoard.entry page = null;
final boolean authenticated = switchboard.adminAuthenticated(header) >= 2;
final int display = ((post == null) || (!authenticated)) ? 0 : post.getInt("display", 0);
prop.put("display", display);
boolean hasRights = switchboard.verifyAuthentication(header, true);
final boolean xml = ((String)header.get(httpHeader.CONNECTION_PROP_PATH)).endsWith(".xml");
final String address = yacyCore.seedDB.mySeed().getPublicAddress();
@ -95,16 +95,16 @@ public class Blog {
} else {
prop.put("mode_admin", "0");
}
if (post == null) {
prop.putHTML("peername", yacyCore.seedDB.mySeed().getName());
prop.put("address", address);
return putBlogDefault(prop, switchboard, address, 0, 20, hasRights, xml);
}
final int start = post.getInt("start",0); //indicates from where entries should be shown
final int num = post.getInt("num",20); //indicates how many entries should be shown
if(!hasRights){
final userDB.Entry userentry = switchboard.userDB.proxyAuth((String)header.get("Authorization", "xxxxxx"));
if(userentry != null && userentry.hasRight(userDB.Entry.BLOG_RIGHT)){
@ -113,172 +113,175 @@ public class Blog {
//opens login window if login link is clicked - contrib [MN]
prop.put("AUTHENTICATE","admin log-in");
}
}
String pagename = post.get("page", DEFAULT_PAGE);
final String ip = (String)header.get(httpHeader.CONNECTION_PROP_CLIENTIP, "127.0.0.1");
String StrAuthor = post.get("author", "");
if (StrAuthor.equals("anonymous")) {
StrAuthor = switchboard.blogDB.guessAuthor(ip);
if (StrAuthor == null || StrAuthor.length() == 0) {
if (de.anomic.yacy.yacyCore.seedDB.mySeed() == null)
StrAuthor = "anonymous";
else {
StrAuthor = de.anomic.yacy.yacyCore.seedDB.mySeed().get("Name", "anonymous");
}
}
}
byte[] author;
try {
author = StrAuthor.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
author = StrAuthor.getBytes();
}
if(hasRights && post.containsKey("delete") && post.get("delete").equals("sure")) {
}
String pagename = post.get("page", DEFAULT_PAGE);
final String ip = (String)header.get(httpHeader.CONNECTION_PROP_CLIENTIP, "127.0.0.1");
String StrAuthor = post.get("author", "");
if (StrAuthor.equals("anonymous")) {
StrAuthor = switchboard.blogDB.guessAuthor(ip);
if (StrAuthor == null || StrAuthor.length() == 0) {
if (de.anomic.yacy.yacyCore.seedDB.mySeed() == null) {
StrAuthor = "anonymous";
} else {
StrAuthor = de.anomic.yacy.yacyCore.seedDB.mySeed().get("Name", "anonymous");
}
}
}
byte[] author;
try {
author = StrAuthor.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
author = StrAuthor.getBytes();
}
if(hasRights && post.containsKey("delete") && post.get("delete").equals("sure")) {
page = switchboard.blogDB.read(pagename);
final Iterator i = page.comments().iterator();
while(i.hasNext()) {
switchboard.blogCommentDB.delete((String) i.next());
}
switchboard.blogDB.delete(pagename);
pagename = DEFAULT_PAGE;
}
if (post.containsKey("discard"))
switchboard.blogDB.delete(pagename);
pagename = DEFAULT_PAGE;
}
if (post.containsKey("discard")) {
pagename = DEFAULT_PAGE;
}
if (post.containsKey("submit") && (hasRights)) {
// store a new/edited blog-entry
byte[] content;
try {
content = post.get("content", "").getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
content = post.get("content", "").getBytes();
}
Date date = null;
// store a new/edited blog-entry
byte[] content;
try {
content = post.get("content", "").getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
content = post.get("content", "").getBytes();
}
Date date = null;
ArrayList comments = null;
//set name for new entry or date for old entry
if(pagename.equals(DEFAULT_PAGE))
pagename = String.valueOf(System.currentTimeMillis());
else {
page = switchboard.blogDB.read(pagename);
//set name for new entry or date for old entry
if(pagename.equals(DEFAULT_PAGE)) {
pagename = String.valueOf(System.currentTimeMillis());
} else {
page = switchboard.blogDB.read(pagename);
comments = page.comments();
date = page.date();
}
final String commentMode = post.get("commentMode", "1");
final String StrSubject = post.get("subject", "");
byte[] subject;
try {
subject = StrSubject.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
subject = StrSubject.getBytes();
}
switchboard.blogDB.write(switchboard.blogDB.newEntry(pagename, subject, author, ip, date, content, comments, commentMode));
// create a news message
final HashMap map = new HashMap();
map.put("page", pagename);
map.put("subject", StrSubject.replace(',', ' '));
map.put("author", StrAuthor.replace(',', ' '));
yacyCore.newsPool.publishMyNews(yacyNewsRecord.newRecord(yacyNewsPool.CATEGORY_BLOG_ADD, map));
}
page = switchboard.blogDB.read(pagename); //maybe "if(page == null)"
if (post.containsKey("edit")) {
//edit an entry
if(hasRights) {
try {
date = page.date();
}
final String commentMode = post.get("commentMode", "1");
final String StrSubject = post.get("subject", "");
byte[] subject;
try {
subject = StrSubject.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
subject = StrSubject.getBytes();
}
switchboard.blogDB.write(switchboard.blogDB.newEntry(pagename, subject, author, ip, date, content, comments, commentMode));
// create a news message
final HashMap map = new HashMap();
map.put("page", pagename);
map.put("subject", StrSubject.replace(',', ' '));
map.put("author", StrAuthor.replace(',', ' '));
yacyCore.newsPool.publishMyNews(yacyNewsRecord.newRecord(yacyNewsPool.CATEGORY_BLOG_ADD, map));
}
page = switchboard.blogDB.read(pagename); //maybe "if(page == null)"
if (post.containsKey("edit")) {
//edit an entry
if(hasRights) {
try {
prop.put("mode", "1"); //edit
prop.put("mode_commentMode", page.getCommentMode());
prop.putHTML("mode_author", new String(page.author(),"UTF-8"), xml);
prop.put("mode_pageid", page.key());
prop.putHTML("mode_subject", new String(page.subject(), "UTF-8"), xml);
prop.put("mode_page-code", new String(page.page(), "UTF-8"));
} catch (UnsupportedEncodingException e) {}
}
else {
prop.put("mode", "3"); //access denied (no rights)
}
}
else if(post.containsKey("preview")) {
//preview the page
if(hasRights) {
prop.put("mode", "2");//preview
prop.putHTML("mode_author", new String(page.author(),"UTF-8"), xml);
prop.put("mode_pageid", page.key());
prop.putHTML("mode_subject", new String(page.subject(), "UTF-8"), xml);
prop.put("mode_page-code", new String(page.page(), "UTF-8"));
} catch (UnsupportedEncodingException e) {}
}
else {
prop.put("mode", "3"); //access denied (no rights)
}
}
else if(post.containsKey("preview")) {
//preview the page
if(hasRights) {
prop.put("mode", "2");//preview
prop.put("mode_commentMode", post.getInt("commentMode", 1));
prop.putHTML("mode_pageid", pagename, xml);
try {
prop.putHTML("mode_author", new String(author, "UTF-8"), xml);
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_author", new String(author), xml);
}
prop.putHTML("mode_subject", post.get("subject",""), xml);
prop.put("mode_date", dateString(new Date()));
prop.putWiki("mode_page", post.get("content", ""));
prop.putHTML("mode_page-code", post.get("content", ""), xml);
}
else prop.put("mode", "3"); //access denied (no rights)
}
else if(post.get("delete", "").equals("try")) {
if(hasRights) {
prop.put("mode", "4");
prop.put("mode_pageid", pagename);
try {
prop.putHTML("mode_author",new String(page.author(), "UTF-8"), xml);
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_author",new String(page.author()), xml);
}
try {
prop.putHTML("mode_subject",new String(page.subject(),"UTF-8"), xml);
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_subject",new String(page.subject()), xml);
}
}
else prop.put("mode", "3"); //access denied (no rights)
}
else if(post.containsKey("import")) {
prop.put("mode", "5");
prop.put("mode_state", "0");
}
else if(post.containsKey("xmlfile")) {
prop.put("mode", "5");
if(switchboard.blogDB.importXML(new String((byte[])post.get("xmlfile$file")))) {
prop.put("mode_state", "1");
}
else {
prop.put("mode_state", "2");
}
}
else {
// show blog-entry/entries
prop.put("mode", "0"); //viewing
if(pagename.equals(DEFAULT_PAGE)) {
// XXX: where are "peername" and "address" used in the template?
prop.putHTML("mode_pageid", pagename, xml);
try {
prop.putHTML("mode_author", new String(author, "UTF-8"), xml);
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_author", new String(author), xml);
}
prop.putHTML("mode_subject", post.get("subject",""), xml);
prop.put("mode_date", dateString(new Date()));
prop.putWiki("mode_page", post.get("content", ""));
prop.putHTML("mode_page-code", post.get("content", ""), xml);
}
else {
prop.put("mode", "3"); //access denied (no rights)
}
}
else if(post.get("delete", "").equals("try")) {
if(hasRights) {
prop.put("mode", "4");
prop.put("mode_pageid", pagename);
try {
prop.putHTML("mode_author",new String(page.author(), "UTF-8"), xml);
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_author",new String(page.author()), xml);
}
try {
prop.putHTML("mode_subject",new String(page.subject(),"UTF-8"), xml);
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_subject",new String(page.subject()), xml);
}
}
else prop.put("mode", "3"); //access denied (no rights)
}
else if(post.containsKey("import")) {
prop.put("mode", "5");
prop.put("mode_state", "0");
}
else if(post.containsKey("xmlfile")) {
prop.put("mode", "5");
if(switchboard.blogDB.importXML(new String((byte[])post.get("xmlfile$file")))) {
prop.put("mode_state", "1");
}
else {
prop.put("mode_state", "2");
}
}
else {
// show blog-entry/entries
prop.put("mode", "0"); //viewing
if(pagename.equals(DEFAULT_PAGE)) {
// XXX: where are "peername" and "address" used in the template?
// XXX: "clientname" is already set to the peername, no need for a new setting
prop.putHTML("peername", yacyCore.seedDB.mySeed().getName(), xml);
prop.put("address", address);
//index all entries
//index all entries
putBlogDefault(prop, switchboard, address, start, num, hasRights, xml);
}
else {
//only show 1 entry
prop.put("mode_entries", "1");
}
else {
//only show 1 entry
prop.put("mode_entries", "1");
putBlogEntry(prop, page, address, 0, hasRights, xml);
}
}
}
}
// return rewrite properties
return prop;
}
// return rewrite properties
return prop;
}
private static serverObjects putBlogDefault(
final serverObjects prop,
final plasmaSwitchboard switchboard,
@ -286,7 +289,8 @@ public class Blog {
int start,
int num,
final boolean hasRights,
final boolean xml) {
final boolean xml)
{
try {
final Iterator i = switchboard.blogDB.keys(false);
String pageid;
@ -305,7 +309,7 @@ public class Blog {
xml);
}
prop.put("mode_entries", count);
if(i.hasNext()) {
prop.put("mode_moreentries", "1"); //more entries are availible
prop.put("mode_moreentries_start", nextstart);
@ -316,29 +320,29 @@ public class Blog {
} catch (IOException e) { serverLog.logSevere("BLOG", "Error reading blog-DB", e); }
return prop;
}
private static serverObjects putBlogEntry(
final serverObjects prop,
final blogBoard.entry entry,
final String address,
final int number,
final boolean hasRights,
final boolean xml) {
final boolean xml)
{
// subject
try {
prop.putHTML("mode_entries_" + number + "_subject", new String(entry.subject(),"UTF-8"), xml);
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_entries_" + number + "_subject", new String(entry.subject()), xml);
}
// author
try {
prop.putHTML("mode_entries_" + number + "_author", new String(entry.author(),"UTF-8"), xml);
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_entries_" + number + "_author", new String(entry.author()), xml);
}
// comments
if(entry.getCommentMode() == 0) {
prop.put("mode_entries_" + number + "_commentsactive", "0");
@ -352,27 +356,27 @@ public class Blog {
prop.put("mode_entries_" + number + "_commentsactive_comments", new String(entry.commentsSize()));
}
}
prop.put("mode_entries_" + number + "_date", dateString(entry.date()));
prop.put("mode_entries_" + number + "_rfc822date", httpc.dateString(entry.date()));
prop.put("mode_entries_" + number + "_pageid", entry.key());
prop.put("mode_entries_" + number + "_address", address);
prop.put("mode_entries_" + number + "_ip", entry.ip());
if(xml) {
prop.put("mode_entries_" + number + "_page", entry.page());
prop.put("mode_entries_" + number + "_timestamp", entry.timestamp());
} else {
prop.putWiki("mode_entries_" + number + "_page", entry.page());
}
if(hasRights) {
prop.put("mode_entries_" + number + "_admin", "1");
prop.put("mode_entries_" + number + "_admin_pageid",entry.key());
} else {
prop.put("mode_entries_" + number + "_admin", "0");
}
return prop;
}
}

@ -1,4 +1,4 @@
// Blog.java
// Blog.java
// -----------------------
// part of YACY
// (C) by Michael Peter Christen; mc@anomic.de
@ -69,18 +69,18 @@ import de.anomic.yacy.yacyCore;
public class BlogComments {
private static SimpleDateFormat SimpleFormatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
// TODO: make userdefined date/time-strings (localisation)
private static SimpleDateFormat SimpleFormatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
// TODO: make userdefined date/time-strings (localisation)
public static String dateString(Date date) {
return SimpleFormatter.format(date);
return SimpleFormatter.format(date);
}
public static serverObjects respond(httpHeader header, serverObjects post, serverSwitch env) {
plasmaSwitchboard switchboard = (plasmaSwitchboard) env;
serverObjects prop = new serverObjects();
blogBoard.entry page = null;
boolean hasRights = switchboard.verifyAuthentication(header, true);
public static serverObjects respond(httpHeader header, serverObjects post, serverSwitch env) {
plasmaSwitchboard switchboard = (plasmaSwitchboard) env;
serverObjects prop = new serverObjects();
blogBoard.entry page = null;
boolean hasRights = switchboard.verifyAuthentication(header, true);
if (hasRights) prop.put("mode_admin", "1");
else prop.put("mode_admin", "0");
@ -99,35 +99,36 @@ public class BlogComments {
else if(post.containsKey("login")){
prop.put("AUTHENTICATE","admin log-in");
}
}
String pagename = post.get("page", "blog_default");
String ip = post.get("CLIENTIP", "127.0.0.1");
String StrAuthor = post.get("author", "anonymous");
if (StrAuthor.equals("anonymous")) {
StrAuthor = switchboard.blogDB.guessAuthor(ip);
if (StrAuthor == null || StrAuthor.length() == 0) {
if (de.anomic.yacy.yacyCore.seedDB.mySeed() == null)
StrAuthor = "anonymous";
else {
StrAuthor = de.anomic.yacy.yacyCore.seedDB.mySeed().get("Name", "anonymous");
}
}
}
byte[] author;
try {
author = StrAuthor.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
author = StrAuthor.getBytes();
}
if (post.containsKey("submit")) {
// store a new/edited blog-entry
byte[] content;
}
String pagename = post.get("page", "blog_default");
String ip = post.get("CLIENTIP", "127.0.0.1");
String StrAuthor = post.get("author", "anonymous");
if (StrAuthor.equals("anonymous")) {
StrAuthor = switchboard.blogDB.guessAuthor(ip);
if (StrAuthor == null || StrAuthor.length() == 0) {
if (de.anomic.yacy.yacyCore.seedDB.mySeed() == null) {
StrAuthor = "anonymous";
}
else {
StrAuthor = de.anomic.yacy.yacyCore.seedDB.mySeed().get("Name", "anonymous");
}
}
}
byte[] author;
try {
author = StrAuthor.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
author = StrAuthor.getBytes();
}
if (post.containsKey("submit")) {
// store a new/edited blog-entry
byte[] content;
if(!post.get("content", "").equals(""))
{
if(post.get("subject", "").equals("")) post.putHTML("subject", "no title");
@ -138,9 +139,8 @@ public class BlogComments {
}
Date date = null;
//set name for new entry or date for old entry
String StrSubject = post.get("subject", "");
byte[] subject;
try {
@ -154,7 +154,7 @@ public class BlogComments {
switchboard.blogDB.write(blogEntry);
switchboard.blogCommentDB.write(switchboard.blogCommentDB.newEntry(commentID, subject, author, ip, date, content));
prop.put("LOCATION","BlogComments.html?page=" + pagename);
messageBoard.entry msgEntry = null;
try {
switchboard.messageDB.write(msgEntry = switchboard.messageDB.newEntry(
@ -173,7 +173,7 @@ public class BlogComments {
}
messageForwardingViaEmail(env, msgEntry);
// finally write notification
File notifierSource = new File(switchboard.getRootPath(), switchboard.getConfig("htRootPath","htroot") + "/env/grafics/message.gif");
File notifierDest = new File(switchboard.getConfigPath("htDocsPath", "DATA/HTDOCS"), "notifier.gif");
@ -181,26 +181,26 @@ public class BlogComments {
serverFileUtils.copy(notifierSource, notifierDest);
} catch (IOException e) {
serverLog.logSevere("MESSAGE", "NEW MESSAGE ARRIVED! (error: " + e.getMessage() + ")");
}
}
}
}
page = switchboard.blogDB.read(pagename); //maybe "if(page == null)"
page = switchboard.blogDB.read(pagename); //maybe "if(page == null)"
if(hasRights && post.containsKey("delete") && post.containsKey("page") && post.containsKey("comment")) {
if(page.removeComment((String) post.get("comment"))) {
switchboard.blogCommentDB.delete((String) post.get("comment"));
}
}
}
if(hasRights && post.containsKey("allow") && post.containsKey("page") && post.containsKey("comment")) {
blogBoardComments.CommentEntry entry = switchboard.blogCommentDB.read((String) post.get("comment"));
entry.allow();
switchboard.blogCommentDB.write(entry);
}
if(post.containsKey("preview")) {
//preview the page
//preview the page
prop.put("mode", "1");//preview
prop.put("mode_pageid", pagename);
try {
@ -212,37 +212,37 @@ public class BlogComments {
prop.put("mode_date", dateString(new Date()));
prop.putWiki("mode_page", post.get("content", ""));
prop.put("mode_page-code", post.get("content", ""));
}
else {
// show blog-entry/entries
prop.put("mode", "0"); //viewing
if(pagename.equals("blog_default")) {
}
else {
// show blog-entry/entries
prop.put("mode", "0"); //viewing
if(pagename.equals("blog_default")) {
prop.put("LOCATION","Blog.html");
}
else {
//show 1 blog entry
prop.put("mode_pageid", page.key());
try {
prop.putHTML("mode_subject", new String(page.subject(),"UTF-8"));
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_subject", new String(page.subject()));
}
try {
prop.putHTML("mode_author", new String(page.author(),"UTF-8"));
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_author", new String(page.author()));
}
}
else {
//show 1 blog entry
prop.put("mode_pageid", page.key());
try {
prop.putHTML("mode_subject", new String(page.subject(),"UTF-8"));
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_subject", new String(page.subject()));
}
try {
prop.putHTML("mode_author", new String(page.author(),"UTF-8"));
} catch (UnsupportedEncodingException e) {
prop.putHTML("mode_author", new String(page.author()));
}
try {
prop.put("mode_comments", new String(page.commentsSize(),"UTF-8"));
} catch (UnsupportedEncodingException e) {
prop.put("mode_comments", new String(page.commentsSize()));
}
prop.put("mode_date", dateString(page.date()));
prop.putWiki("mode_page", page.page());
if(hasRights) {
prop.put("mode_admin", "1");
prop.put("mode_admin_pageid", page.key());
}
prop.put("mode_date", dateString(page.date()));
prop.putWiki("mode_page", page.page());
if(hasRights) {
prop.put("mode_admin", "1");
prop.put("mode_admin_pageid", page.key());
}
//show all commments
try {
Iterator i = page.comments().iterator();
@ -250,8 +250,9 @@ public class BlogComments {
String pageid;
blogBoardComments.CommentEntry entry;
boolean xml = false;
if(post.containsKey("xml"))
if(post.containsKey("xml")) {
xml = true;
}
int count = 0; //counts how many entries are shown to the user
int start = post.getInt("start",0); //indicates from where entries should be shown
int num = post.getInt("num",20); //indicates how many entries should be shown
@ -267,7 +268,7 @@ public class BlogComments {
if (commentMode == 2 && !hasRights && !entry.isAllowed())
continue;
prop.put("mode", "0");
prop.put("mode_entries_"+count+"_pageid", entry.key());
if(!xml) {
@ -307,20 +308,20 @@ public class BlogComments {
} catch (IOException e) {
}
}
}
}
}
// return rewrite properties
return prop;
}
// return rewrite properties
return prop;
}
private static void messageForwardingViaEmail(serverSwitch env, messageBoard.entry msgEntry) {
try {
if (!Boolean.valueOf(env.getConfig("msgForwardingEnabled","false")).booleanValue()) return;
// getting the recipient address
String sendMailTo = env.getConfig("msgForwardingTo","root@localhost").trim();
// getting the sendmail configuration
String sendMailStr = env.getConfig("msgForwardingCmd","/usr/bin/sendmail")+" "+sendMailTo;
String[] sendMail = sendMailStr.trim().split(" ");
@ -353,10 +354,9 @@ public class BlogComments {
Process process=Runtime.getRuntime().exec(sendMail);
PrintWriter email = new PrintWriter(process.getOutputStream());
email.print(new String(emailTxt));
email.close();
email.close();
} catch (Exception e) {
yacyCore.log.logWarning("message: message forwarding via email failed. ",e);
}
}
}

Loading…
Cancel
Save