1 #include "wui/savegamedeleter.h"
2 
3 #include <boost/format.hpp>
4 
5 #include "base/i18n.h"
6 #include "base/log.h"
7 #include "io/filesystem/filesystem_exceptions.h"
8 #include "io/filesystem/layered_filesystem.h"
9 #include "logic/filesystem_constants.h"
10 #include "ui_basic/messagebox.h"
11 
SavegameDeleter(UI::Panel * parent)12 SavegameDeleter::SavegameDeleter(UI::Panel* parent) : parent_(parent) {
13 }
14 
delete_savegames(const std::vector<SavegameData> & to_be_deleted) const15 bool SavegameDeleter::delete_savegames(const std::vector<SavegameData>& to_be_deleted) const {
16 	bool do_delete = SDL_GetModState() & KMOD_CTRL;
17 	if (!do_delete) {
18 		do_delete = show_confirmation_window(to_be_deleted);
19 	}
20 	if (do_delete) {
21 		delete_and_count_failures(to_be_deleted);
22 	}
23 	return do_delete;
24 }
25 
show_confirmation_window(const std::vector<SavegameData> & selections) const26 bool SavegameDeleter::show_confirmation_window(const std::vector<SavegameData>& selections) const {
27 	size_t no_selections = selections.size();
28 	std::string confirmation_window_header = create_header_for_confirmation_window(no_selections);
29 	const std::string message =
30 	   (boost::format("%s\n%s") % confirmation_window_header % as_filename_list(selections)).str();
31 
32 	UI::WLMessageBox confirmationBox(
33 	   parent_->get_parent()->get_parent(),
34 	   no_selections == 1 ? _("Confirm Deleting File") : _("Confirm Deleting Files"), message,
35 	   UI::WLMessageBox::MBoxType::kOkCancel);
36 	return confirmationBox.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kOk;
37 }
38 
39 const std::string
create_header_for_confirmation_window(const size_t no_selections) const40 SavegameDeleter::create_header_for_confirmation_window(const size_t no_selections) const {
41 	std::string header =
42 	   no_selections == 1 ? _("Do you really want to delete this game?") :
43 	                        /** TRANSLATORS: Used with multiple games, 1 game has a separate
44 	                           string. DO NOT omit the placeholder in your translation. */
45 	      (boost::format(ngettext("Do you really want to delete this %d game?",
46 	                              "Do you really want to delete these %d games?", no_selections)) %
47 	       no_selections)
48 	         .str();
49 
50 	return header;
51 }
52 
delete_and_count_failures(const std::vector<SavegameData> & to_be_deleted) const53 void SavegameDeleter::delete_and_count_failures(
54    const std::vector<SavegameData>& to_be_deleted) const {
55 	uint32_t number_of_failed_deletes = try_to_delete(to_be_deleted);
56 	if (number_of_failed_deletes > 0) {
57 		notify_deletion_failed(to_be_deleted, number_of_failed_deletes);
58 	}
59 }
60 
try_to_delete(const std::vector<SavegameData> & to_be_deleted) const61 uint32_t SavegameDeleter::try_to_delete(const std::vector<SavegameData>& to_be_deleted) const {
62 	// Failed deletions aren't a serious problem, we just catch the errors
63 	// and keep track to notify the player.
64 	uint32_t failed_deletions = 0;
65 	for (const auto& delete_me : to_be_deleted) {
66 		try {
67 			g_fs->fs_unlink(delete_me.filename);
68 		} catch (const FileError& e) {
69 			log("player-requested file deletion failed: %s", e.what());
70 			++failed_deletions;
71 		}
72 	}
73 	return failed_deletions;
74 }
75 
notify_deletion_failed(const std::vector<SavegameData> & to_be_deleted,const uint32_t no_failed) const76 void SavegameDeleter::notify_deletion_failed(const std::vector<SavegameData>& to_be_deleted,
77                                              const uint32_t no_failed) const {
78 	const std::string caption =
79 	   (no_failed == 1) ? _("Error Deleting File!") : _("Error Deleting Files!");
80 	std::string header = create_header_for_deletion_failed_window(to_be_deleted.size(), no_failed);
81 	std::string message = (boost::format("%s\n%s") % header % as_filename_list(to_be_deleted)).str();
82 
83 	UI::WLMessageBox msgBox(
84 	   parent_->get_parent()->get_parent(), caption, message, UI::WLMessageBox::MBoxType::kOk);
85 	msgBox.run<UI::Panel::Returncodes>();
86 }
87 
create_header_for_deletion_failed_window(size_t no_to_be_deleted,size_t no_failed) const88 std::string SavegameDeleter::create_header_for_deletion_failed_window(size_t no_to_be_deleted,
89                                                                       size_t no_failed) const {
90 	if (no_to_be_deleted == 1) {
91 		return _("The game could not be deleted.");
92 	} else {
93 		/** TRANSLATORS: Used with multiple games, 1 game has a separate
94 		                        string. DO NOT omit the placeholder in your translation. */
95 		return (boost::format(ngettext(
96 		           "%d game could not be deleted.", "%d games could not be deleted.", no_failed)) %
97 		        no_failed)
98 		   .str();
99 	}
100 }
101 
ReplayDeleter(UI::Panel * parent)102 ReplayDeleter::ReplayDeleter(UI::Panel* parent) : SavegameDeleter(parent) {
103 }
104 
105 const std::string
create_header_for_confirmation_window(const size_t no_selections) const106 ReplayDeleter::create_header_for_confirmation_window(const size_t no_selections) const {
107 	std::string header =
108 	   no_selections == 1 ?
109 	      _("Do you really want to delete this replay?") :
110 	      /** TRANSLATORS: Used with multiple replays, 1 replay has a
111 	                         separate string. DO NOT omit the placeholder in your translation. */
112 	      (boost::format(ngettext("Do you really want to delete this %d replay?",
113 	                              "Do you really want to delete these %d replays?", no_selections)) %
114 	       no_selections)
115 	         .str();
116 
117 	return header;
118 }
119 
create_header_for_deletion_failed_window(size_t no_to_be_deleted,size_t no_failed) const120 std::string ReplayDeleter::create_header_for_deletion_failed_window(size_t no_to_be_deleted,
121                                                                     size_t no_failed) const {
122 	if (no_to_be_deleted == 1) {
123 		return _("The replay could not be deleted.");
124 	} else {
125 		/** TRANSLATORS: Used with multiple replays, 1 replay has a separate
126 		                        string. DO NOT omit the placeholder in your translation. */
127 		return (boost::format(ngettext("%d replay could not be deleted.",
128 		                               "%d replays could not be deleted.", no_failed)) %
129 		        no_failed)
130 		   .str();
131 	}
132 }
133 
try_to_delete(const std::vector<SavegameData> & to_be_deleted) const134 uint32_t ReplayDeleter::try_to_delete(const std::vector<SavegameData>& to_be_deleted) const {
135 	// Failed deletions aren't a serious problem, we just catch the errors
136 	// and keep track to notify the player.
137 	uint32_t failed_deletions = 0;
138 	bool failed;
139 	for (const auto& delete_me : to_be_deleted) {
140 		failed = false;
141 		const std::string& file_to_be_deleted = delete_me.filename;
142 		try {
143 			g_fs->fs_unlink(file_to_be_deleted);
144 		} catch (const FileError& e) {
145 			log("player-requested file deletion failed: %s", e.what());
146 			failed = true;
147 		}
148 
149 		try {
150 			g_fs->fs_unlink(file_to_be_deleted + kSavegameExtension);
151 			// If at least one of the two relevant files of a replay are
152 			// successfully deleted then count it as success.
153 			// (From the player perspective the replay is gone.)
154 			failed = false;
155 			// If it was a multiplayer replay, also delete the synchstream. Do
156 			// it here, so it's only attempted if replay deletion was successful.
157 			if (g_fs->file_exists(file_to_be_deleted + kSyncstreamExtension)) {
158 				g_fs->fs_unlink(file_to_be_deleted + kSyncstreamExtension);
159 			}
160 		} catch (const FileError& e) {
161 			log("player-requested file deletion failed: %s", e.what());
162 		}
163 
164 		if (failed) {
165 			++failed_deletions;
166 		}
167 	}
168 	return failed_deletions;
169 }
170