From 5cd0108f93d9b91e30b74ec92e66571493a2b997 Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Thu, 8 Apr 2010 12:54:14 +0000
Subject: [PATCH] 2010-04-08  Tatsuhiro Tsujikawa 
 <t-tujikawa@users.sourceforge.net>

	Added --save-session=FILE option.  This option saves
	error/unfinished downloads to FILE on exit.  You can pass this
	output file to aria2c with -i option on restart. Please note that
	downloads added by aria2.addTorrent and aria2.addMetalink XML-RPC
	method are not saved.
	* src/BtPostDownloadHandler.cc
	* src/DownloadResult.h
	* src/Makefile.am
	* src/MetadataInfo.cc
	* src/MetadataInfo.h
	* src/Metalink2RequestGroup.cc
	* src/MetalinkPostDownloadHandler.cc
	* src/MultiUrlRequestInfo.cc
	* src/OptionHandlerFactory.cc
	* src/RequestGroup.cc
	* src/RequestGroup.h
	* src/SessionSerializer.cc
	* src/SessionSerializer.h
	* src/UTMetadataPostDownloadHandler.cc
	* src/download_helper.cc
	* src/download_helper.h
	* src/prefs.cc
	* src/prefs.h
	* src/usage_text.h
	* test/Makefile.am
	* test/SessionSerializerTest.cc
	* test/XmlRpcMethodTest.cc
	* test/serialize_session.meta4
---
 ChangeLog                            |  31 ++++
 doc/aria2c.1                         |  15 +-
 doc/aria2c.1.html                    |  13 +-
 doc/aria2c.1.txt                     |   7 +
 src/BtPostDownloadHandler.cc         |   5 +
 src/DownloadResult.h                 |  14 +-
 src/Makefile.am                      |   4 +-
 src/Makefile.in                      |  29 ++--
 src/MetadataInfo.cc                  |  49 +++++++
 src/MetadataInfo.h                   |  76 ++++++++++
 src/Metalink2RequestGroup.cc         |  17 ++-
 src/MetalinkPostDownloadHandler.cc   |   6 +
 src/MultiUrlRequestInfo.cc           |  11 ++
 src/OptionHandlerFactory.cc          |   9 ++
 src/RequestGroup.cc                  |   4 +-
 src/RequestGroup.h                   |  13 ++
 src/SessionSerializer.cc             | 203 +++++++++++++++++++++++++++
 src/SessionSerializer.h              |  65 +++++++++
 src/UTMetadataPostDownloadHandler.cc |   5 +-
 src/download_helper.cc               |  29 ++++
 src/download_helper.h                |  14 ++
 src/prefs.cc                         |   6 +-
 src/prefs.h                          |   6 +-
 src/usage_text.h                     |   6 +
 test/Makefile.am                     |   3 +-
 test/Makefile.in                     |  18 ++-
 test/SessionSerializerTest.cc        |  71 ++++++++++
 test/XmlRpcMethodTest.cc             |   4 +-
 test/serialize_session.meta4         |   9 ++
 29 files changed, 707 insertions(+), 35 deletions(-)
 create mode 100644 src/MetadataInfo.cc
 create mode 100644 src/MetadataInfo.h
 create mode 100644 src/SessionSerializer.cc
 create mode 100644 src/SessionSerializer.h
 create mode 100644 test/SessionSerializerTest.cc
 create mode 100644 test/serialize_session.meta4

diff --git a/ChangeLog b/ChangeLog
index 6fdcad1b..bc92659a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,34 @@
+2010-04-08  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Added --save-session=FILE option.  This option saves
+	error/unfinished downloads to FILE on exit.  You can pass this
+	output file to aria2c with -i option on restart. Please note that
+	downloads added by aria2.addTorrent and aria2.addMetalink XML-RPC
+	method are not saved.
+	* src/BtPostDownloadHandler.cc
+	* src/DownloadResult.h
+	* src/Makefile.am
+	* src/MetadataInfo.cc
+	* src/MetadataInfo.h
+	* src/Metalink2RequestGroup.cc
+	* src/MetalinkPostDownloadHandler.cc
+	* src/MultiUrlRequestInfo.cc
+	* src/OptionHandlerFactory.cc
+	* src/RequestGroup.cc
+	* src/RequestGroup.h
+	* src/SessionSerializer.cc
+	* src/SessionSerializer.h
+	* src/UTMetadataPostDownloadHandler.cc
+	* src/download_helper.cc
+	* src/download_helper.h
+	* src/prefs.cc
+	* src/prefs.h
+	* src/usage_text.h
+	* test/Makefile.am
+	* test/SessionSerializerTest.cc
+	* test/XmlRpcMethodTest.cc
+	* test/serialize_session.meta4
+
 2010-04-07  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
 
 	Fixed the bug that FTP data connection is not established via
diff --git a/doc/aria2c.1 b/doc/aria2c.1
index c56cf6a7..478e928b 100644
--- a/doc/aria2c.1
+++ b/doc/aria2c.1
@@ -2,12 +2,12 @@
 .\"     Title: aria2c
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: 04/03/2010
+.\"      Date: 04/08/2010
 .\"    Manual: Aria2 Manual
 .\"    Source: Aria2 1.9.1a
 .\"  Language: English
 .\"
-.TH "ARIA2C" "1" "04/03/2010" "Aria2 1\&.9\&.1a" "Aria2 Manual"
+.TH "ARIA2C" "1" "04/08/2010" "Aria2 1\&.9\&.1a" "Aria2 Manual"
 .\" -----------------------------------------------------------------
 .\" * Define some portability stuff
 .\" -----------------------------------------------------------------
@@ -1354,6 +1354,17 @@ For Metalink downloads, \-C1 is recommended for proxy server which disables resu
 .sp .5v
 .RE
 .PP
