/*************************************************************************
*    UrBackup - Client/Server backup system
*    Copyright (C) 2011-2016 Martin Raiber
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Affero General Public License as published by
*    the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
*    You should have received a copy of the GNU Affero General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/

#include "FullFileBackup.h"
#include "database.h"
#include <vector>
#include "../Interface/Server.h"
#include "dao/ServerBackupDao.h"
#include "ClientMain.h"
#include "server_log.h"
#include "../urbackupcommon/fileclient/FileClient.h"
#include "../urbackupcommon/filelist_utils.h"
#include "server_running.h"
#include "ServerDownloadThreadGroup.h"
#include "../urbackupcommon/file_metadata.h"
#include "FileMetadataDownloadThread.h"
#include "snapshot_helper.h"
#include <stack>
#include "PhashLoad.h"
#include "server.h"

extern std::string server_identity;


FullFileBackup::FullFileBackup( ClientMain* client_main, int clientid, std::string clientname, std::string clientsubname,
	LogAction log_action, int group, bool use_tmpfiles, std::string tmpfile_path, bool use_reflink, bool use_snapshots,
	std::string server_token, std::string details, bool scheduled)
	: FileBackup(client_main, clientid, clientname, clientsubname, log_action, false, group,
	use_tmpfiles, tmpfile_path, use_reflink, use_snapshots, server_token, details, scheduled)
{

}


SBackup FullFileBackup::getLastFullDurations( void )
{
	std::vector<ServerBackupDao::SDuration> durations = 
		backup_dao->getLastFullDurations(clientid);

	ServerBackupDao::SDuration duration = interpolateDurations(durations);

	SBackup b;

	b.indexing_time_ms = duration.indexing_time_ms;
	b.backup_time_ms = duration.duration*1000;

	return b;
}

bool FullFileBackup::doFileBackup()
{
	ServerLogger::Log(logid, std::string("Starting ") + (scheduled ? "scheduled" : "unscheduled") + " full file backup...", LL_INFO);

	SBackup last_backup_info = getLastFullDurations();

	int64 eta_set_time = Server->getTimeMS();
	ServerStatus::setProcessEta(clientname, status_id, last_backup_info.backup_time_ms + last_backup_info.indexing_time_ms, eta_set_time);

	int64 indexing_start_time = Server->getTimeMS();

	bool no_backup_dirs=false;
	bool connect_fail=false;
	bool b=request_filelist_construct(true, false, group, true, no_backup_dirs, connect_fail, clientsubname);
	if(!b)
	{
		has_early_error=true;

		if (no_backup_dirs)
		{
			backup_dao->updateClientNumIssues(ServerBackupDao::num_issues_no_backuppaths, clientid);
		}

		if(no_backup_dirs || connect_fail)
		{
			log_backup=false;
		}
		else
		{
			log_backup=true;
		}

		return false;
	}

	bool hashed_transfer=true;
	bool save_incomplete_files=false;

	if(client_main->isOnInternetConnection())
	{
		if(server_settings->getSettings()->internet_full_file_transfer_mode=="raw")
			hashed_transfer=false;
		if(server_settings->getSettings()->internet_incr_file_transfer_mode=="blockhash")
			save_incomplete_files=true;
	}
	else
	{
		if(server_settings->getSettings()->local_full_file_transfer_mode=="raw")
			hashed_transfer=false;
		if(server_settings->getSettings()->local_incr_file_transfer_mode=="blockhash")
			save_incomplete_files=true;
	}

	if(hashed_transfer)
	{
		ServerLogger::Log(logid, clientname+": Doing backup with hashed transfer...", LL_DEBUG);
	}
	else
	{
		ServerLogger::Log(logid, clientname+": Doing backup without hashed transfer...", LL_DEBUG);
	}
	std::string identity = client_main->getIdentity();
	FileClient fc(false, identity, client_main->getProtocolVersions().filesrv_protocol_version,
		client_main->isOnInternetConnection(), client_main, use_tmpfiles?NULL:this);
	_u32 rc=client_main->getClientFilesrvConnection(&fc, server_settings.get(), 60000);
	if(rc!=ERR_CONNECTED)
	{
		ServerLogger::Log(logid, "Full Backup of "+clientname+" failed - CONNECT error", LL_ERROR);
		has_early_error=true;
		log_backup=false;
		return false;
	}
	fc.setProgressLogCallback(this);

	IFsFile* tmp_filelist = ClientMain::getTemporaryFileRetry(use_tmpfiles, tmpfile_path, logid);
	ScopedDeleteFile tmp_filelist_delete(tmp_filelist);
	if(tmp_filelist==NULL) 
	{
		ServerLogger::Log(logid, "Error creating temporary file in ::doFullBackup", LL_ERROR);
		return false;
	}

	ServerLogger::Log(logid, clientname+": Loading file list...", LL_INFO);

	int64 full_backup_starttime=Server->getTimeMS();

	rc=fc.GetFile(group>0?("urbackup/filelist_"+convert(group)+".ub"):"urbackup/filelist.ub", tmp_filelist, hashed_transfer, false, 0, false, 0);
	if(rc!=ERR_SUCCESS)
	{
		ServerLogger::Log(logid, "Error getting filelist of "+clientname+". Errorcode: "+fc.getErrorString(rc)+" ("+convert(rc)+")", LL_ERROR);
		has_early_error=true;
		return false;
	}

	getTokenFile(fc, hashed_transfer, false);

	if (!backup_dao->newFileBackup(0, clientid, backuppath_single, 0, 
		Server->getTimeMS() - indexing_start_time, group, 0, 0))
	{
		ServerLogger::Log(logid, "Error creating new backup row in database", LL_ERROR);
		has_early_error = true;
		return false;
	}

	backupid = static_cast<int>(db->getLastInsertID());

	tmp_filelist->Seek(0);

	FileListParser list_parser;

	Server->deleteFile(clientlistName(backupid));
	IFile *clientlist=Server->openFile(clientlistName(backupid), MODE_RW_CREATE);
	ScopedDeleteFile clientlist_delete(clientlist);

	if(clientlist==NULL )
	{
		ServerLogger::Log(logid, "Error creating clientlist for client "+clientname+" at "+Server->getServerWorkingDir()+ "/"+clientlistName(backupid)+". "+os_last_error_str(), LL_ERROR);
		has_early_error=true;
		return false;
	}

	if(ServerStatus::getProcess(clientname, status_id).stop)
	{
		ServerLogger::Log(logid, "Server admin stopped backup. -1", LL_ERROR);
		has_early_error=true;
		return false;
	}

	if(!startFileMetadataDownloadThread())
	{
		ServerLogger::Log(logid, "Error starting file metadata download thread", LL_ERROR);
		has_early_error=true;
		return false;
	}

	_i64 filelist_size=tmp_filelist->Size();

	char buffer[4096];
	_u32 read;
	std::string curr_path;
	std::string curr_os_path;
	std::string curr_orig_path;
	std::string orig_sep;
	SFile cf;
	int depth=0;
	int64 laststatsupdate=0;
	int64 last_eta_update=0;
	int64 last_eta_received_bytes=0;
	double eta_estimated_speed=0;
	ServerRunningUpdater *running_updater=new ServerRunningUpdater(backupid, false);
	Server->getThreadPool()->execute(running_updater, "backup progress update");

	ServerLogger::Log(logid, clientname+": Started loading files...", LL_INFO);

	bool with_sparse_hashing = client_main->getProtocolVersions().select_sha_version > 0;

	std::vector<size_t> diffs;
	bool backup_with_components;
	_i64 files_size = getIncrementalSize(tmp_filelist, diffs, backup_with_components, true);

	std::string last_backuppath;
	std::string last_backuppath_complete;
	std::unique_ptr<ServerDownloadThreadGroup> server_download(new ServerDownloadThreadGroup(fc, NULL, backuppath,
		backuppath_hashes, last_backuppath, last_backuppath_complete,
		hashed_transfer, save_incomplete_files, clientid, clientname, clientsubname,
		use_tmpfiles, tmpfile_path, server_token, use_reflink,
		backupid, false, hashpipe_prepare, client_main, client_main->getProtocolVersions().filesrv_protocol_version,
		0, logid, with_hashes, shares_without_snapshot, with_sparse_hashing, metadata_download_thread.get(),
		backup_with_components, server_settings->getSettings()->download_threads, server_settings.get(),
		false, filepath_corrections, max_file_id));

	bool queue_downloads = client_main->getProtocolVersions().filesrv_protocol_version>2;

	ServerStatus::setProcessTotalBytes(clientname, status_id, files_size);

	fc.resetReceivedDataBytes(true);
	tmp_filelist->Seek(0);

	size_t line = 0;
	int64 linked_bytes = 0;

	size_t max_ok_id=0;

	bool c_has_error=false;
	bool script_dir=false;
	bool r_offline=false;

	std::stack<std::set<std::string> > folder_files;
	folder_files.push(std::set<std::string>());
	std::vector<size_t> folder_items;
	folder_items.push_back(0);

	bool has_read_error = false;
	while( (read=tmp_filelist->Read(buffer, 4096, &has_read_error))>0 && !r_offline && !c_has_error)
	{
		if (has_read_error)
		{
			break;
		}

		for(size_t i=0;i<read;++i)
		{
			std::map<std::string, std::string> extra_params;
			bool b=list_parser.nextEntry(buffer[i], cf, &extra_params);
			if(b)
			{
				FileMetadata metadata;
				metadata.read(extra_params);

				bool has_orig_path = metadata.has_orig_path;
				if(has_orig_path)
				{
					curr_orig_path = metadata.orig_path;
					str_map::iterator it_orig_sep = extra_params.find("orig_sep");
					if(it_orig_sep!=extra_params.end())
					{
						orig_sep = it_orig_sep->second;
					}
					if(orig_sep.empty()) orig_sep="\\";
				}

				do
				{
					int64 ctime = Server->getTimeMS();
					if (ctime - laststatsupdate > status_update_intervall)
					{
						if (ServerStatus::getProcess(clientname, status_id).stop)
						{
							r_offline = true;
							should_backoff = false;
							ServerLogger::Log(logid, "Server admin stopped backup.", LL_ERROR);
							server_download->queueSkip();
							break;
						}

						laststatsupdate = ctime;
						if (files_size == 0)
						{
							ServerStatus::setProcessPcDone(clientname, status_id, 100);
						}
						else
						{
							int64 done_bytes = fc.getReceivedDataBytes(true) + linked_bytes;
							ServerStatus::setProcessDoneBytes(clientname, status_id, done_bytes);
							ServerStatus::setProcessPcDone(clientname, status_id,
								(std::min)(100, (int)(((float)done_bytes) / ((float)files_size / 100.f) + 0.5f)));
						}

						ServerStatus::setProcessQueuesize(clientname, status_id,
							(_u32)hashpipe->getNumElements(), (_u32)hashpipe_prepare->getNumElements());
					}

					if (ctime - last_eta_update > eta_update_intervall)
					{
						calculateEtaFileBackup(last_eta_update, eta_set_time, ctime, fc, NULL, linked_bytes, last_eta_received_bytes, eta_estimated_speed, files_size);
					}

					calculateDownloadSpeed(ctime, fc, NULL);

				} while (server_download->sleepQueue());

				if(server_download->isOffline())
				{
					ServerLogger::Log(logid, "Client "+clientname+" went offline.", LL_ERROR);
					r_offline=true;
					break;
				}

				std::string osspecific_name;

				if(!cf.isdir || cf.name!="..")
				{
					osspecific_name = fixFilenameForOS(cf.name, folder_files.top(), curr_path, true, logid, filepath_corrections);

					for(size_t j=0;j<folder_items.size();++j)
					{
						++folder_items[j];
					}
				}

				if(cf.isdir)
				{
					if(cf.name!="..")
					{
						std::string orig_curr_path = curr_path;
						std::string orig_curr_os_path = curr_os_path;
						curr_path+="/"+cf.name;
						curr_os_path+="/"+osspecific_name;
						std::string local_curr_os_path=convertToOSPathFromFileClient(curr_os_path);

						if(!has_orig_path)
						{
							if (curr_orig_path != orig_sep)
							{
								curr_orig_path += orig_sep;
							}
							curr_orig_path += cf.name;
							metadata.orig_path = curr_orig_path;
                            metadata.exist=true;
							metadata.has_orig_path=true;
						}

						std::string metadata_fn = backuppath_hashes+local_curr_os_path+os_file_sep()+metadata_dir_fn;

						bool create_hash_dir=true;
						str_map::iterator sym_target = extra_params.find("sym_target");
						if(sym_target!=extra_params.end())
						{
							if(!createSymlink(backuppath+local_curr_os_path, depth, sym_target->second, (orig_sep), true))
							{
								ServerLogger::Log(logid, "Creating symlink at \""+backuppath+local_curr_os_path+"\" to \""+sym_target->second+"\" failed. " + systemErrorInfo(), LL_ERROR);
								c_has_error=true;
								break;
							}

							metadata_fn = backuppath_hashes + convertToOSPathFromFileClient(orig_curr_os_path + "/" + escape_metadata_fn(cf.name)); 
							create_hash_dir=false;
						}
						else if(!os_create_dir(os_file_prefix(backuppath+local_curr_os_path)))
						{
							ServerLogger::Log(logid, "Creating directory  \""+backuppath+local_curr_os_path+"\" failed. " + systemErrorInfo(), LL_ERROR);
							c_has_error=true;
							break;
						}

						if (depth == 0 && curr_path == "/urbackup_backup_scripts")
						{
							metadata.file_permissions = permissionsAllowAll();
							curr_orig_path = local_curr_os_path;
							metadata.orig_path = curr_orig_path;
						}
						
						if(create_hash_dir && !os_create_dir(os_file_prefix(backuppath_hashes+local_curr_os_path)))
						{
							ServerLogger::Log(logid, "Creating directory  \""+backuppath_hashes+local_curr_os_path+"\" failed. " + systemErrorInfo(), LL_ERROR);
							c_has_error=true;
							break;
						}
						else if(metadata.exist && !write_file_metadata(metadata_fn, client_main, metadata, false))
						{
							ServerLogger::Log(logid, "Writing directory metadata to \""+metadata_fn+"\" failed.", LL_ERROR);
							c_has_error=true;
							break;
						}

						folder_files.push(std::set<std::string>());
						folder_items.push_back(0);

						++depth;
						if(depth==1)
						{
							std::string t=curr_path;
							t.erase(0,1);
							if(t=="urbackup_backup_scripts")
							{
								script_dir=true;
							}
							else
							{
								ServerLogger::Log(logid, "Starting shadowcopy \""+t+"\".", LL_DEBUG);
								server_download->addToQueueStartShadowcopy(t);

								continuous_sequences[cf.name]=SContinuousSequence(
									watoi64(extra_params["sequence_id"]), watoi64(extra_params["sequence_next"]));
							}							
						}
					}
					else
					{
						folder_files.pop();

                        if(client_main->getProtocolVersions().file_meta>0 && !script_dir)
						{
							server_download->addToQueueFull(line, ExtractFileName(curr_path, "/"),
								ExtractFileName(curr_os_path, "/"), ExtractFilePath(curr_path, "/"), ExtractFilePath(curr_os_path, "/"), queue_downloads?0:-1,
								metadata, false, true, folder_items.back(), std::string());
						}
						folder_items.pop_back();
						--depth;
						if(depth==0)
						{
							std::string t=curr_path;
							t.erase(0,1);
							if(t=="urbackup_backup_scripts")
							{
								script_dir=false;
							}
							else
							{
								ServerLogger::Log(logid, "Stopping shadowcopy \""+t+"\".", LL_DEBUG);
								server_download->addToQueueStopShadowcopy(t);
							}							
						}

						curr_path=ExtractFilePath(curr_path, "/");
						curr_os_path=ExtractFilePath(curr_os_path, "/");

						if(!has_orig_path)
						{
							curr_orig_path = ExtractFilePath(curr_orig_path, orig_sep);
						}
					}
				}
				else
				{
					if (depth == 0)
					{
						server_download->addToQueueStartShadowcopy(cf.name);
					}

					if(!has_orig_path)
					{
						if (curr_orig_path != orig_sep)
						{
							metadata.orig_path = curr_orig_path + orig_sep + cf.name;
						}
						else
						{
							metadata.orig_path = orig_sep + cf.name;
						}
					}

					bool file_ok=false;
					bool write_file_metadata = false;

                    str_map::iterator sym_target = extra_params.find("sym_target");
                    if(sym_target!=extra_params.end())
                    {
                        std::string symlink_path = backuppath + convertToOSPathFromFileClient(curr_os_path)+os_file_sep()+osspecific_name;
                        if(!createSymlink(symlink_path, depth, sym_target->second, (orig_sep), false))
                        {
                            ServerLogger::Log(logid, "Creating symlink at \""+symlink_path+"\" to \""+sym_target->second+"\" failed. " + systemErrorInfo(), LL_ERROR);
                            c_has_error=true;
                            break;
                        }
                        else
                        {
							if (line>max_ok_id)
							{
								max_ok_id = line;
							}

                            file_ok=true;
							write_file_metadata = true;
                        }
                    }
					else if(extra_params.find("special")!=extra_params.end())
					{
						std::string touch_path = backuppath + convertToOSPathFromFileClient(curr_os_path)+os_file_sep()+osspecific_name;
						std::unique_ptr<IFile> touch_file(Server->openFile(os_file_prefix(touch_path), MODE_WRITE));
						if(touch_file.get()==NULL)
						{
							ServerLogger::Log(logid, "Error touching file at \""+touch_path+"\". " + systemErrorInfo(), LL_ERROR);
							c_has_error=true;
							break;
						}
						else
						{
							if (line>max_ok_id)
							{
								max_ok_id = line;
							}

							file_ok=true;
							write_file_metadata = true;
						}
					}

					std::string curr_sha2;
					std::map<std::string, std::string>::iterator hash_it=( (local_hash.get()==NULL)?extra_params.end():extra_params.find(sha_def_identifier) );
					
					if (local_hash.get() != NULL && hash_it == extra_params.end())
					{
						hash_it = extra_params.find("thash");
					}

					if (!file_ok
						&& hash_it != extra_params.end())
					{
						curr_sha2 = base64_decode_dash(hash_it->second);
					}
					else if (!file_ok
						&& phash_load.get() != NULL
						&& !script_dir
						&& extra_params.find("no_hash") == extra_params.end()
						&& cf.size >= link_file_min_size)
					{
						if (!phash_load->getHash(line, curr_sha2))
						{
							ServerLogger::Log(logid, "Error getting parallel hash for file \"" + cf.name + "\" line " + convert(line), LL_ERROR);
							r_offline = true;
							break;
						}
						else
						{
							metadata.shahash = curr_sha2;
						}
					}

					if(!curr_sha2.empty())
					{						
						if(cf.size>= link_file_min_size
							&& link_file(cf.name, osspecific_name, curr_path, curr_os_path, curr_sha2, cf.size,
							             true, metadata))
						{
							file_ok=true;
							linked_bytes+=cf.size;
							if(line>max_ok_id)
							{
								max_ok_id=line;
							}
						}
					}

                    if(file_ok)
                    {
						if(client_main->getProtocolVersions().file_meta>0)
						{
                    	    server_download->addToQueueFull(line, cf.name, osspecific_name, curr_path, curr_os_path, queue_downloads?0:-1,
                    	        metadata, script_dir, true, 0, curr_sha2, false, 0, std::string(), write_file_metadata);
                    	}
                    }
                    else
					{
						server_download->addToQueueFull(line, cf.name, osspecific_name, curr_path, curr_os_path, queue_downloads?cf.size:-1,
							metadata, script_dir, false, 0, curr_sha2);
					}

					if (depth == 0)
					{
						server_download->addToQueueStopShadowcopy(cf.name);
					}
				}

				max_file_id.setMaxPreProcessed(line);
				++line;
			}
		}

		if(read<4096)
			break;
	}

	if (has_read_error)
	{
		ServerLogger::Log(logid, "Error reading from file " + tmp_filelist->getFilename() + ". " + os_last_error_str(), LL_ERROR);
		disk_error = true;
	}

	stopPhashDownloadThread(filelist_async_id);


	server_download->queueStop();

	ServerLogger::Log(logid, "Waiting for file transfers...", LL_INFO);

	while(!server_download->join(1000))
	{
		if(files_size==0)
		{
			ServerStatus::setProcessPcDone(clientname, status_id, 100);
		}
		else
		{
			int64 done_bytes = fc.getReceivedDataBytes(true) + linked_bytes;
			ServerStatus::setProcessDoneBytes(clientname, status_id, done_bytes);
			ServerStatus::setProcessPcDone(clientname, status_id,
				(std::min)(100,(int)(((float)done_bytes)/((float)files_size/100.f)+0.5f)));
		}

		ServerStatus::setProcessQueuesize(clientname, status_id,
			(_u32)hashpipe->getNumElements(), (_u32)hashpipe_prepare->getNumElements());

		int64 ctime = Server->getTimeMS();
		if(ctime-last_eta_update>eta_update_intervall)
		{
			calculateEtaFileBackup(last_eta_update, eta_set_time, ctime, fc, NULL, linked_bytes, last_eta_received_bytes, eta_estimated_speed, files_size);
		}

		calculateDownloadSpeed(ctime, fc, NULL);
	}

	ServerStatus::setProcessSpeed(clientname, status_id, 0);

	if(server_download->isOffline() && !r_offline)
	{
		ServerLogger::Log(logid, "Client "+clientname+" went offline.", LL_ERROR);
		r_offline=true;
	}

	num_issues += server_download->getNumIssues();

	if (server_download->getHasDiskError()
		&& !server_settings->getSettings()->ignore_disk_errors)
	{
		r_offline = true;
	}

	int64 transfer_stop_time = Server->getTimeMS();

	size_t max_line = line;

	if(!r_offline && !c_has_error && !disk_error)
	{
		sendBackupOkay(true);
	}
	else
	{
		sendBackupOkay(false);
	}

	if(r_offline && server_download->hasTimeout() && !server_download->shouldBackoff())
	{
		ServerLogger::Log(logid, "Client had timeout. Retrying backup soon...", LL_INFO);
		should_backoff=false;
		has_timeout_error=true;
	}

	running_updater->stop();
	backup_dao->updateFileBackupRunning(backupid);

	ServerLogger::Log(logid, "Waiting for file hashing and copying threads...", LL_INFO);

	waitForFileThreads();

	if (!r_offline && !c_has_error && !disk_error)
	{
		if (!loadWindowsBackupComponentConfigXml(fc))
		{
			c_has_error = true;
		}
	}

	stopFileMetadataDownloadThread(false, server_download->getNumEmbeddedMetadataFiles());

	if (!r_offline && !c_has_error && !disk_error
		&& client_main->getProtocolVersions().wtokens_version > 0 )
	{
		getTokenFile(fc, hashed_transfer, true);
	}

	server_download->deleteTempFolder();

	ServerLogger::Log(logid, "Writing new file list...", LL_INFO);

	bool has_all_metadata=true;

	tmp_filelist->Seek(0);
	line = 0;
	list_parser.reset();
	size_t output_offset=0;
	std::stack<size_t> last_modified_offsets;
	script_dir=false;
	has_read_error = false;
	size_t download_max_ok_id = server_download->getMaxOkId();
	while( (read=tmp_filelist->Read(buffer, 4096, &has_read_error))>0 )
	{
		if (has_read_error)
		{
			break;
		}

		for(size_t i=0;i<read;++i)
		{
			bool b=list_parser.nextEntry(buffer[i], cf, NULL);
			if(b)
			{
				if(cf.isdir)
				{
					if(cf.name!="..")
					{
						if (line < max_line)
						{
							size_t curr_last_modified_offset = 0;
							size_t curr_output_offset = output_offset;
							writeFileItem(clientlist, cf, &output_offset, &curr_last_modified_offset);

							last_modified_offsets.push(curr_output_offset + curr_last_modified_offset);
						}
						else
						{
							last_modified_offsets.push(std::string::npos);
						}

						if(cf.name=="urbackup_backup_scripts")
						{
							script_dir=true;
						}
					}					
					else
					{
						if(!script_dir
							&& metadata_download_thread.get()!=NULL
							&& !metadata_download_thread->hasMetadataId(line+1)
							&& last_modified_offsets.top()!= std::string::npos)
						{
							if (line < max_line)
							{
								has_all_metadata = false;
								ServerLogger::Log(logid, "Metadata of \"" + cf.name + "\" missing", LL_DEBUG);
							}

							//go back to the directory entry and change the last modified time
							if(clientlist->Seek(last_modified_offsets.top()))
							{
								char ch;
								if(clientlist->Read(&ch, 1)==1)
								{
									ch = (ch=='0' ? '1' : '0');
									if(!clientlist->Seek(last_modified_offsets.top())
										|| clientlist->Write(&ch, 1)!=1)
									{
										ServerLogger::Log(logid, "Error writing to clientlist", LL_ERROR);
									}
								}
								else
								{
									ServerLogger::Log(logid, "Error reading from clientlist "+clientlist->getFilename()+" from offset "+convert(last_modified_offsets.top()), LL_ERROR);	
								}
							}
							else
							{
								ServerLogger::Log(logid, "Error seeking in clientlist", LL_ERROR);	
							}

							if(!clientlist->Seek(clientlist->Size()))
							{
								ServerLogger::Log(logid, "Error seeking to end in clientlist", LL_ERROR);	
							}
						}

						if (line < max_line)
						{
							writeFileItem(clientlist, cf, &output_offset);
						}

						script_dir=false;
						last_modified_offsets.pop();
					}					
				}
				else if(!cf.isdir && 
					line <= (std::max)(download_max_ok_id, max_ok_id) &&
					server_download->isDownloadOk(line) )
				{
					bool metadata_missing = (!script_dir && metadata_download_thread.get()!=NULL
						&& !metadata_download_thread->hasMetadataId(line+1));

					if(metadata_missing)
					{
						ServerLogger::Log(logid, "Metadata of \"" + cf.name + "\" is missing", LL_DEBUG);

						has_all_metadata=false;
					}

					if(server_download->isDownloadPartial(line)
						|| metadata_missing)
					{
						if(cf.last_modified==0)
						{
							cf.last_modified+=1;
						}
						cf.last_modified *= Server->getRandomNumber();
					}
					writeFileItem(clientlist, cf, &output_offset);
				}				
				++line;
			}
		}
	}

	if (has_read_error)
	{
		ServerLogger::Log(logid, "Error reading from file " + tmp_filelist->getFilename() + ". " + os_last_error_str(), LL_ERROR);
		disk_error = true;
	}

	if(has_all_metadata)
	{
		ServerLogger::Log(logid, "All metadata was present", LL_INFO);
	}
	else
	{
		ServerLogger::Log(logid, "Some metadata was missing", LL_DEBUG);
	}

	bool verification_ok = true;
	if(!r_offline && !c_has_error
	        && (server_settings->getSettings()->end_to_end_file_backup_verification
		|| (client_main->isOnInternetConnection()
		&& server_settings->getSettings()->verify_using_client_hashes 
		&& server_settings->getSettings()->internet_calculate_filehashes_on_client) ) )
	{
		if(!verify_file_backup(tmp_filelist))
		{
			ServerLogger::Log(logid, "Backup verification failed", LL_ERROR);
			c_has_error=true;
			verification_ok = false;
		}
		else
		{
			ServerLogger::Log(logid, "Backup verification ok", LL_INFO);
		}
	}

	for (size_t i = 0; i < bsh.size(); ++i)
	{
		if (bsh[i]->hasError())
			disk_error = true;
	}
	for (size_t i = 0; i < bsh_prepare.size(); ++i)
	{
		if (bsh_prepare[i]->hasError())
			disk_error = true;
	}

	if(!disk_error && verification_ok)
	{
		FileIndex::flush();

		ServerLogger::Log(logid, "Syncing file system...", LL_DEBUG);

		clientlist->Sync();
		clientlist_delete.release();

		if ( !os_sync(backuppath)
			|| !os_sync(backuppath_hashes) )
		{
			ServerLogger::Log(logid, "Syncing file system failed. Backup may not be completely on disk. " + os_last_error_str(), BackupServer::canSyncFs() ? LL_ERROR : LL_DEBUG);

			if(BackupServer::canSyncFs())
				c_has_error = true;
		}

		std::unique_ptr<IFile> sync_f;
		if (!c_has_error)
		{
			sync_f.reset(Server->openFile(os_file_prefix(backuppath_hashes + os_file_sep() + sync_fn), MODE_WRITE));

			if (sync_f.get() != NULL
				&& !sync_f->Sync())
			{
				ServerLogger::Log(logid, "Error syncing sync file to disk. " + os_last_error_str(), LL_ERROR);
				c_has_error = true;
			}
		}

		if (!c_has_error
			&& use_snapshots)
		{
			if (!SnapshotHelper::makeReadonly(false, clientname, backuppath_single))
			{
				ServerLogger::Log(logid, "Making backup snapshot read only failed", LL_WARNING);
			}
		}

		if (sync_f.get() != NULL)
		{
			DBScopedSynchronous synchronous(db);
			DBScopedWriteTransaction trans(db);

			backup_dao->setFileBackupDone(backupid);
			backup_dao->setFileBackupSynced(backupid);
		}
		else if(!c_has_error)
		{
			ServerLogger::Log(logid, "Error creating sync file at "+ backuppath_hashes + os_file_sep() + sync_fn+". Not setting backup to done.", LL_ERROR);
			c_has_error = true;
		}

		Server->destroy(clientlist);
	}

	if( !r_offline && !c_has_error && !disk_error
		&& (group==c_group_default || group==c_group_continuous)) 
	{
		std::string backupfolder=server_settings->getSettings()->backupfolder;

		std::string name="current";
		if(group==c_group_continuous)
		{
			name="continuous";
		}

		std::string currdir=backupfolder+os_file_sep()+clientname+os_file_sep()+name;
		os_remove_symlink_dir(os_file_prefix(currdir));
		os_link_symbolic(os_file_prefix(backuppath), os_file_prefix(currdir));

		if(group==c_group_default)
		{
			currdir=backupfolder+os_file_sep()+"clients";
			if(!os_create_dir(os_file_prefix(currdir)) && !os_directory_exists(os_file_prefix(currdir)))
			{
				Server->Log("Error creating \"clients\" dir for symbolic links", LL_ERROR);
			}
			currdir+=os_file_sep()+clientname;
			os_remove_symlink_dir(os_file_prefix(currdir));
			os_link_symbolic(os_file_prefix(backuppath), os_file_prefix(currdir));

			if(server_settings->getSettings()->create_linked_user_views)
			{
				ServerLogger::Log(logid, "Creating user views...", LL_INFO);

				createUserViews(tmp_filelist);
			}

			saveUsersOnClient();
		}
	}

	_i64 transferred_bytes=fc.getTransferredBytes();
	_i64 transferred_compressed=fc.getRealTransferredBytes();
	int64 passed_time=transfer_stop_time-full_backup_starttime;
	if(passed_time==0) passed_time=1;

	ServerLogger::Log(logid, "Transferred "+PrettyPrintBytes(transferred_bytes)+" - Average speed: "+PrettyPrintSpeed((size_t)((transferred_bytes*1000)/(passed_time)) ), LL_INFO );
	if(transferred_compressed>0)
	{
		ServerLogger::Log(logid, "(Before compression: "+PrettyPrintBytes(transferred_compressed)+" ratio: "+convert((float)transferred_compressed/transferred_bytes)+")");
	}
	if (linked_bytes > 0)
	{
		ServerLogger::Log(logid, PrettyPrintBytes(linked_bytes) + " of files were already present on the server and did not need to be transferred", LL_INFO);
	}

	if (!ClientMain::run_script("urbackup" + os_file_sep() + "post_full_filebackup",
		"\"" + backuppath + "\" " + ((c_has_error || r_offline || disk_error) ? "0" : "1") + " " + convert(group), logid))
	{
		c_has_error = true;
	}

	if(c_has_error)
		return false;

	return !r_offline;
}

