void CMissionManager::CleanupModFolder(const idStr& name)
{
	CModInfoPtr info = GetModInfo(name);

	if (info == NULL)
	{
		DM_LOG(LC_MAINMENU, LT_INFO)LOGSTRING("Cannot erase mission folder for mod %s, mission info not found\r", name.c_str());
		return;
	}

	// Delete folder contents
	fs::path modPath = info->GetModFolderPath().c_str();

	if (fs::exists(modPath))
	{
		// Iterate over all files in the mod folder
		for (fs::directory_iterator i(modPath); i != fs::directory_iterator(); ++i)
		{
			if (boost::algorithm::to_lower_copy(fs::extension(*i)) == ".pk4")
			{
				DM_LOG(LC_MAINMENU, LT_INFO)LOGSTRING("Won't erase PK4 files %s\r", i->path().string().c_str());
				continue;
			}

			if (i->path().filename() == cv_tdm_fm_desc_file.GetString() || 
				i->path().filename() == cv_tdm_fm_notes_file.GetString() || 
				i->path().filename() == cv_tdm_fm_splashimage_file.GetString())
			{
				DM_LOG(LC_MAINMENU, LT_INFO)LOGSTRING("Won't erase meta data file %s\r", i->path().string().c_str());
				continue;
			}

			DM_LOG(LC_MAINMENU, LT_INFO)LOGSTRING("Will erase recursively: %s\r", i->path().string().c_str());
			fs::remove_all(*i);
		}
	}
	else
	{
		DM_LOG(LC_MAINMENU, LT_INFO)LOGSTRING("Cannot erase mod folder %s, directory not found\r", modPath.string().c_str());
		return;
	}

	info->ClearModFolderSize();
}
void CMissionManager::OnMissionStart()
{
	CModInfoPtr info = GetCurrentModInfo();

	if (info == NULL)
	{
		DM_LOG(LC_MAINMENU, LT_ERROR)LOGSTRING("Could not find mission info for current mod.\r");
		return;
	}

	time_t seconds;
	tm* timeInfo;

	seconds = time(NULL);
	timeInfo = localtime(&seconds);

	// Mark the current difficulty level as completed
	info->SetKeyValue("last_play_date", va("%d-%02d-%02d", timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday));
}
void CMissionManager::RefreshMetaDataForNewFoundMods()
{
	// greebo: If we have new found mods, refresh the meta data of the corresponding MissionDB entries
	// otherwise we end up with empty display names after downloading a mod we had on the HDD before
	for (int i = 0; i < _newFoundMods.Num(); ++i)
	{
		CModInfoPtr info = GetModInfo(_newFoundMods[i]);

		if (info != NULL) 
		{
			if (info->LoadMetaData())
			{
				DM_LOG(LC_MAINMENU, LT_INFO)LOGSTRING("Successfully read meta data for newly found mod %s\r", _newFoundMods[i].c_str());
			}
			else
			{
				DM_LOG(LC_MAINMENU, LT_DEBUG)LOGSTRING("Could not read meta data for newly found mod %s\r", _newFoundMods[i].c_str());
			}
		}
	}
}
void CMissionManager::OnMissionComplete()
{
	CModInfoPtr info = GetCurrentModInfo();

	if (info == NULL)
	{
		DM_LOG(LC_MAINMENU, LT_ERROR)LOGSTRING("Could not find mission info for current mod.\r");
		return;
	}

	// Ensure that this was the last mission if in campaign mode, otherwise ignore this call
	if (CurrentModIsCampaign())
	{
		if (_curMissionIndex == -1)
		{
			gameLocal.Error("Invalid mission index in OnMissionComplete()");
		}

		if (_curMissionIndex < _mapSequence.Num() - 1)
		{
			// This is not yet the last mission in the campaign, ignore this call
			return;
		}
	}

	// Mark the current difficulty level as completed
	info->SetKeyValue(va("mission_completed_%d", gameLocal.m_DifficultyManager.GetDifficultyLevel()), "1");

	idPlayer* player = gameLocal.GetLocalPlayer();

	if (player != NULL)
	{
		int gold, jewelry, goods;
		int total = player->Inventory()->GetLoot(gold, jewelry, goods);

		info->SetKeyValue(va("mission_loot_collected_%d", gameLocal.m_DifficultyManager.GetDifficultyLevel()), idStr(total));
	}
}
void CDownloadMenu::ShowDownloadResult( idUserInterface *gui ) {
	// greebo: Let the mod list be refreshed
	// We need the information from darkmod.txt later down this road
	gameLocal.m_MissionManager->ReloadModList();
	int successfulDownloads = 0;
	int failedDownloads = 0;
	const DownloadableModList &mods = gameLocal.m_MissionManager->GetDownloadableMods();
	for( ActiveDownloads::iterator i = _downloads.begin(); i != _downloads.end(); ++i ) {
		CDownloadPtr download = gameLocal.m_DownloadManager->GetDownload( i->second.missionDownloadId );
		if( download == NULL ) {
			continue;
		}
		if( i->first > mods.Num() ) {
			continue;
		}
		const DownloadableMod &mod = *mods[i->first];
		switch( download->GetStatus() ) {
		case CDownload::NOT_STARTED_YET:
			gameLocal.Warning( "Some downloads haven't been processed?" );
			break;
		case CDownload::FAILED:
			failedDownloads++;
			break;
		case CDownload::IN_PROGRESS:
			gameLocal.Warning( "Some downloads still in progress?" );
			break;
		case CDownload::SUCCESS: {
			// gnartsch
			bool l10nPackDownloaded = false;
			// In case of success, check l10n download status
			if( i->second.l10nPackDownloadId != -1 ) {
				CDownloadPtr l10nDownload = gameLocal.m_DownloadManager->GetDownload( i->second.l10nPackDownloadId );
				CDownload::DownloadStatus l10nStatus = l10nDownload->GetStatus();
				if( l10nStatus == CDownload::NOT_STARTED_YET || l10nStatus == CDownload::IN_PROGRESS ) {
					gameLocal.Warning( "Localisation pack download not started or still in progress?" );
				} else if( l10nStatus == CDownload::FAILED ) {
					gameLocal.Warning( "Failed to download localisation pack!" );
					// Turn this download into a failed one
					failedDownloads++;
				} else if( l10nStatus == CDownload::SUCCESS ) {
					// both successfully downloaded
					successfulDownloads++;
					// gnartsch
					l10nPackDownloaded = true;
				}
			} else { // regular download without l10n ... or l10n download only (gnartsch)
				successfulDownloads++;
				// gnartsch: Consider Localization pack having been dealt with as well
				l10nPackDownloaded = true;
			}
			// Save the mission version into the MissionDB for later use
			CModInfoPtr missionInfo = gameLocal.m_MissionManager->GetModInfo( mod.modName );
			missionInfo->SetKeyValue( "downloaded_version", idStr( mod.version ).c_str() );
			// gnartsch: Mark l10n pack as present, so that the mission may disappear from the list of 'Available Downloads'
			missionInfo->isL10NpackInstalled = l10nPackDownloaded;
		}
		break;
		};
	}
	gameLocal.Printf( "Successful downloads: %d\nFailed downloads: %d\n", successfulDownloads, failedDownloads );
	// Display the popup box
	GuiMessage msg;
	msg.type = GuiMessage::MSG_OK;
	msg.okCmd = "close_msg_box;onDownloadCompleteConfirm";
	msg.title = common->Translate( "#str_02142" ); // "Mission Download Result"
	msg.message = "";
	if( successfulDownloads > 0 ) {
		msg.message += va(
						   // "%d mission/missions successfully downloaded. You'll find it/them in the 'New Mission' page."
						   GetPlural( successfulDownloads, common->Translate( "#str_02144" ), common->Translate( "#str_02145" ) ),
						   successfulDownloads );
	}
	if( failedDownloads > 0 ) {
		// "\n%d mission(s) couldn't be downloaded. Please check your disk space (or maybe some file is write protected) and try again."
		msg.message += va( common->Translate( "#str_02146" ),
						   failedDownloads );
	}
	gameLocal.AddMainMenuMessage( msg );
	// Remove all downloads
	for( ActiveDownloads::iterator i = _downloads.begin(); i != _downloads.end(); ++i ) {
		gameLocal.m_DownloadManager->RemoveDownload( i->second.missionDownloadId );
		if( i->second.l10nPackDownloadId != -1 ) {
			gameLocal.m_DownloadManager->RemoveDownload( i->second.l10nPackDownloadId );
		}
	}
	_downloads.clear();
}
void CMissionManager::LoadModListFromXml(const XmlDocumentPtr& doc)
{
	assert(doc != NULL);

	/* Example XML Snippet
	
	<mission id="11" title="Living Expenses" releaseDate="2010-01-02" size="5.9" author="Sonosuke">
		<downloadLocation language="English" url="http://www.bloodgate.com/mirrors/tdm/pub/pk4/fms/living_expenses.pk4"/>
		<downloadLocation language="German" url="http://www.bloodgate.com/mirrors/tdm/pub/pk4/fms/living_expenses_de.pk4"/>
	</mission>
	
	*/

	pugi::xpath_node_set nodes = doc->select_nodes("//tdm/availableMissions//mission");

	const char* fs_currentfm = cvarSystem->GetCVarString("fs_currentfm");

	// Tels: #3419 - After game start the sequence is always the same, so set a random seed
	time_t seconds = time(NULL);
	gameLocal.random.SetSeed( static_cast<int>(seconds) );

	for (pugi::xpath_node_set::const_iterator i = nodes.begin(); i != nodes.end(); ++i)	
	{
		pugi::xml_node node = i->node();

		DownloadableMod mission;

		mission.title = node.attribute("title").value();

		mission.id = node.attribute("id").as_int();
		mission.sizeMB = node.attribute("size").as_float();
		mission.author = node.attribute("author").value();
		mission.releaseDate = node.attribute("releaseDate").value();
		mission.type = idStr::Icmp(node.attribute("type").value(), "multi") == 0 ? DownloadableMod::Multi : DownloadableMod::Single;

		mission.modName = node.attribute("internalName").value();
		// Tels #3294: We need to clean the server-side modName of things like uppercase letters, trailing ".pk4" etc.
		//	       Otherwise we get duplicated entries in the MissionDB like "broads.pk4" and "broads" or "VFAT1" and "vfat1":
		mission.modName.ToLower();
		mission.modName.StripTrailingOnce(".pk4");

		// Clean modName string from any weird characters
		int modNameLen = mission.modName.Length();
		for (int i = 0; i < modNameLen; ++i)
		{
			if (idStr::CharIsAlpha(mission.modName[i]) || idStr::CharIsNumeric(mission.modName[i])) continue;
			mission.modName[i] = '_'; // replace non-ASCII keys with underscores
		}

		mission.version = node.attribute("version").as_int();
		mission.isUpdate = false;
        mission.needsL10NpackDownload = false; // gnartsch

		if (idStr::Cmp(mission.modName.c_str(), fs_currentfm) == 0)
		{
			DM_LOG(LC_MAINMENU, LT_DEBUG)LOGSTRING("Removing currently installed mission %s from the list of downloadable missions.\r", fs_currentfm);
			continue;
		}

		bool missionExists = false;

		// Check if this mission is already downloaded
		for (int j = 0; j < _availableMods.Num(); ++j)
		{
			if (idStr::Icmp(_availableMods[j], mission.modName) == 0)
			{
				missionExists = true;
				break;
			}
		}

		if (missionExists)
		{
			// Check mod version, there might be an update available
			if (_missionDB->ModInfoExists(mission.modName))
			{
				CModInfoPtr missionInfo = _missionDB->GetModInfo(mission.modName);

				idStr versionStr = missionInfo->GetKeyValue("downloaded_version", "1");
				int existingVersion = atoi(versionStr.c_str());

				if (existingVersion >= mission.version)
				{
					// gnartsch : Skip to next mission only in case the localization pack 
                    //            for the current mission had been downloaded already as well
					if (missionInfo->isL10NpackInstalled) 
                    {
						continue; // Our version is up to date
					}
				}
				else
				{
					mission.isUpdate = true;
				}
			}
		}

		// gnartsch : Process mission download locations only if the mission itself is not 
        //            present or not up to date, otherwise skip to the localization pack
		if (!missionExists || mission.isUpdate) 
		{
            // Mission download links
		    pugi::xpath_node_set downloadLocations = node.select_nodes("downloadLocation");

		    for (pugi::xpath_node_set::const_iterator loc = downloadLocations.begin(); loc != downloadLocations.end(); ++loc)	
		    {
			    pugi::xml_node locNode = loc->node();

			    // Only accept English downloadlinks
			    if (idStr::Icmp(locNode.attribute("language").value(), "english") != 0) continue;

			    // Tels: #3419: Randomize the order of download URLs by inserting at a random place (+2 to avoid the first URL always being placed last)
			    mission.missionUrls.Insert(locNode.attribute("url").value(), gameLocal.random.RandomInt( mission.missionUrls.Num() + 2 ) );
		    }
        }

		// Localisation packs
        // gnartsch: Process only if mission is either present locally or at least a download link for the mission is available.
        if (missionExists || mission.missionUrls.Num() > 0)
		{
		    pugi::xpath_node_set l10PackNodes = node.select_nodes("localisationPack");

		    for (pugi::xpath_node_set::const_iterator loc = l10PackNodes.begin(); loc != l10PackNodes.end(); ++loc)	
		    {
			    pugi::xml_node locNode = loc->node();

			    // Tels: #3419: Randomize the order of l10n URLs by inserting at a random place (+2 to avoid the first URL always being placed last)
			    mission.l10nPackUrls.Insert(locNode.attribute("url").value(), gameLocal.random.RandomInt( mission.l10nPackUrls.Num() + 2 ) );

                // gnartsch: Found a localization pack url for download
				mission.needsL10NpackDownload = true;
		    }
        }

		// Only add missions with valid locations
		// gnartsch: add the mission in case localization pack needs to be downloaded
		if (mission.missionUrls.Num() > 0 || mission.l10nPackUrls.Num() > 0)
		{
			// Copy-construct the local mission struct into the heap-allocated one
			_downloadableMods.Append(new DownloadableMod(mission));
		}
	}

	SortDownloadableMods();
}