+\fB\-\-save\-session\fR=FILE
+.RS 4
+Save error/unfinished downloads to FILE on exit\&. You can pass this output file to aria2c with
+\fB\-i\fR
+option on restart\&. Please note that downloads added by
+\fBaria2\&.addTorrent\fR
+and
+\fBaria2\&.addMetalink\fR
+XML\-RPC method are not saved\&.
+.RE
+.PP
 \fB\-\-stop\fR=SEC
 .RS 4
 Stop application after SEC seconds has passed\&. If
diff --git a/doc/aria2c.1.html b/doc/aria2c.1.html
index 412e7617..fadabc7b 100644
--- a/doc/aria2c.1.html
+++ b/doc/aria2c.1.html
@@ -2201,6 +2201,17 @@ connections.</td>
 </div>
 <div class="dlist"><dl>
 <dt class="hdlist1">
+<strong>--save-session</strong>=FILE
+</dt>
+<dd>
+<p>
+  Save error/unfinished downloads to FILE on exit.  You can pass this
+  output file to aria2c with <strong>-i</strong> option on restart. Please note that
+  downloads added by <strong>aria2.addTorrent</strong> and <strong>aria2.addMetalink</strong>
+  XML-RPC method are not saved.
+</p>
+</dd>
+<dt class="hdlist1">
 <strong>--stop</strong>=SEC
 </dt>
 <dd>
@@ -4137,7 +4148,7 @@ files in the program, then also delete it here.</p></div>
 <div id="footnotes"><hr /></div>
 <div id="footer">
 <div id="footer-text">
-Last updated 2010-04-03 13:10:23 JST
+Last updated 2010-04-08 21:48:17 JST
 </div>
 </div>
 </body>
diff --git a/doc/aria2c.1.txt b/doc/aria2c.1.txt
index 1dba74dd..7bcbad71 100644
--- a/doc/aria2c.1.txt
+++ b/doc/aria2c.1.txt
@@ -914,6 +914,13 @@ For Metalink downloads, -C1 is recommended for proxy server which
 disables resume, in order to avoid establishing unnecessary
 connections.
 
+*--save-session*=FILE::
+
+  Save error/unfinished downloads to FILE on exit.  You can pass this
+  output file to aria2c with *-i* option on restart. Please note that
+  downloads added by *aria2.addTorrent* and *aria2.addMetalink*
+  XML-RPC method are not saved.
+
 *--stop*=SEC::
   Stop application after SEC seconds has passed.
   If '0' is given, this feature is disabled.
diff --git a/src/BtPostDownloadHandler.cc b/src/BtPostDownloadHandler.cc
index 0281e9f6..2398d7f7 100644
--- a/src/BtPostDownloadHandler.cc
+++ b/src/BtPostDownloadHandler.cc
@@ -82,6 +82,11 @@ void BtPostDownloadHandler::getNextRequestGroups
                                   std::vector<std::string>(),
                                   content);
   requestGroup->followedBy(newRgs.begin(), newRgs.end());
+  SharedHandle<MetadataInfo> mi =
+    createMetadataInfoFromFirstFileEntry(requestGroup->getDownloadContext());
+  if(!mi.isNull()) {
+    setMetadataInfo(newRgs.begin(), newRgs.end(), mi);
+  }
   groups.insert(groups.end(), newRgs.begin(), newRgs.end());
 }
 
diff --git a/src/DownloadResult.h b/src/DownloadResult.h
index 7318d946..54c0f924 100644
--- a/src/DownloadResult.h
+++ b/src/DownloadResult.h
@@ -45,6 +45,8 @@
 #include "SharedHandle.h"
 #include "DownloadResultCode.h"
 #include "RequestGroup.h"
+#include "Option.h"
+#include "MetadataInfo.h"
 
 namespace aria2 {
 
@@ -74,6 +76,10 @@ public:
   // RequestGroup.cc::_belongsToGID.
   gid_t belongsTo;
 
+  SharedHandle<Option> option;
+
+  SharedHandle<MetadataInfo> metadataInfo;
+
   DownloadResult(gid_t gid,
                  const std::vector<SharedHandle<FileEntry> >& fileEntries,
                  bool inMemoryDownload,
@@ -81,7 +87,9 @@ public:
                  int64_t sessionTime,
                  downloadresultcode::RESULT result,
                  const std::vector<gid_t> followedBy,
-                 gid_t belongsTo):
+                 gid_t belongsTo,
+                 const SharedHandle<Option>& option,
+                 const SharedHandle<MetadataInfo>& metadataInfo):
     gid(gid),
     fileEntries(fileEntries),
     inMemoryDownload(inMemoryDownload),
@@ -89,7 +97,9 @@ public:
     sessionTime(sessionTime),
     result(result),
     followedBy(followedBy),
-    belongsTo(belongsTo) {}
+    belongsTo(belongsTo),
+    option(option),
+    metadataInfo(metadataInfo) {}
 };
 
 typedef SharedHandle<DownloadResult> DownloadResultHandle;
diff --git a/src/Makefile.am b/src/Makefile.am
index b77f82e8..bd527c6f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -198,7 +198,9 @@ SRCS =  Socket.h\
 	CreateRequestCommand.cc CreateRequestCommand.h\
 	DownloadResultCode.h\
 	wallclock.h\
-	download_helper.cc download_helper.h
+	download_helper.cc download_helper.h\
+	MetadataInfo.cc MetadataInfo.h\
+	SessionSerializer.cc SessionSerializer.h
 
 if ENABLE_XML_RPC
 SRCS += XmlRpcRequestParserController.cc XmlRpcRequestParserController.h\
diff --git a/src/Makefile.in b/src/Makefile.in
index c2e33782..aca935f9 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -429,7 +429,8 @@ am__libaria2c_a_SOURCES_DIST = Socket.h SocketCore.cc SocketCore.h \
 	LongestSequencePieceSelector.cc LongestSequencePieceSelector.h \
 	bitfield.cc bitfield.h BDE.cc BDE.h CreateRequestCommand.cc \
 	CreateRequestCommand.h DownloadResultCode.h wallclock.h \
-	download_helper.cc download_helper.h \
+	download_helper.cc download_helper.h MetadataInfo.cc \
+	MetadataInfo.h SessionSerializer.cc SessionSerializer.h \
 	XmlRpcRequestParserController.cc \
 	XmlRpcRequestParserController.h \
 	XmlRpcRequestParserStateMachine.cc \
@@ -855,7 +856,8 @@ am__objects_27 = SocketCore.$(OBJEXT) Command.$(OBJEXT) \
 	URIResult.$(OBJEXT) SelectEventPoll.$(OBJEXT) \
 	LongestSequencePieceSelector.$(OBJEXT) bitfield.$(OBJEXT) \
 	BDE.$(OBJEXT) CreateRequestCommand.$(OBJEXT) \
-	download_helper.$(OBJEXT) $(am__objects_1) $(am__objects_2) \
+	download_helper.$(OBJEXT) MetadataInfo.$(OBJEXT) \
+	SessionSerializer.$(OBJEXT) $(am__objects_1) $(am__objects_2) \
 	$(am__objects_3) $(am__objects_4) $(am__objects_5) \
 	$(am__objects_6) $(am__objects_7) $(am__objects_8) \
 	$(am__objects_9) $(am__objects_10) $(am__objects_11) \
@@ -1189,16 +1191,17 @@ SRCS = Socket.h SocketCore.cc SocketCore.h BinaryStream.h Command.cc \
 	LongestSequencePieceSelector.cc LongestSequencePieceSelector.h \
 	bitfield.cc bitfield.h BDE.cc BDE.h CreateRequestCommand.cc \
 	CreateRequestCommand.h DownloadResultCode.h wallclock.h \
-	download_helper.cc download_helper.h $(am__append_1) \
-	$(am__append_2) $(am__append_3) $(am__append_4) \
-	$(am__append_5) $(am__append_6) $(am__append_7) \
-	$(am__append_8) $(am__append_9) $(am__append_10) \
-	$(am__append_11) $(am__append_12) $(am__append_13) \
-	$(am__append_14) $(am__append_15) $(am__append_16) \
-	$(am__append_17) $(am__append_18) $(am__append_19) \
-	$(am__append_20) $(am__append_21) $(am__append_22) \
-	$(am__append_23) $(am__append_24) $(am__append_25) \
-	$(am__append_26)
+	download_helper.cc download_helper.h MetadataInfo.cc \
+	MetadataInfo.h SessionSerializer.cc SessionSerializer.h \
+	$(am__append_1) $(am__append_2) $(am__append_3) \
+	$(am__append_4) $(am__append_5) $(am__append_6) \
+	$(am__append_7) $(am__append_8) $(am__append_9) \
+	$(am__append_10) $(am__append_11) $(am__append_12) \
+	$(am__append_13) $(am__append_14) $(am__append_15) \
+	$(am__append_16) $(am__append_17) $(am__append_18) \
+	$(am__append_19) $(am__append_20) $(am__append_21) \
+	$(am__append_22) $(am__append_23) $(am__append_24) \
+	$(am__append_25) $(am__append_26)
 noinst_LIBRARIES = libaria2c.a
 libaria2c_a_SOURCES = $(SRCS)
 aria2c_LDADD = libaria2c.a @LIBINTL@ @ALLOCA@ @LIBGNUTLS_LIBS@\
@@ -1495,6 +1498,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MSEHandshake.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MemoryBufferPreDownloadHandler.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MessageDigestHelper.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MetadataInfo.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Metalink2RequestGroup.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MetalinkEntry.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MetalinkHelper.Po@am__quote@
@@ -1552,6 +1556,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SelectEventPoll.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ServerStat.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ServerStatMan.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SessionSerializer.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Signature.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SimpleBtMessage.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SimpleLogger.Po@am__quote@
diff --git a/src/MetadataInfo.cc b/src/MetadataInfo.cc
new file mode 100644
index 00000000..dc3bc3c3
--- /dev/null
+++ b/src/MetadataInfo.cc
@@ -0,0 +1,49 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2010 Tatsuhiro Tsujikawa
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#include "MetadataInfo.h"
+
+namespace aria2 {
+
+int64_t MetadataInfo::_count = 0;
+
+int64_t MetadataInfo::genId()
+{
+  if(_count == INT64_MAX) {
+    _count = 0;
+  }
+  return ++_count;
+}
+
+} // namespace aria2
diff --git a/src/MetadataInfo.h b/src/MetadataInfo.h
new file mode 100644
index 00000000..4e8293c6
--- /dev/null
+++ b/src/MetadataInfo.h
@@ -0,0 +1,76 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2010 Tatsuhiro Tsujikawa
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#ifndef _D_METADATA_INFO_H_
+#define _D_METADATA_INFO_H_
+
+#include "common.h"
+
+#include <string>
+
+namespace aria2 {
+
+class MetadataInfo {
+private:
+  int64_t _id;
+  std::string _uri;
+  bool _dataOnly;
+  static int64_t _count;
+public:
+  MetadataInfo(const std::string& uri):_id(genId()), _uri(uri), _dataOnly(false)
+  {}
+
+  MetadataInfo():_id(genId()), _dataOnly(true) {}
+
+  bool dataOnly() const
+  {
+    return _dataOnly;
+  }
+
+  const std::string& getUri() const
+  {
+    return _uri;
+  }
+
+  int64_t getId() const
+  {
+    return _id;
+  }
+  
+  static int64_t genId();
+};
+
+} // namespace aria2
+
+#endif // _D_METADATA_INFO_H_
diff --git a/src/Metalink2RequestGroup.cc b/src/Metalink2RequestGroup.cc
index df275a81..f2413f11 100644
--- a/src/Metalink2RequestGroup.cc
+++ b/src/Metalink2RequestGroup.cc
@@ -109,7 +109,16 @@ Metalink2RequestGroup::generate
 {
   std::vector<SharedHandle<MetalinkEntry> > entries;
   MetalinkHelper::parseAndQuery(entries, metalinkFile, option.get());
-  createRequestGroup(groups, entries, option);
+  std::vector<SharedHandle<RequestGroup> > tempgroups;
+  createRequestGroup(tempgroups, entries, option);
+  SharedHandle<MetadataInfo> mi;
+  if(metalinkFile == "-") {
+    mi.reset(new MetadataInfo());
+  } else {
+    mi.reset(new MetadataInfo(metalinkFile));
+  }
+  setMetadataInfo(tempgroups.begin(), tempgroups.end(), mi);
+  groups.insert(groups.end(), tempgroups.begin(), tempgroups.end());
 }
 
 void
@@ -120,7 +129,11 @@ Metalink2RequestGroup::generate
 {
   std::vector<SharedHandle<MetalinkEntry> > entries;
   MetalinkHelper::parseAndQuery(entries, binaryStream, option.get());
-  createRequestGroup(groups, entries, option);
+  std::vector<SharedHandle<RequestGroup> > tempgroups;
+  createRequestGroup(tempgroups, entries, option);
+  SharedHandle<MetadataInfo> mi(new MetadataInfo());
+  setMetadataInfo(tempgroups.begin(), tempgroups.end(), mi);
+  groups.insert(groups.end(), tempgroups.begin(), tempgroups.end());
 }
 
 void
diff --git a/src/MetalinkPostDownloadHandler.cc b/src/MetalinkPostDownloadHandler.cc
index 5f43c709..8806aabc 100644
--- a/src/MetalinkPostDownloadHandler.cc
+++ b/src/MetalinkPostDownloadHandler.cc
@@ -44,6 +44,7 @@
 #include "prefs.h"
 #include "Option.h"
 #include "DownloadContext.h"
+#include "download_helper.h"
 
 namespace aria2 {
 
@@ -77,6 +78,11 @@ void MetalinkPostDownloadHandler::getNextRequestGroups
     Metalink2RequestGroup().generate(newRgs, diskAdaptor,
                                      requestGroup->getOption());
     requestGroup->followedBy(newRgs.begin(), newRgs.end());
+    SharedHandle<MetadataInfo> mi =
+      createMetadataInfoFromFirstFileEntry(requestGroup->getDownloadContext());
+    if(!mi.isNull()) {
+      setMetadataInfo(newRgs.begin(), newRgs.end(), mi);
+    }
     groups.insert(groups.end(), newRgs.begin(), newRgs.end());
     diskAdaptor->closeFile();
   } catch(Exception& e) {
diff --git a/src/MultiUrlRequestInfo.cc b/src/MultiUrlRequestInfo.cc
index b70ebfb1..35e1be22 100644
--- a/src/MultiUrlRequestInfo.cc
+++ b/src/MultiUrlRequestInfo.cc
@@ -55,6 +55,8 @@
 #include "Netrc.h"
 #include "AuthConfigFactory.h"
 #include "DownloadContext.h"
+#include "SessionSerializer.h"
+#include "ServerStatMan.h"
 #ifdef ENABLE_SSL
 # include "SocketCore.h"
 # include "TLSContext.h"
@@ -193,6 +195,15 @@ downloadresultcode::RESULT MultiUrlRequestInfo::execute()
         returnValue = s.getLastErrorResult();
       }
     }
+    SessionSerializer sessionSerializer(e->_requestGroupMan);
+    // TODO Add option: --save-session-status=error,inprogress,waiting
+    if(!_option->blank(PREF_SAVE_SESSION)) {
+      if(sessionSerializer.save(_option->get(PREF_SAVE_SESSION))) {
+        _logger->notice("Serialized session successfully.");
+      } else {
+        _logger->notice("Failed to serialize session.");
+      }
+    }
   } catch(RecoverableException& e) {
     if(returnValue == downloadresultcode::FINISHED) {
       returnValue = downloadresultcode::UNKNOWN_ERROR;
diff --git a/src/OptionHandlerFactory.cc b/src/OptionHandlerFactory.cc
index 2dabd7c6..98ad419f 100644
--- a/src/OptionHandlerFactory.cc
+++ b/src/OptionHandlerFactory.cc
@@ -442,6 +442,15 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers()
     op->addTag(TAG_ADVANCED);
     handlers.push_back(op);
   }
+  {
+    SharedHandle<OptionHandler> op(new DefaultOptionHandler
+                                   (PREF_SAVE_SESSION,
+                                    TEXT_SAVE_SESSION,
+                                    NO_DEFAULT_VALUE,
+                                    "FILENAME"));
+    op->addTag(TAG_ADVANCED);
+    handlers.push_back(op);
+  }
   {
     SharedHandle<OptionHandler> op(new NumberOptionHandler
                                    (PREF_STOP,
diff --git a/src/RequestGroup.cc b/src/RequestGroup.cc
index fef4f88c..76e5a33a 100644
--- a/src/RequestGroup.cc
+++ b/src/RequestGroup.cc
@@ -1050,7 +1050,9 @@ DownloadResultHandle RequestGroup::createDownloadResult() const
                         _downloadContext->calculateSessionTime(),
                         downloadResult(),
                         _followedByGIDs,
-                        _belongsToGID));
+                        _belongsToGID,
+                        _option,
+                        _metadataInfo));
 }
   
 void RequestGroup::reportDownloadFinished()
diff --git a/src/RequestGroup.h b/src/RequestGroup.h
index 1c940d20..569b1172 100644
--- a/src/RequestGroup.h
+++ b/src/RequestGroup.h
@@ -46,6 +46,7 @@
 #include "TimeA2.h"
 #include "Request.h"
 #include "DownloadResultCode.h"
+#include "MetadataInfo.h"
 
 namespace aria2 {
 
@@ -165,6 +166,8 @@ private:
   // RequestGroup.
   gid_t _belongsToGID;
 
+  SharedHandle<MetadataInfo> _metadataInfo;
+
   RequestGroupMan* _requestGroupMan;
 
   int _resumeFailureCount;
@@ -514,6 +517,16 @@ public:
 
   bool p2pInvolved() const;
 
+  void setMetadataInfo(const SharedHandle<MetadataInfo>& info)
+  {
+    _metadataInfo = info;
+  }
+
+  const SharedHandle<MetadataInfo>& getMetadataInfo() const
+  {
+    return _metadataInfo;
+  }
+
   static void resetGIDCounter() { _gidCounter = 0; }
 
   static gid_t newGID();
diff --git a/src/SessionSerializer.cc b/src/SessionSerializer.cc
new file mode 100644
index 00000000..45db0e15
--- /dev/null
+++ b/src/SessionSerializer.cc
@@ -0,0 +1,203 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2010 Tatsuhiro Tsujikawa
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#include "SessionSerializer.h"
+
+#include <fstream>
+#include <iterator>
+
+#include "RequestGroupMan.h"
+#include "ServerStatMan.h"
+#include "a2functional.h"
+#include "File.h"
+#include "A2STR.h"
+#include "download_helper.h"
+#include "Option.h"
+#include "DownloadResult.h"
+#include "FileEntry.h"
+#include "prefs.h"
+#include "util.h"
+#include "array_fun.h"
+
+namespace aria2 {
+
+SessionSerializer::SessionSerializer
+(const SharedHandle<RequestGroupMan>& requestGroupMan):
+  _rgman(requestGroupMan),
+  _saveError(true),
+  _saveInProgress(true),
+  _saveWaiting(true) {}
+
+bool SessionSerializer::save(const std::string& filename) const
+{
+  std::string tempFilename = strconcat(filename, "__temp");
+  std::ofstream out(tempFilename.c_str(), std::ios::binary);
+  if(!out) {
+    return false;
+  }
+  save(out);
+  out.flush();
+  if(!out) {
+    return false;
+  }
+  return File(tempFilename).renameTo(filename);
+}
+
+
+static const std::vector<std::string>& getCumulativeOpts()
+{
+  static std::string cumulativeOpts[] = { PREF_INDEX_OUT, PREF_HEADER };
+  static std::vector<std::string> opts
+    (vbegin(cumulativeOpts), vend(cumulativeOpts));
+  return opts;
+}
+
+static bool inCumulativeOpts(const std::string& opt)
+{
+  const std::vector<std::string>& cumopts = getCumulativeOpts();
+  for(std::vector<std::string>::const_iterator itr = cumopts.begin(),
+        eoi = cumopts.end(); itr != eoi; ++itr) {
+    if(opt == *itr) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static void writeOption(std::ostream& out, const SharedHandle<Option>& op)
+{
+  const std::set<std::string>& requestOptions = listRequestOptions();
+  for(std::set<std::string>::const_iterator itr = requestOptions.begin(),
+        eoi = requestOptions.end(); itr != eoi; ++itr) {
+    if(inCumulativeOpts(*itr)) {
+      continue;
+    }
+    if(op->defined(*itr)) {
+      out << " " << *itr << "=" << op->get(*itr) << "\n";
+    }
+  }
+  const std::vector<std::string>& cumopts = getCumulativeOpts();
+  for(std::vector<std::string>::const_iterator opitr = cumopts.begin(),
+        eoi = cumopts.end(); opitr != eoi; ++opitr) {
+    if(op->defined(*opitr)) {
+      std::vector<std::string> v;
+      util::split(op->get(*opitr), std::back_inserter(v), "\n",
+                  false, false);
+      for(std::vector<std::string>::const_iterator i = v.begin(), eoi = v.end();
+          i != eoi; ++i) {
+        out << " " << *opitr << "=" << *i << "\n";
+      }
+    }
+  }
+}
+
+static void writeDownloadResult
+(std::ostream& out, std::set<int64_t>& metainfoCache,
+ const SharedHandle<DownloadResult>& dr)
+{
+  const SharedHandle<MetadataInfo>& mi = dr->metadataInfo;
+  if(dr->belongsTo != 0 || (!mi.isNull() && mi->dataOnly())) {
+    return;
+  }
+  if(mi.isNull()) {
+    // only save first file entry
+    if(dr->fileEntries.empty()) {
+      return;
+    }
+    const SharedHandle<FileEntry>& file = dr->fileEntries[0];
+    std::vector<std::string> uris;
+    file->getUris(uris);
+    if(uris.empty()) {
+      return;
+    }
+    std::copy(uris.begin(), uris.end(),
+              std::ostream_iterator<std::string>(out, "\t"));
+    out << "\n";
+  } else {
+    if(metainfoCache.count(mi->getId()) != 0) {
+      return;
+    } else {
+      metainfoCache.insert(mi->getId());
+      out << mi->getUri() << "\n";
+    }
+  }
+  writeOption(out, dr->option);
+}
+
+void SessionSerializer::save(std::ostream& out) const
+{
+  std::set<int64_t> metainfoCache;
+  const std::deque<SharedHandle<DownloadResult> >& results =
+    _rgman->getDownloadResults();
+  for(std::deque<SharedHandle<DownloadResult> >::const_iterator itr =
+        results.begin(), eoi = results.end(); itr != eoi; ++itr) {
+    if((*itr)->result == downloadresultcode::FINISHED) {
+      continue;
+    } else if((*itr)->result == downloadresultcode::IN_PROGRESS) {
+      if(_saveInProgress) {
+        writeDownloadResult(out, metainfoCache, *itr);
+      }
+    } else {
+      // error download
+      if(_saveError) {
+        writeDownloadResult(out, metainfoCache, *itr);
+      }
+    }
+  }
+  if(_saveInProgress) {
+    const std::deque<SharedHandle<RequestGroup> >& groups =
+      _rgman->getRequestGroups();
+    for(std::deque<SharedHandle<RequestGroup> >::const_iterator itr =
+          groups.begin(), eoi = groups.end(); itr != eoi; ++itr) {
+      SharedHandle<DownloadResult> result = (*itr)->createDownloadResult();
+      if(result->result == downloadresultcode::FINISHED) {
+        continue;
+      }
+      writeDownloadResult(out, metainfoCache, result);
+    }
+  }
+  if(_saveWaiting) {
+    const std::deque<SharedHandle<RequestGroup> >& groups =
+      _rgman->getReservedGroups();
+    for(std::deque<SharedHandle<RequestGroup> >::const_iterator itr =
+          groups.begin(), eoi = groups.end(); itr != eoi; ++itr) {
+      SharedHandle<DownloadResult> result = (*itr)->createDownloadResult();
+      writeDownloadResult(out, metainfoCache, result);
+    }
+  }
+}
+
+} // namespace aria2
+
+
diff --git a/src/SessionSerializer.h b/src/SessionSerializer.h
new file mode 100644
index 00000000..53ca392b
--- /dev/null
+++ b/src/SessionSerializer.h
@@ -0,0 +1,65 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2010 Tatsuhiro Tsujikawa
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+#ifndef _D_SESSION_SERIALIZER_H_
+#define _D_SESSION_SERIALIZER_H_
+
+#include "common.h"
+
+#include <string>
+#include <iosfwd>
+
+#include "SharedHandle.h"
+
+namespace aria2 {
+
+class RequestGroupMan;
+
+class SessionSerializer {
+private:
+  SharedHandle<RequestGroupMan> _rgman;
+  bool _saveError;
+  bool _saveInProgress;
+  bool _saveWaiting;
+public:
+  SessionSerializer(const SharedHandle<RequestGroupMan>& requestGroupMan);
+
+  bool save(const std::string& filename) const;
+
+  void save(std::ostream& out) const;
+};
+
+} // namespace aria2
+
+#endif // _D_SESSION_SERIALIZER_H_
diff --git a/src/UTMetadataPostDownloadHandler.cc b/src/UTMetadataPostDownloadHandler.cc
index 3d8f7f10..672f6995 100644
--- a/src/UTMetadataPostDownloadHandler.cc
+++ b/src/UTMetadataPostDownloadHandler.cc
@@ -96,8 +96,11 @@ void UTMetadataPostDownloadHandler::getNextRequestGroups
     std::vector<SharedHandle<RequestGroup> > newRgs;
     createRequestGroupForBitTorrent(newRgs, requestGroup->getOption(),
                                     std::vector<std::string>(), torrent);
-  
     requestGroup->followedBy(newRgs.begin(), newRgs.end());
+    if(!requestGroup->getMetadataInfo().isNull()) {
+      setMetadataInfo(newRgs.begin(), newRgs.end(),
+                      requestGroup->getMetadataInfo());
+    }
     groups.insert(groups.end(), newRgs.begin(), newRgs.end());
   }
 }
diff --git a/src/download_helper.cc b/src/download_helper.cc
index f2f6ecfb..ca43aa3d 100644
--- a/src/download_helper.cc
+++ b/src/download_helper.cc
@@ -61,6 +61,7 @@
 #include "ByteArrayDiskWriter.h"
 #include "a2functional.h"
 #include "ByteArrayDiskWriterFactory.h"
+#include "MetadataInfo.h"
 #ifdef ENABLE_BITTORRENT
 # include "bittorrent_helper.h"
 # include "BtConstants.h"
@@ -212,6 +213,16 @@ static SharedHandle<RequestGroup> createRequestGroup
   return rg;
 }
 
+static SharedHandle<MetadataInfo> createMetadataInfo(const std::string& uri)
+{
+  return SharedHandle<MetadataInfo>(new MetadataInfo(uri));
+}
+
+static SharedHandle<MetadataInfo> createMetadataInfoDataOnly()
+{
+  return SharedHandle<MetadataInfo>(new MetadataInfo());
+}
+
 #ifdef ENABLE_BITTORRENT
 
 static
@@ -226,10 +237,12 @@ createBtRequestGroup(const std::string& torrentFilePath,
   dctx->setDir(option->get(PREF_DIR));
   if(torrentData.empty()) {
     bittorrent::load(torrentFilePath, dctx, auxUris);// may throw exception
+    rg->setMetadataInfo(createMetadataInfo(torrentFilePath));
   } else {
     bittorrent::loadFromMemory(torrentData, dctx, auxUris, "default"); // may
     // throw
     // exception
+    rg->setMetadataInfo(createMetadataInfoDataOnly());
   }
   dctx->setFileFilter(util::parseIntRange(option->get(PREF_SELECT_FILE)));
   std::istringstream indexOutIn(option->get(PREF_INDEX_OUT));
@@ -272,6 +285,7 @@ createBtMagnetRequestGroup(const std::string& magnetLink,
      (new UTMetadataPostDownloadHandler()));
   rg->setDiskWriterFactory
     (SharedHandle<DiskWriterFactory>(new ByteArrayDiskWriterFactory()));
+  rg->setMetadataInfo(createMetadataInfo(magnetLink));
   return rg;
 }
 
@@ -476,4 +490,19 @@ void createRequestGroupForUriList
   }
 }
 
+SharedHandle<MetadataInfo>
+createMetadataInfoFromFirstFileEntry(const SharedHandle<DownloadContext>& dctx)
+{
+  if(dctx->getFileEntries().empty()) {
+    return SharedHandle<MetadataInfo>();
+  } else {
+    std::vector<std::string> uris;
+    dctx->getFileEntries()[0]->getUris(uris);
+    if(uris.empty()) {
+      return SharedHandle<MetadataInfo>();
+    }
+    return SharedHandle<MetadataInfo>(new MetadataInfo(uris[0]));
+  }
+}
+
 } // namespace aria2
diff --git a/src/download_helper.h b/src/download_helper.h
index e6d00dd4..c3f34827 100644
--- a/src/download_helper.h
+++ b/src/download_helper.h
@@ -47,6 +47,8 @@ namespace aria2 {
 
 class RequestGroup;
 class Option;
+class MetadataInfo;
+class DownloadContext;
 
 const std::set<std::string>& listRequestOptions();
 
@@ -90,6 +92,18 @@ void createRequestGroupForUri
  bool ignoreForceSequential = false,
  bool ignoreLocalPath = false);
 
+template<typename InputIterator>
+void setMetadataInfo
+(InputIterator first, InputIterator last, const SharedHandle<MetadataInfo>& mi)
+{
+  for(; first != last; ++first) {
+    (*first)->setMetadataInfo(mi);
+  }
+}
+
+SharedHandle<MetadataInfo>
+createMetadataInfoFromFirstFileEntry(const SharedHandle<DownloadContext>& dctx);
+
 } // namespace aria2
 
 #endif // _D_DOWNLOAD_HELPER_H_
diff --git a/src/prefs.cc b/src/prefs.cc
index 8d82f024..5beeb0e5 100644
--- a/src/prefs.cc
+++ b/src/prefs.cc
@@ -184,8 +184,8 @@ const std::string PREF_REMOVE_CONTROL_FILE("remove-control-file");
 const std::string PREF_ALWAYS_RESUME("always-resume");
 // value: 1*digit
 const std::string PREF_MAX_RESUME_FAILURE_TRIES("max-resume-failure-tries");
-// value: true | false
-const std::string PREF_HTTP_ACCEPT_GZIP("http-accept-gzip");
+// value: string that your file system recognizes as a file name.
+const std::string PREF_SAVE_SESSION("save-session");
 
 /**
  * FTP related preferences
@@ -234,6 +234,8 @@ const std::string PREF_USE_HEAD("use-head");
 const std::string PREF_HTTP_AUTH_CHALLENGE("http-auth-challenge");
 // value: true | false
 const std::string PREF_HTTP_NO_CACHE("http-no-cache");
+// value: true | false
+const std::string PREF_HTTP_ACCEPT_GZIP("http-accept-gzip");
 
 /** 
  * Proxy related preferences
diff --git a/src/prefs.h b/src/prefs.h
index 35a289ed..189bb762 100644
--- a/src/prefs.h
+++ b/src/prefs.h
@@ -188,8 +188,8 @@ extern const std::string PREF_REMOVE_CONTROL_FILE;
 extern const std::string PREF_ALWAYS_RESUME;
 // value: 1*digit
 extern const std::string PREF_MAX_RESUME_FAILURE_TRIES;
-// value: true | false
-extern const std::string PREF_HTTP_ACCEPT_GZIP;
+// value: string that your file system recognizes as a file name.
+extern const std::string PREF_SAVE_SESSION;
 
 /**
  * FTP related preferences
@@ -238,6 +238,8 @@ extern const std::string PREF_USE_HEAD;
 extern const std::string PREF_HTTP_AUTH_CHALLENGE;
 // value: true | false
 extern const std::string PREF_HTTP_NO_CACHE;
+// value: true | false
+extern const std::string PREF_HTTP_ACCEPT_GZIP;
 
 /**;
  * Proxy related preferences
diff --git a/src/usage_text.h b/src/usage_text.h
index deb21ba0..e9d9f2b2 100644
--- a/src/usage_text.h
+++ b/src/usage_text.h
@@ -672,3 +672,9 @@
     "                              and inflate response if remote server responds\n" \
     "                              with 'Content-Encoding: gzip' or\n"  \
     "                              'Content-Encoding: deflate'.")
+#define TEXT_SAVE_SESSION                       \
+  _(" --save-session=FILE          Save error/unfinished downloads to FILE on exit.\n" \
+    "                              You can pass this output file to aria2c with -i\n" \
+    "                              option on restart. Please note that downloads\n" \
+    "                              added by aria2.addTorrent and aria2.addMetalink\n" \
+    "                              XML-RPC method are not saved.")
diff --git a/test/Makefile.am b/test/Makefile.am
index df411531..6f3ff423 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -71,7 +71,8 @@ aria2c_SOURCES = AllTest.cc\
 	a2algoTest.cc\
 	bitfieldTest.cc\
 	BDETest.cc\
-	DownloadContextTest.cc
+	DownloadContextTest.cc\
+	SessionSerializerTest.cc
 
 if ENABLE_XML_RPC
 aria2c_SOURCES += XmlRpcRequestParserControllerTest.cc\
diff --git a/test/Makefile.in b/test/Makefile.in
index 26777ae6..767ca9ce 100644
--- a/test/Makefile.in
+++ b/test/Makefile.in
@@ -213,7 +213,8 @@ am__aria2c_SOURCES_DIST = AllTest.cc TestUtil.cc TestUtil.h \
 	RarestPieceSelectorTest.cc PieceStatManTest.cc \
 	InOrderPieceSelector.h LongestSequencePieceSelectorTest.cc \
 	a2algoTest.cc bitfieldTest.cc BDETest.cc \
-	DownloadContextTest.cc XmlRpcRequestParserControllerTest.cc \
+	DownloadContextTest.cc SessionSerializerTest.cc \
+	XmlRpcRequestParserControllerTest.cc \
 	XmlRpcRequestProcessorTest.cc XmlRpcMethodTest.cc \
 	FallocFileAllocationIteratorTest.cc GZipDecoderTest.cc \
 	GZipEncoderTest.cc Sqlite3MozCookieParserTest.cc \
@@ -406,9 +407,10 @@ am_aria2c_OBJECTS = AllTest.$(OBJEXT) TestUtil.$(OBJEXT) \
 	RarestPieceSelectorTest.$(OBJEXT) PieceStatManTest.$(OBJEXT) \
 	LongestSequencePieceSelectorTest.$(OBJEXT) \
 	a2algoTest.$(OBJEXT) bitfieldTest.$(OBJEXT) BDETest.$(OBJEXT) \
-	DownloadContextTest.$(OBJEXT) $(am__objects_1) \
-	$(am__objects_2) $(am__objects_3) $(am__objects_4) \
-	$(am__objects_5) $(am__objects_6) $(am__objects_7)
+	DownloadContextTest.$(OBJEXT) SessionSerializerTest.$(OBJEXT) \
+	$(am__objects_1) $(am__objects_2) $(am__objects_3) \
+	$(am__objects_4) $(am__objects_5) $(am__objects_6) \
+	$(am__objects_7)
 aria2c_OBJECTS = $(am_aria2c_OBJECTS)
 am__DEPENDENCIES_1 =
 aria2c_DEPENDENCIES = ../src/libaria2c.a $(am__DEPENDENCIES_1)
@@ -639,9 +641,10 @@ aria2c_SOURCES = AllTest.cc TestUtil.cc TestUtil.h SocketCoreTest.cc \
 	RarestPieceSelectorTest.cc PieceStatManTest.cc \
 	InOrderPieceSelector.h LongestSequencePieceSelectorTest.cc \
 	a2algoTest.cc bitfieldTest.cc BDETest.cc \
-	DownloadContextTest.cc $(am__append_1) $(am__append_2) \
-	$(am__append_3) $(am__append_4) $(am__append_5) \
-	$(am__append_6) $(am__append_7)
+	DownloadContextTest.cc SessionSerializerTest.cc \
+	$(am__append_1) $(am__append_2) $(am__append_3) \
+	$(am__append_4) $(am__append_5) $(am__append_6) \
+	$(am__append_7)
 
 #aria2c_CXXFLAGS = ${CPPUNIT_CFLAGS} -I../src -I../lib -Wall -D_FILE_OFFSET_BITS=64
 #aria2c_LDFLAGS = ${CPPUNIT_LIBS}
@@ -870,6 +873,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SequentialPickerTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ServerStatManTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ServerStatTest.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SessionSerializerTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ShareRatioSeedCriteriaTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SharedHandleTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SignatureTest.Po@am__quote@
diff --git a/test/SessionSerializerTest.cc b/test/SessionSerializerTest.cc
new file mode 100644
index 00000000..10b96618
--- /dev/null
+++ b/test/SessionSerializerTest.cc
@@ -0,0 +1,71 @@
+#include "SessionSerializer.h"
+
+#include <iostream>
+#include <sstream>
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#include "RequestGroupMan.h"
+#include "ServerStatMan.h"
+#include "array_fun.h"
+#include "download_helper.h"
+#include "FileEntry.h"
+#include "prefs.h"
+
+namespace aria2 {
+
+class SessionSerializerTest:public CppUnit::TestFixture {
+
+  CPPUNIT_TEST_SUITE(SessionSerializerTest);
+  CPPUNIT_TEST(testSave);
+  CPPUNIT_TEST_SUITE_END();
+public:
+  void testSave();
+};
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SessionSerializerTest);
+
+void SessionSerializerTest::testSave()
+{
+#if defined(ENABLE_BITTORRENT) && defined(ENABLE_METALINK)
+  const std::string URIs[] =
+    { "http://localhost/file",
+      "http://mirror/file",
+      "test.torrent",
+      "serialize_session.meta4",
+      "magnet:?xt=urn:btih:248D0A1CD08284299DE78D5C1ED359BB46717D8C"};
+  std::vector<std::string> uris(vbegin(URIs), vend(URIs));
+  std::vector<SharedHandle<RequestGroup> > result;
+  SharedHandle<Option> option(new Option());
+  option->put(PREF_DIR, "/tmp");
+  createRequestGroupForUri(result, option, uris);
+  CPPUNIT_ASSERT_EQUAL((size_t)5, result.size());
+  SharedHandle<RequestGroupMan> rgman
+    (new RequestGroupMan(result, 1, option.get()));
+  SessionSerializer s(rgman);
+  std::stringstream ss;
+  s.save(ss);
+  std::string line;
+  std::getline(ss, line);
+  CPPUNIT_ASSERT_EQUAL(strconcat(uris[0], "\t", uris[1], "\t"), line);
+  std::getline(ss, line);
+  CPPUNIT_ASSERT_EQUAL(std::string(" dir=/tmp"), line);
+  std::getline(ss, line);
+  CPPUNIT_ASSERT_EQUAL(uris[2], line);
+  std::getline(ss, line);
+  CPPUNIT_ASSERT_EQUAL(std::string(" dir=/tmp"), line);
+  std::getline(ss, line);
+  CPPUNIT_ASSERT_EQUAL(uris[3], line);
+  std::getline(ss, line);
+  CPPUNIT_ASSERT_EQUAL(std::string(" dir=/tmp"), line);
+  std::getline(ss, line);
+  CPPUNIT_ASSERT_EQUAL(uris[4], line);
+  std::getline(ss, line);
+  CPPUNIT_ASSERT_EQUAL(std::string(" dir=/tmp"), line);
+  std::getline(ss, line);
+  CPPUNIT_ASSERT(!ss);
+#endif // defined(ENABLE_BITTORRENT) && defined(ENABLE_METALINK)
+}
+
+} // namespace aria2
diff --git a/test/XmlRpcMethodTest.cc b/test/XmlRpcMethodTest.cc
index 84c26474..74aa128e 100644
--- a/test/XmlRpcMethodTest.cc
+++ b/test/XmlRpcMethodTest.cc
@@ -679,7 +679,9 @@ void XmlRpcMethodTest::testGatherStoppedDownload()
                         1000,
                         downloadresultcode::FINISHED,
                         followedBy,
-                        2));
+                        2,
+                        SharedHandle<Option>(),
+                        SharedHandle<MetadataInfo>()));
   BDE entry = BDE::dict();
   gatherStoppedDownload(entry, d);
 
diff --git a/test/serialize_session.meta4 b/test/serialize_session.meta4
new file mode 100644
index 00000000..e4d56d52
--- /dev/null
+++ b/test/serialize_session.meta4
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metalink xmlns="urn:ietf:params:xml:ns:metalink">
+  <file name="t/README">
+    <url>http://example.org/README</url>
+  </file>
+  <file name="t/image.iso">
+    <url>http://example.org/image.iso</url>
+  </file>
+</metalink>