root / trunk / plugins / par2 / plugin_par2.c @ 1850
History | View | Annotate | Download (44 KB)
| 1 | 
                  /* 
                 | 
              
|---|---|
| 2 | 
                  Copyright (C) 2005-2010 Erik van Pienbroek  | 
              
| 3 | 
                   | 
              
| 4 | 
                  This program is free software; you can redistribute it and/or modify  | 
              
| 5 | 
                  it under the terms of the GNU General Public License as published by  | 
              
| 6 | 
                  the Free Software Foundation; either version 2 of the License, or  | 
              
| 7 | 
                  (at your option) any later version.  | 
              
| 8 | 
                   | 
              
| 9 | 
                  This program is distributed in the hope that it will be useful,  | 
              
| 10 | 
                  but WITHOUT ANY WARRANTY; without even the implied warranty of  | 
              
| 11 | 
                  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the  | 
              
| 12 | 
                  GNU General Public License for more details.  | 
              
| 13 | 
                   | 
              
| 14 | 
                  You should have received a copy of the GNU General Public License  | 
              
| 15 | 
                  along with this program; if not, write to the Free Software  | 
              
| 16 | 
                  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA  | 
              
| 17 | 
                  */  | 
              
| 18 | 
                   | 
              
| 19 | 
                  #include  | 
              
| 20 | 
                  #include  | 
              
| 21 | 
                  #include  | 
              
| 22 | 
                  #include  | 
              
| 23 | 
                  #include  | 
              
| 24 | 
                  #include  | 
              
| 25 | 
                  #include  | 
              
| 26 | 
                  #include  | 
              
| 27 | 
                  #ifndef WIN32
                 | 
              
| 28 | 
                  #include  | 
              
| 29 | 
                  #endif
                 | 
              
| 30 | 
                   | 
              
| 31 | 
                  #include "nntpgrab_plugin.h"  | 
              
| 32 | 
                  #include "marshalers.h"  | 
              
| 33 | 
                  #include "config.h"  | 
              
| 34 | 
                   | 
              
| 35 | 
                  static gboolean nntpgrab_plugin_par2_repair_files(NGPlugin *plugin_data, const char *collection_name, const char *directory, const char *par2filename, ngboolean more_par2_sets_remaining_in_collection);  | 
              
| 36 | 
                  static void on_collection_downloaded(NGPlugin *plugin_data, const char *collection_name, gpointer data);  | 
              
| 37 | 
                  static void par2_thread_func(gpointer data, gpointer user_data);  | 
              
| 38 | 
                   | 
              
| 39 | 
                  typedef struct _plugin_par2_priv {  | 
              
| 40 | 
                  GThreadPool *thread_pool;  | 
              
| 41 | 
                  const char *collection_name;  | 
              
| 42 | 
                  const char *par2filename;  | 
              
| 43 | 
                  GList *par2files;  | 
              
| 44 | 
                  gboolean repair_succeeded;  | 
              
| 45 | 
                  gboolean command_completed_normally;  | 
              
| 46 | 
                  gboolean abort_flag;  | 
              
| 47 | 
                      int pid;
                 | 
              
| 48 | 
                  } PluginPAR2Priv;  | 
              
| 49 | 
                   | 
              
| 50 | 
                  #ifdef WIN32
                 | 
              
| 51 | 
                  #include  | 
              
| 52 | 
                  #include  | 
              
| 53 | 
                   | 
              
| 54 | 
                  #define popen pt_popen
                 | 
              
| 55 | 
                  #define pclose pt_pclose
                 | 
              
| 56 | 
                   | 
              
| 57 | 
                  HANDLE my_pipein[2], my_pipeout[2], my_pipeerr[2];  | 
              
| 58 | 
                  char my_popenmode = ' ';  | 
              
| 59 | 
                   | 
              
| 60 | 
                  static int  | 
              
| 61 | 
                  my_pipe(HANDLE *readwrite)  | 
              
| 62 | 
                  {
                 | 
              
| 63 | 
                  SECURITY_ATTRIBUTES sa;  | 
              
| 64 | 
                   | 
              
| 65 | 
                      sa.nLength = sizeof(sa);
                 | 
              
| 66 | 
                      sa.bInheritHandle = 1;
                 | 
              
| 67 | 
                      sa.lpSecurityDescriptor = NULL;
                 | 
              
| 68 | 
                   | 
              
| 69 | 
                  if (!CreatePipe (&readwrite[0],&readwrite[1],&sa,1 << 13)) {  | 
              
| 70 | 
                  errno = EMFILE;  | 
              
| 71 | 
                  return -1;  | 
              
| 72 | 
                  }  | 
              
| 73 | 
                   | 
              
| 74 | 
                  return 0;  | 
              
| 75 | 
                  }  | 
              
| 76 | 
                   | 
              
| 77 | 
                  static FILE *
                 | 
              
| 78 | 
                  pt_popen(const char *cmd, const char *mode)  | 
              
| 79 | 
                  {
                 | 
              
| 80 | 
                      FILE *fptr = (FILE *)0;
                 | 
              
| 81 | 
                  PROCESS_INFORMATION piProcInfo;  | 
              
| 82 | 
                  STARTUPINFO siStartInfo;  | 
              
| 83 | 
                      int success, catch_stderr;
                 | 
              
| 84 | 
                   | 
              
| 85 | 
                      my_pipein[0]   = INVALID_HANDLE_VALUE;
                 | 
              
| 86 | 
                      my_pipein[1]   = INVALID_HANDLE_VALUE;
                 | 
              
| 87 | 
                      my_pipeout[0]  = INVALID_HANDLE_VALUE;
                 | 
              
| 88 | 
                      my_pipeout[1]  = INVALID_HANDLE_VALUE;
                 | 
              
| 89 | 
                      my_pipeerr[0]  = INVALID_HANDLE_VALUE;
                 | 
              
| 90 | 
                      my_pipeerr[1]  = INVALID_HANDLE_VALUE;
                 | 
              
| 91 | 
                   | 
              
| 92 | 
                      if (!mode || !*mode)
                 | 
              
| 93 | 
                          goto finito;
                 | 
              
| 94 | 
                   | 
              
| 95 | 
                  my_popenmode = *mode;  | 
              
| 96 | 
                  if (my_popenmode != 'r' && my_popenmode != 'w')  | 
              
| 97 | 
                          goto finito;
                 | 
              
| 98 | 
                   | 
              
| 99 | 
                  catch_stderr = strstr((char *) cmd, "2>&1") != NULL;  | 
              
| 100 | 
                      if (catch_stderr) {
                 | 
              
| 101 | 
                  char *ptr = strstr((char*) cmd, "2>&1");  | 
              
| 102 | 
                          *ptr = '\0';
                 | 
              
| 103 | 
                  }  | 
              
| 104 | 
                   | 
              
| 105 | 
                  if (my_pipe(my_pipein) == -1 || my_pipe(my_pipeout) == -1)  | 
              
| 106 | 
                          goto finito;
                 | 
              
| 107 | 
                  if (!catch_stderr && my_pipe(my_pipeerr) == -1)  | 
              
| 108 | 
                        goto finito;
                 | 
              
| 109 | 
                   | 
              
| 110 | 
                      ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
                 | 
              
| 111 | 
                      siStartInfo.cb             = sizeof(STARTUPINFO);
                 | 
              
| 112 | 
                      siStartInfo.hStdInput      = my_pipein[0];
                 | 
              
| 113 | 
                      siStartInfo.hStdOutput     = my_pipeout[1];
                 | 
              
| 114 | 
                      if (catch_stderr)
                 | 
              
| 115 | 
                          siStartInfo.hStdError  = my_pipeout[1];
                 | 
              
| 116 | 
                      else
                 | 
              
| 117 | 
                          siStartInfo.hStdError  = my_pipeerr[1];
                 | 
              
| 118 | 
                  siStartInfo.dwFlags = STARTF_USESTDHANDLES;  | 
              
| 119 | 
                   | 
              
| 120 | 
                      success = CreateProcess(NULL,
                 | 
              
| 121 | 
                         (LPTSTR)cmd,       // command line
                 | 
              
| 122 | 
                  NULL, // process security attributes  | 
              
| 123 | 
                  NULL, // primary thread security attributes  | 
              
| 124 | 
                         TRUE,              // handles are inherited
                 | 
              
| 125 | 
                         DETACHED_PROCESS,  // creation flags
                 | 
              
| 126 | 
                  NULL, // use parent's environment  | 
              
| 127 | 
                  NULL, // use parent's current directory  | 
              
| 128 | 
                         &siStartInfo,      // STARTUPINFO pointer
                 | 
              
| 129 | 
                         &piProcInfo);      // receives PROCESS_INFORMATION
                 | 
              
| 130 | 
                   | 
              
| 131 | 
                      if (!success)
                 | 
              
| 132 | 
                          goto finito;
                 | 
              
| 133 | 
                   | 
              
| 134 | 
                  CloseHandle(my_pipein[0]); my_pipein[0] = INVALID_HANDLE_VALUE;  | 
              
| 135 | 
                  CloseHandle(my_pipeout[1]); my_pipeout[1] = INVALID_HANDLE_VALUE;  | 
              
| 136 | 
                  CloseHandle(my_pipeerr[1]); my_pipeerr[1] = INVALID_HANDLE_VALUE;  | 
              
| 137 | 
                   | 
              
| 138 | 
                  if (my_popenmode == 'r')  | 
              
| 139 | 
                  fptr = _fdopen(_open_osfhandle((long)my_pipeout[0],_O_BINARY),"r");  | 
              
| 140 | 
                      else
                 | 
              
| 141 | 
                  fptr = _fdopen(_open_osfhandle((long)my_pipein[1],_O_BINARY),"w");  | 
              
| 142 | 
                   | 
              
| 143 | 
                  finito:
                 | 
              
| 144 | 
                      if (!fptr) {
                 | 
              
| 145 | 
                  if (my_pipein[0] != INVALID_HANDLE_VALUE)  | 
              
| 146 | 
                              CloseHandle(my_pipein[0]);
                 | 
              
| 147 | 
                  if (my_pipein[1] != INVALID_HANDLE_VALUE)  | 
              
| 148 | 
                              CloseHandle(my_pipein[1]);
                 | 
              
| 149 | 
                  if (my_pipeout[0] != INVALID_HANDLE_VALUE)  | 
              
| 150 | 
                              CloseHandle(my_pipeout[0]);
                 | 
              
| 151 | 
                  if (my_pipeout[1] != INVALID_HANDLE_VALUE)  | 
              
| 152 | 
                              CloseHandle(my_pipeout[1]);
                 | 
              
| 153 | 
                  if (my_pipeerr[0] != INVALID_HANDLE_VALUE)  | 
              
| 154 | 
                              CloseHandle(my_pipeerr[0]);
                 | 
              
| 155 | 
                  if (my_pipeerr[1] != INVALID_HANDLE_VALUE)  | 
              
| 156 | 
                              CloseHandle(my_pipeerr[1]);
                 | 
              
| 157 | 
                  }  | 
              
| 158 | 
                   | 
              
| 159 | 
                      return fptr;
                 | 
              
| 160 | 
                  }  | 
              
| 161 | 
                   | 
              
| 162 | 
                  static int  | 
              
| 163 | 
                  pt_pclose(FILE *file)  | 
              
| 164 | 
                  {
                 | 
              
| 165 | 
                      if (!file) {
                 | 
              
| 166 | 
                  return -1;  | 
              
| 167 | 
                  }  | 
              
| 168 | 
                   | 
              
| 169 | 
                  fclose(file);  | 
              
| 170 | 
                   | 
              
| 171 | 
                      CloseHandle(my_pipeerr[0]);
                 | 
              
| 172 | 
                  if (my_popenmode == 'r')  | 
              
| 173 | 
                          CloseHandle(my_pipein[1]);
                 | 
              
| 174 | 
                      else
                 | 
              
| 175 | 
                         CloseHandle(my_pipeout[0]);
                 | 
              
| 176 | 
                   | 
              
| 177 | 
                  return 0;  | 
              
| 178 | 
                  }  | 
              
| 179 | 
                  #endif
                 | 
              
| 180 | 
                   | 
              
| 181 | 
                  void
                 | 
              
| 182 | 
                  nntpgrab_plugin_initialize(NGPlugin *plugin_data)  | 
              
| 183 | 
                  {
                 | 
              
| 184 | 
                      ng_plugin_set_name(plugin_data, "PAR2");
                 | 
              
| 185 | 
                  ng_plugin_set_version(plugin_data, PACKAGE_VERSION);  | 
              
| 186 | 
                      ng_plugin_set_author(plugin_data, "Erik van Pienbroek");
                 | 
              
| 187 | 
                      ng_plugin_set_url(plugin_data, "https://www.nntpgrab.nl");
                 | 
              
| 188 | 
                      ng_plugin_set_description(plugin_data, "Automatically verify and repair files using PAR2 after a collection has been downloaded");
                 | 
              
| 189 | 
                   | 
              
| 190 | 
                      ng_plugin_set_required_event(plugin_data, "collection_downloaded");
                 | 
              
| 191 | 
                   | 
              
| 192 | 
                  ng_plugin_register_function(plugin_data,  | 
              
| 193 | 
                                                  "par2_repair_files",
                 | 
              
| 194 | 
                  NG_PLUGIN_FUNCTION(nntpgrab_plugin_par2_repair_files),  | 
              
| 195 | 
                  ng_plugin_marshal_BOOLEAN__STRING_STRING_STRING,  | 
              
| 196 | 
                  G_TYPE_BOOLEAN,  | 
              
| 197 | 
                                                  3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
                 | 
              
| 198 | 
                   | 
              
| 199 | 
                  ng_plugin_create_event(plugin_data, "par2_begin_verify", 2);  | 
              
| 200 | 
                  ng_plugin_create_event(plugin_data, "par2_load_progress_update", 4);  | 
              
| 201 | 
                  ng_plugin_create_event(plugin_data, "par2_recovery_file_loaded", 5);  | 
              
| 202 | 
                  ng_plugin_create_event(plugin_data, "par2_file_loaded", 6);  | 
              
| 203 | 
                  ng_plugin_create_event(plugin_data, "par2_repair_progress_update", 3);  | 
              
| 204 | 
                  ng_plugin_create_event(plugin_data, "par2_repair_failure", 4);  | 
              
| 205 | 
                  ng_plugin_create_event(plugin_data, "par2_repair_success", 3);  | 
              
| 206 | 
                  ng_plugin_create_event(plugin_data, "par2_no_repair_required", 3);  | 
              
| 207 | 
                  }  | 
              
| 208 | 
                   | 
              
| 209 | 
                  ngboolean  | 
              
| 210 | 
                  nntpgrab_plugin_load(NGPlugin *plugin_data, char **errmsg)
                 | 
              
| 211 | 
                  {
                 | 
              
| 212 | 
                      GError *err = NULL;
                 | 
              
| 213 | 
                  PluginPAR2Priv *priv;  | 
              
| 214 | 
                   | 
              
| 215 | 
                  plugin_data->priv = g_slice_new0(PluginPAR2Priv);  | 
              
| 216 | 
                  priv = plugin_data->priv;  | 
              
| 217 | 
                  priv->abort_flag = FALSE;  | 
              
| 218 | 
                      priv->thread_pool = g_thread_pool_new(par2_thread_func, plugin_data, 1, FALSE, &err);
                 | 
              
| 219 | 
                   | 
              
| 220 | 
                      if (!priv->thread_pool) {
                 | 
              
| 221 | 
                          *errmsg = g_strdup_printf(_("%s:%i Unable to create PAR2 thread pool: %s"), __FILE__, __LINE__, err->message);
                 | 
              
| 222 | 
                  g_error_free(err);  | 
              
| 223 | 
                          return FALSE;
                 | 
              
| 224 | 
                  }  | 
              
| 225 | 
                   | 
              
| 226 | 
                  ng_plugin_connect_event(plugin_data, "collection_downloaded", NG_PLUGIN_FUNCTION(on_collection_downloaded), NULL);  | 
              
| 227 | 
                   | 
              
| 228 | 
                      return TRUE;
                 | 
              
| 229 | 
                  }  | 
              
| 230 | 
                   | 
              
| 231 | 
                  ngboolean  | 
              
| 232 | 
                  nntpgrab_plugin_can_unload(NGPlugin *plugin_data, char **reason)
                 | 
              
| 233 | 
                  {
                 | 
              
| 234 | 
                      return TRUE;
                 | 
              
| 235 | 
                  }  | 
              
| 236 | 
                   | 
              
| 237 | 
                  void
                 | 
              
| 238 | 
                  nntpgrab_plugin_unload(NGPlugin *plugin_data)  | 
              
| 239 | 
                  {
                 | 
              
| 240 | 
                  PluginPAR2Priv *priv = plugin_data->priv;  | 
              
| 241 | 
                   | 
              
| 242 | 
                  priv->abort_flag = TRUE;  | 
              
| 243 | 
                  #ifndef WIN32
                 | 
              
| 244 | 
                  if (priv->pid > 0) {  | 
              
| 245 | 
                  kill(priv->pid, SIGTERM);  | 
              
| 246 | 
                  }  | 
              
| 247 | 
                  #endif
                 | 
              
| 248 | 
                  g_thread_pool_free(priv->thread_pool, TRUE, TRUE);  | 
              
| 249 | 
                   | 
              
| 250 | 
                  g_slice_free(PluginPAR2Priv, plugin_data->priv);  | 
              
| 251 | 
                      plugin_data->priv = NULL;
                 | 
              
| 252 | 
                  }  | 
              
| 253 | 
                   | 
              
| 254 | 
                  void
                 | 
              
| 255 | 
                  nntpgrab_plugin_destroy(NGPlugin *plugin_data)  | 
              
| 256 | 
                  {
                 | 
              
| 257 | 
                  }  | 
              
| 258 | 
                   | 
              
| 259 | 
                  int
                 | 
              
| 260 | 
                  nntpgrab_plugin_get_version(void)
                 | 
              
| 261 | 
                  {
                 | 
              
| 262 | 
                      return NNTPGRAB_PLUGIN_API_VERSION;
                 | 
              
| 263 | 
                  }  | 
              
| 264 | 
                   | 
              
| 265 | 
                  #if 0 
                 | 
              
| 266 | 
                  static bool  | 
              
| 267 | 
                  LoadVerificationPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header)  | 
              
| 268 | 
                  {
                 | 
              
| 269 | 
                  VerificationPacket *packet = new VerificationPacket;  | 
              
| 270 | 
                   | 
              
| 271 | 
                  // Load the packet from disk  | 
              
| 272 | 
                  if (!packet->Load(diskfile, offset, header))  | 
              
| 273 | 
                    {
                 | 
              
| 274 | 
                  delete packet;  | 
              
| 275 | 
                  return false;  | 
              
| 276 | 
                  }  | 
              
| 277 | 
                   | 
              
| 278 | 
                  // What is the fileid  | 
              
| 279 | 
                  const MD5Hash &fileid = packet->FileId();  | 
              
| 280 | 
                   | 
              
| 281 | 
                  // Look up the fileid in the source file map for an existing source file entry  | 
              
| 282 | 
                  map  | 
              
| 283 | 
                  Par2RepairerSourceFile *sourcefile = (sfmi == sourcefilemap.end()) ? 0 :sfmi->second;  | 
              
| 284 | 
                   | 
              
| 285 | 
                  // Was there an existing source file  | 
              
| 286 | 
                  if (sourcefile)  | 
              
| 287 | 
                    {
                 | 
              
| 288 | 
                  // Does the source file already have a verification packet  | 
              
| 289 | 
                  if (sourcefile->GetVerificationPacket())  | 
              
| 290 | 
                      {
                 | 
              
| 291 | 
                  // Yes. We don't need another copy.  | 
              
| 292 | 
                  delete packet;  | 
              
| 293 | 
                  return false;  | 
              
| 294 | 
                  }  | 
              
| 295 | 
                  else  | 
              
| 296 | 
                      {
                 | 
              
| 297 | 
                  // No. Store the packet in the source file  | 
              
| 298 | 
                  sourcefile->SetVerificationPacket(packet);  | 
              
| 299 | 
                   | 
              
| 300 | 
                  return true;  | 
              
| 301 | 
                  }  | 
              
| 302 | 
                  }  | 
              
| 303 | 
                  else  | 
              
| 304 | 
                    {
                 | 
              
| 305 | 
                  // Create a new source file for the packet  | 
              
| 306 | 
                  sourcefile = new Par2RepairerSourceFile(NULL, packet);  | 
              
| 307 | 
                   | 
              
| 308 | 
                  // Record the source file in the source file map  | 
              
| 309 | 
                  sourcefilemap.insert(pair  | 
              
| 310 | 
                   | 
              
| 311 | 
                  return true;  | 
              
| 312 | 
                  }  | 
              
| 313 | 
                  }  | 
              
| 314 | 
                  #endif  | 
              
| 315 | 
                   | 
              
| 316 | 
                  gboolean  | 
              
| 317 | 
                  nntpgrab_plugin_par2_load_verification_packet(const char *filename, PAR2Set **par2set)  | 
              
| 318 | 
                  {
                 | 
              
| 319 | 
                  #if 0 
                 | 
              
| 320 | 
                  DiskFile *file;  | 
              
| 321 | 
                  VerificationPacket *packet = NULL;  | 
              
| 322 | 
                   | 
              
| 323 | 
                  g_return_val_if_fail(filename != NULL, FALSE);  | 
              
| 324 | 
                  g_return_val_if_fail(par2set != NULL, FALSE);  | 
              
| 325 | 
                   | 
              
| 326 | 
                  *par2set = NULL;  | 
              
| 327 | 
                   | 
              
| 328 | 
                  file = new DiskFile();  | 
              
| 329 | 
                   | 
              
| 330 | 
                      if (!file->Open(filename)) {
                 | 
              
| 331 | 
                  delete file;  | 
              
| 332 | 
                  return FALSE;  | 
              
| 333 | 
                  }  | 
              
| 334 | 
                   | 
              
| 335 | 
                      g_print("Now reading '%s' for PAR2 verification data..\n", filename);
                 | 
              
| 336 | 
                   | 
              
| 337 | 
                  // How many useable packets have we found  | 
              
| 338 | 
                  uint32_t packets = 0;  | 
              
| 339 | 
                   | 
              
| 340 | 
                  // How big is the file  | 
              
| 341 | 
                  guint64 filesize = file->FileSize();  | 
              
| 342 | 
                      if (filesize <= 0) {
                 | 
              
| 343 | 
                  delete file;  | 
              
| 344 | 
                          g_print("Filesize == 0\n");
                 | 
              
| 345 | 
                  return FALSE;  | 
              
| 346 | 
                  }  | 
              
| 347 | 
                   | 
              
| 348 | 
                  // Allocate a buffer to read data into  | 
              
| 349 | 
                  // The buffer should be large enough to hold a whole  | 
              
| 350 | 
                  // critical packet (i.e. file verification, file description, main,  | 
              
| 351 | 
                  // and creator), but not necessarily a whole recovery packet.  | 
              
| 352 | 
                  size_t buffersize = (size_t)min((u64)1048576, filesize);  | 
              
| 353 | 
                  u8 *buffer = new u8[buffersize];  | 
              
| 354 | 
                   | 
              
| 355 | 
                  // Start at the beginning of the file  | 
              
| 356 | 
                  guint64 offset = 0;  | 
              
| 357 | 
                   | 
              
| 358 | 
                  // Continue as long as there is at least enough for the packet header  | 
              
| 359 | 
                      while (offset + sizeof(PACKET_HEADER) <= filesize) {
                 | 
              
| 360 | 
                  // Attempt to read the next packet header  | 
              
| 361 | 
                  PACKET_HEADER header;  | 
              
| 362 | 
                  if (!file->Read(offset, &header, sizeof(header)))  | 
              
| 363 | 
                  break;  | 
              
| 364 | 
                   | 
              
| 365 | 
                  // Does this look like it might be a packet  | 
              
| 366 | 
                          if (packet_magic != header.magic) {
                 | 
              
| 367 | 
                  offset++;  | 
              
| 368 | 
                   | 
              
| 369 | 
                  // Is there still enough for at least a whole packet header  | 
              
| 370 | 
                              while (offset + sizeof(PACKET_HEADER) <= filesize) {
                 | 
              
| 371 | 
                  // How much can we read into the buffer  | 
              
| 372 | 
                  size_t want = (size_t)min((u64)buffersize, filesize-offset);  | 
              
| 373 | 
                   | 
              
| 374 | 
                  // Fill the buffer  | 
              
| 375 | 
                                  if (!file->Read(offset, buffer, want)) {
                 | 
              
| 376 | 
                  offset = filesize;  | 
              
| 377 | 
                  break;  | 
              
| 378 | 
                  }  | 
              
| 379 | 
                   | 
              
| 380 | 
                  // Scan the buffer for the magic value  | 
              
| 381 | 
                  u8 *current = buffer;  | 
              
| 382 | 
                  u8 *limit = &buffer[want-sizeof(PACKET_HEADER)];  | 
              
| 383 | 
                                  while (current <= limit && packet_magic != ((PACKET_HEADER*)current)->magic) {
                 | 
              
| 384 | 
                  current++;  | 
              
| 385 | 
                  }  | 
              
| 386 | 
                   | 
              
| 387 | 
                  // What file offset did we reach  | 
              
| 388 | 
                  offset += current-buffer;  | 
              
| 389 | 
                   | 
              
| 390 | 
                  // Did we find the magic  | 
              
| 391 | 
                                  if (current <= limit) {
                 | 
              
| 392 | 
                  memcpy(&header, current, sizeof(header));  | 
              
| 393 | 
                  break;  | 
              
| 394 | 
                  }  | 
              
| 395 | 
                  }  | 
              
| 396 | 
                   | 
              
| 397 | 
                  // Did we reach the end of the file  | 
              
| 398 | 
                  if (offset + sizeof(PACKET_HEADER) > filesize)  | 
              
| 399 | 
                              {
                 | 
              
| 400 | 
                  break;  | 
              
| 401 | 
                  }  | 
              
| 402 | 
                  }  | 
              
| 403 | 
                   | 
              
| 404 | 
                  // We have found the magic  | 
              
| 405 | 
                  // Check the packet length  | 
              
| 406 | 
                  if (sizeof(PACKET_HEADER) > header.length || // packet length is too small  | 
              
| 407 | 
                  0 != (header.length & 3) || // packet length is not a multiple of 4  | 
              
| 408 | 
                              filesize < offset + header.length) {     // packet would extend beyond the end of the file
                 | 
              
| 409 | 
                  offset++;  | 
              
| 410 | 
                  continue;  | 
              
| 411 | 
                  }  | 
              
| 412 | 
                   | 
              
| 413 | 
                  // Compute the MD5 Hash of the packet  | 
              
| 414 | 
                  MD5Context context;  | 
              
| 415 | 
                  context.Update(&header.setid, sizeof(header)-offsetof(PACKET_HEADER, setid));  | 
              
| 416 | 
                   | 
              
| 417 | 
                  // How much more do I need to read to get the whole packet  | 
              
| 418 | 
                  u64 current = offset+sizeof(PACKET_HEADER);  | 
              
| 419 | 
                  u64 limit = offset+header.length;  | 
              
| 420 | 
                          while (current < limit) {
                 | 
              
| 421 | 
                  size_t want = (size_t)min((u64)buffersize, limit-current);  | 
              
| 422 | 
                   | 
              
| 423 | 
                  if (!file->Read(current, buffer, want))  | 
              
| 424 | 
                  break;  | 
              
| 425 | 
                   | 
              
| 426 | 
                  context.Update(buffer, want);  | 
              
| 427 | 
                   | 
              
| 428 | 
                  current += want;  | 
              
| 429 | 
                  }  | 
              
| 430 | 
                   | 
              
| 431 | 
                  // Did the whole packet get processed  | 
              
| 432 | 
                          if (current < limit) {
                 | 
              
| 433 | 
                  offset++;  | 
              
| 434 | 
                  continue;  | 
              
| 435 | 
                  }  | 
              
| 436 | 
                   | 
              
| 437 | 
                  // Check the calculated packet hash against the value in the header  | 
              
| 438 | 
                  MD5Hash hash;  | 
              
| 439 | 
                  context.Final(hash);  | 
              
| 440 | 
                          if (hash != header.hash) {
                 | 
              
| 441 | 
                  offset++;  | 
              
| 442 | 
                  continue;  | 
              
| 443 | 
                  }  | 
              
| 444 | 
                   | 
              
| 445 | 
                  // The Set ID is now validated, generate the PAR2Set structure  | 
              
| 446 | 
                  *par2set = (PAR2Set*) g_slice_new0(PAR2Set);  | 
              
| 447 | 
                  memcpy((*par2set)->set_id, header.setid.hash, sizeof(header.setid.hash));  | 
              
| 448 | 
                   | 
              
| 449 | 
                          if (fileverificationpacket_type == header.type)  {
                 | 
              
| 450 | 
                              //if (LoadVerificationPacket(file, offset, header)) {
                 | 
              
| 451 | 
                  packets++;  | 
              
| 452 | 
                  //}  | 
              
| 453 | 
                   | 
              
| 454 | 
                  // Advance to the next packet  | 
              
| 455 | 
                  offset += header.length;  | 
              
| 456 | 
                  }  | 
              
| 457 | 
                  }  | 
              
| 458 | 
                   | 
              
| 459 | 
                  delete [] buffer;  | 
              
| 460 | 
                   | 
              
| 461 | 
                  // We have finished with the file for now  | 
              
| 462 | 
                  file->Close();  | 
              
| 463 | 
                   | 
              
| 464 | 
                  // Did we actually find any interesting packets  | 
              
| 465 | 
                      if (packets == 0) {
                 | 
              
| 466 | 
                          if ((*par2set)) {
                 | 
              
| 467 | 
                  g_slice_free(PAR2Set, (*par2set));  | 
              
| 468 | 
                  }  | 
              
| 469 | 
                   | 
              
| 470 | 
                  delete packet;  | 
              
| 471 | 
                  delete file;  | 
              
| 472 | 
                  return FALSE;  | 
              
| 473 | 
                  }  | 
              
| 474 | 
                   | 
              
| 475 | 
                  delete packet;  | 
              
| 476 | 
                  delete file;  | 
              
| 477 | 
                  #endif  | 
              
| 478 | 
                   | 
              
| 479 | 
                      return TRUE;
                 | 
              
| 480 | 
                  }  | 
              
| 481 | 
                   | 
              
| 482 | 
                  char *
                 | 
              
| 483 | 
                  strip_large_filenames(const char *filename)  | 
              
| 484 | 
                  {
                 | 
              
| 485 | 
                      /* par2cmdline strips filenames which are larger than 56 characters. 
                 | 
              
| 486 | 
                  * With this function, the same algoritm to strip such filenames can be applied */  | 
              
| 487 | 
                  char filename_stripped[1024];  | 
              
| 488 | 
                      int len = strlen(filename);
                 | 
              
| 489 | 
                   | 
              
| 490 | 
                  if (len < 56) {  | 
              
| 491 | 
                          return g_strdup(filename);
                 | 
              
| 492 | 
                  }  | 
              
| 493 | 
                   | 
              
| 494 | 
                  memset(filename_stripped, 0, sizeof(filename_stripped));  | 
              
| 495 | 
                      strncpy(filename_stripped, filename, 28);
                 | 
              
| 496 | 
                      strcat(filename_stripped, "...");
                 | 
              
| 497 | 
                  strncat(filename_stripped, filename + len - 28, sizeof(filename_stripped) - 28 - 3 - 1);  | 
              
| 498 | 
                   | 
              
| 499 | 
                      return g_strdup(filename_stripped);
                 | 
              
| 500 | 
                  }  | 
              
| 501 | 
                   | 
              
| 502 | 
                  struct _par2_repair_file {
                 | 
              
| 503 | 
                      char filename[PATH_MAX];
                 | 
              
| 504 | 
                      double verify_progress;
                 | 
              
| 505 | 
                      int num_packets_found;
                 | 
              
| 506 | 
                      int num_blocks_found;
                 | 
              
| 507 | 
                      int num_duplicate_blocks_found;
                 | 
              
| 508 | 
                      int num_blocks_expected;
                 | 
              
| 509 | 
                  gboolean is_missing;  | 
              
| 510 | 
                  gboolean is_complete;  | 
              
| 511 | 
                  time_t stamp_last_progress_update;  | 
              
| 512 | 
                  };  | 
              
| 513 | 
                   | 
              
| 514 | 
                  static time_t stamp_last_repair_progress_update = 0;  | 
              
| 515 | 
                   | 
              
| 516 | 
                  static struct _par2_repair_file *  | 
              
| 517 | 
                  process_message(NGPlugin *plugin_data, GHashTable *files, struct _par2_repair_file *active_file, const char *line, ngboolean more_par2_sets_remaining_in_collection)  | 
              
| 518 | 
                  {
                 | 
              
| 519 | 
                  struct _par2_repair_file *file = NULL;  | 
              
| 520 | 
                  char *filename = NULL;  | 
              
| 521 | 
                  char *filename_stripped = NULL;  | 
              
| 522 | 
                  PluginPAR2Priv *priv = plugin_data->priv;  | 
              
| 523 | 
                  const char *params[7];  | 
              
| 524 | 
                  char progress_str[16];  | 
              
| 525 | 
                  char num_packets_found_str[16];  | 
              
| 526 | 
                  char num_blocks_found_str[16];  | 
              
| 527 | 
                  char num_blocks_expected_str[16];  | 
              
| 528 | 
                   | 
              
| 529 | 
                      // NOTE: par2cmdline can do wrapping of the filename!
                 | 
              
| 530 | 
                   | 
              
| 531 | 
                  memset(&progress_str, 0, sizeof(progress_str));  | 
              
| 532 | 
                  memset(&num_packets_found_str, 0, sizeof(num_packets_found_str));  | 
              
| 533 | 
                  memset(&num_blocks_found_str, 0, sizeof(num_blocks_found_str));  | 
              
| 534 | 
                  memset(&num_blocks_expected_str, 0, sizeof(num_blocks_expected_str));  | 
              
| 535 | 
                   | 
              
| 536 | 
                  g_return_val_if_fail(priv->collection_name != NULL, NULL);  | 
              
| 537 | 
                  g_return_val_if_fail(priv->par2filename != NULL, NULL);  | 
              
| 538 | 
                   | 
              
| 539 | 
                  if (!strncmp(line, "Loading \"", 9)) { // Loading "Top 40 dossier _1994 final_.par2".  | 
              
| 540 | 
                  char *end = strstr(line + 10, "\"");  | 
              
| 541 | 
                   | 
              
| 542 | 
                  g_return_val_if_fail(end != NULL, NULL);  | 
              
| 543 | 
                   | 
              
| 544 | 
                  filename = g_strndup(line + 9, end - line - 9);  | 
              
| 545 | 
                  filename_stripped = strip_large_filenames(filename);  | 
              
| 546 | 
                  file = g_hash_table_lookup(files, filename_stripped);  | 
              
| 547 | 
                          if (!file) {
                 | 
              
| 548 | 
                              file = g_slice_new0(struct _par2_repair_file);
                 | 
              
| 549 | 
                  strncpy(file->filename, filename, sizeof(file->filename) - 1);  | 
              
| 550 | 
                  g_hash_table_insert(files, g_strdup(filename_stripped), file);  | 
              
| 551 | 
                  }  | 
              
| 552 | 
                   | 
              
| 553 | 
                  g_free(filename_stripped);  | 
              
| 554 | 
                  g_free(filename);  | 
              
| 555 | 
                   | 
              
| 556 | 
                  snprintf(progress_str, sizeof(progress_str) - 1, "%.2f", file->verify_progress);  | 
              
| 557 | 
                          params[0] = priv->collection_name;
                 | 
              
| 558 | 
                          params[1] = priv->par2filename;
                 | 
              
| 559 | 
                          params[2] = file->filename;
                 | 
              
| 560 | 
                          params[3] = progress_str;
                 | 
              
| 561 | 
                  params[4] = NULL;  | 
              
| 562 | 
                          ng_plugin_emit_event(plugin_data, "par2_load_progress_update", params);
                 | 
              
| 563 | 
                  } else if (!strncmp(line, "Loading: ", 9)) { // Loading: 97.9%  | 
              
| 564 | 
                  g_return_val_if_fail(active_file != NULL, NULL);  | 
              
| 565 | 
                   | 
              
| 566 | 
                  active_file->verify_progress = g_ascii_strtod(line + 9, NULL);  | 
              
| 567 | 
                  file = active_file;  | 
              
| 568 | 
                  if (active_file->stamp_last_progress_update != time(NULL)) {  | 
              
| 569 | 
                  snprintf(progress_str, sizeof(progress_str) - 1, "%.2f", file->verify_progress);  | 
              
| 570 | 
                   | 
              
| 571 | 
                              params[0] = priv->collection_name;
                 | 
              
| 572 | 
                              params[1] = priv->par2filename;
                 | 
              
| 573 | 
                              params[2] = active_file->filename;
                 | 
              
| 574 | 
                              params[3] = progress_str;
                 | 
              
| 575 | 
                  params[4] = NULL;  | 
              
| 576 | 
                              ng_plugin_emit_event(plugin_data, "par2_load_progress_update", params);
                 | 
              
| 577 | 
                              active_file->stamp_last_progress_update = time(NULL);
                 | 
              
| 578 | 
                  }  | 
              
| 579 | 
                  } else if (!strncmp(line, "Loaded ", 7)) { // Loaded 4 new packets  | 
              
| 580 | 
                                                                              // Loaded 12 new packets including 12 recovery blocks
                 | 
              
| 581 | 
                  g_return_val_if_fail(active_file != NULL, NULL);  | 
              
| 582 | 
                   | 
              
| 583 | 
                  if (sscanf(line, "Loaded %d new packets including %d recovery blocks", &active_file->num_packets_found, &active_file->num_blocks_found) != 1) {  | 
              
| 584 | 
                              // line == 'Loaded x new packets'
                 | 
              
| 585 | 
                              active_file->num_packets_found = atoi(line + 7);
                 | 
              
| 586 | 
                  }  | 
              
| 587 | 
                   | 
              
| 588 | 
                  active_file->verify_progress = 100.0;  | 
              
| 589 | 
                   | 
              
| 590 | 
                  snprintf(num_packets_found_str, sizeof(num_packets_found_str) - 1, "%i", active_file->num_packets_found);  | 
              
| 591 | 
                  snprintf(num_blocks_found_str, sizeof(num_blocks_found_str) - 1, "%i", active_file->num_blocks_found);  | 
              
| 592 | 
                          params[0] = priv->collection_name;
                 | 
              
| 593 | 
                          params[1] = priv->par2filename;
                 | 
              
| 594 | 
                          params[2] = active_file->filename;
                 | 
              
| 595 | 
                          params[3] = num_packets_found_str;
                 | 
              
| 596 | 
                          params[4] = num_blocks_found_str;
                 | 
              
| 597 | 
                  params[5] = NULL;  | 
              
| 598 | 
                          ng_plugin_emit_event(plugin_data, "par2_recovery_file_loaded", params);
                 | 
              
| 599 | 
                   | 
              
| 600 | 
                          /* Maintain a list of PAR2 files which can be removed after the verification/reparation has succeeded */
                 | 
              
| 601 | 
                  priv->par2files = g_list_append(priv->par2files, g_strdup(active_file->filename));  | 
              
| 602 | 
                  } else if (!strncmp(line, "No new packets found", 20)) {  | 
              
| 603 | 
                  g_return_val_if_fail(active_file != NULL, NULL);  | 
              
| 604 | 
                          active_file->num_packets_found = 0;
                 | 
              
| 605 | 
                  active_file->verify_progress = 100.0;  | 
              
| 606 | 
                   | 
              
| 607 | 
                  snprintf(num_packets_found_str, sizeof(num_packets_found_str) - 1, "%i", active_file->num_packets_found);  | 
              
| 608 | 
                  snprintf(num_blocks_found_str, sizeof(num_blocks_found_str) - 1, "%i", active_file->num_blocks_found);  | 
              
| 609 | 
                          params[0] = priv->collection_name;
                 | 
              
| 610 | 
                          params[1] = priv->par2filename;
                 | 
              
| 611 | 
                          params[2] = active_file->filename;
                 | 
              
| 612 | 
                          params[3] = num_packets_found_str;
                 | 
              
| 613 | 
                          params[4] = num_blocks_found_str;
                 | 
              
| 614 | 
                  params[5] = NULL;  | 
              
| 615 | 
                          ng_plugin_emit_event(plugin_data, "par2_recovery_file_loaded", params);
                 | 
              
| 616 | 
                   | 
              
| 617 | 
                          /* Maintain a list of PAR2 files which can be removed after the verification/reparation has succeeded */
                 | 
              
| 618 | 
                  priv->par2files = g_list_append(priv->par2files, g_strdup(active_file->filename));  | 
              
| 619 | 
                  } else if (!strncmp(line, "Scanning: \"", 11)) { // Scanning: "post832.rar": 0.5%  | 
              
| 620 | 
                  char *end = strstr(line + 12, "\"");  | 
              
| 621 | 
                   | 
              
| 622 | 
                  g_return_val_if_fail(end != NULL, NULL);  | 
              
| 623 | 
                   | 
              
| 624 | 
                  filename = g_strndup(line + 11, end - line - 11);  | 
              
| 625 | 
                  filename_stripped = strip_large_filenames(filename);  | 
              
| 626 | 
                  file = g_hash_table_lookup(files, filename_stripped);  | 
              
| 627 | 
                   | 
              
| 628 | 
                          /* On Win32 environments, it can happen that the filename is saved in a different 
                 | 
              
| 629 | 
                  * character encoding than UTF-8. This can confuse the code above, so add a ugly  | 
              
| 630 | 
                  * workaround for these kind of situations */  | 
              
| 631 | 
                          if (!file) {
                 | 
              
| 632 | 
                              file = g_slice_new0(struct _par2_repair_file);
                 | 
              
| 633 | 
                  strncpy(file->filename, filename, sizeof(file->filename) - 1);  | 
              
| 634 | 
                  g_hash_table_insert(files, g_strdup(filename_stripped), file);  | 
              
| 635 | 
                  }  | 
              
| 636 | 
                   | 
              
| 637 | 
                  g_free(filename_stripped);  | 
              
| 638 | 
                  g_free(filename);  | 
              
| 639 | 
                   | 
              
| 640 | 
                  g_return_val_if_fail(file != NULL, NULL);  | 
              
| 641 | 
                   | 
              
| 642 | 
                  file->verify_progress = g_ascii_strtod(end + 3, NULL);  | 
              
| 643 | 
                  if (file->stamp_last_progress_update != time(NULL)) {  | 
              
| 644 | 
                  snprintf(progress_str, sizeof(progress_str) - 1, "%.2f", file->verify_progress);  | 
              
| 645 | 
                              params[0] = priv->collection_name;
                 | 
              
| 646 | 
                              params[1] = priv->par2filename;
                 | 
              
| 647 | 
                              params[2] = file->filename;
                 | 
              
| 648 | 
                              params[3] = progress_str;
                 | 
              
| 649 | 
                  params[4] = NULL;  | 
              
| 650 | 
                              ng_plugin_emit_event(plugin_data, "par2_load_progress_update", params);
                 | 
              
| 651 | 
                   | 
              
| 652 | 
                              file->stamp_last_progress_update = time(NULL);
                 | 
              
| 653 | 
                  }  | 
              
| 654 | 
                  } else if (!strncmp(line, "Target: \"", 9)) { // Target: "post832.rar" - found.  | 
              
| 655 | 
                  char *end = strstr(line, "\" - ");  | 
              
| 656 | 
                  const char *state_str = NULL;  | 
              
| 657 | 
                   | 
              
| 658 | 
                  g_return_val_if_fail(end != NULL, NULL);  | 
              
| 659 | 
                   | 
              
| 660 | 
                  filename = g_strndup(line + 9, end - line - 9);  | 
              
| 661 | 
                  filename_stripped = strip_large_filenames(filename);  | 
              
| 662 | 
                  file = g_hash_table_lookup(files, filename_stripped);  | 
              
| 663 | 
                   | 
              
| 664 | 
                          // The file can be missing, add a fallback for that situation
                 | 
              
| 665 | 
                          if (!file) {
                 | 
              
| 666 | 
                              file = g_slice_new0(struct _par2_repair_file);
                 | 
              
| 667 | 
                  strncpy(file->filename, filename, sizeof(file->filename) - 1);  | 
              
| 668 | 
                  g_hash_table_insert(files, g_strdup(filename_stripped), file);  | 
              
| 669 | 
                  }  | 
              
| 670 | 
                   | 
              
| 671 | 
                  g_free(filename_stripped);  | 
              
| 672 | 
                  g_free(filename);  | 
              
| 673 | 
                   | 
              
| 674 | 
                  g_return_val_if_fail(file != NULL, NULL);  | 
              
| 675 | 
                   | 
              
| 676 | 
                          end += 4;
                 | 
              
| 677 | 
                  if (!strncmp(end, "found", 5)) {  | 
              
| 678 | 
                  file->is_complete = TRUE;  | 
              
| 679 | 
                              state_str = "FOUND";
                 | 
              
| 680 | 
                  } else if (!strncmp(end, "missing", 7)) {  | 
              
| 681 | 
                  file->is_missing = TRUE;  | 
              
| 682 | 
                              state_str = "MISSING";
                 | 
              
| 683 | 
                  } else if (!strncmp(end, "damaged", 7)) {  | 
              
| 684 | 
                  if (strstr(end, "from several target files")) {  | 
              
| 685 | 
                                  state_str = "NO_NEW_BLOCKS_FOUND";
                 | 
              
| 686 | 
                              } else {
                 | 
              
| 687 | 
                  g_return_val_if_fail (sscanf(end, "damaged. Found %d of %d data blocks.", &file->num_blocks_found, &file->num_blocks_expected) == 2, NULL);  | 
              
| 688 | 
                                  state_str = "DAMAGED";
                 | 
              
| 689 | 
                  }  | 
              
| 690 | 
                          } else {
                 | 
              
| 691 | 
                              g_print(__FILE__ ":%i reason = %s\n", __LINE__, end);
                 | 
              
| 692 | 
                              state_str = "MISSING";
                 | 
              
| 693 | 
                  }  | 
              
| 694 | 
                   | 
              
| 695 | 
                  snprintf(num_blocks_found_str, sizeof(num_blocks_found_str) - 1, "%i", file->num_blocks_found);  | 
              
| 696 | 
                  snprintf(num_blocks_expected_str, sizeof(num_blocks_expected_str) - 1, "%i", file->num_blocks_expected);  | 
              
| 697 | 
                   | 
              
| 698 | 
                          params[0] = priv->collection_name,
                 | 
              
| 699 | 
                          params[1] = priv->par2filename;
                 | 
              
| 700 | 
                          params[2] = file->filename;
                 | 
              
| 701 | 
                          params[3] = state_str;
                 | 
              
| 702 | 
                          params[4] = num_blocks_found_str;
                 | 
              
| 703 | 
                          params[5] = num_blocks_expected_str;
                 | 
              
| 704 | 
                  params[6] = NULL;  | 
              
| 705 | 
                          ng_plugin_emit_event(plugin_data, "par2_file_loaded", params);
                 | 
              
| 706 | 
                  } else if (!strncmp(line, "File: \"", 7)) { // File: "1994_026_CDS__TW_ Prodigy - No Good _Start The Dance_ _Original Mix_.mp3" - found 12 of 13 data blocks from "1994_026[CDS][TW] Prodigy - No Good (Start The Dance) (Original Mix).mp3".  | 
              
| 707 | 
                  char *end = strstr(line, "\" - ");  | 
              
| 708 | 
                  const char *state_str;  | 
              
| 709 | 
                   | 
              
| 710 | 
                  g_return_val_if_fail(end != NULL, NULL);  | 
              
| 711 | 
                   | 
              
| 712 | 
                  filename = g_strndup(line + 7, end - line - 7);  | 
              
| 713 | 
                  filename_stripped = strip_large_filenames(filename);  | 
              
| 714 | 
                  file = g_hash_table_lookup(files, filename_stripped);  | 
              
| 715 | 
                   | 
              
| 716 | 
                          // The file can be missing, add a fallback for that situation
                 | 
              
| 717 | 
                          if (!file) {
                 | 
              
| 718 | 
                              file = g_slice_new0(struct _par2_repair_file);
                 | 
              
| 719 | 
                  strncpy(file->filename, filename, sizeof(file->filename) - 1);  | 
              
| 720 | 
                  g_hash_table_insert(files, g_strdup(filename_stripped), file);  | 
              
| 721 | 
                  }  | 
              
| 722 | 
                   | 
              
| 723 | 
                  g_free(filename);  | 
              
| 724 | 
                   | 
              
| 725 | 
                  g_return_val_if_fail(file != NULL, NULL);  | 
              
| 726 | 
                   | 
              
| 727 | 
                          end += 4;
                 | 
              
| 728 | 
                  if (strncmp(end, "no data found.", 14) &&  | 
              
| 729 | 
                  strncmp(end, "is a match for", 14) &&  | 
              
| 730 | 
                              !strstr(end, "duplicate data blocks.") &&
                 | 
              
| 731 | 
                              !strstr(end, "data blocks from several target files")) {
                 | 
              
| 732 | 
                   | 
              
| 733 | 
                  g_return_val_if_fail (sscanf(end, "found %d of %d data blocks", &file->num_blocks_found, &file->num_blocks_expected) == 2, NULL);  | 
              
| 734 | 
                   | 
              
| 735 | 
                              if (file->num_blocks_found == file->num_blocks_expected) {
                 | 
              
| 736 | 
                                  state_str = "FOUND";
                 | 
              
| 737 | 
                              } else {
                 | 
              
| 738 | 
                                  state_str = "DAMAGED";
                 | 
              
| 739 | 
                  }  | 
              
| 740 | 
                  } else if (!strncmp(end, "no data found.", 14) ||  | 
              
| 741 | 
                                      strstr(end, "data blocks from several target files") ||
                 | 
              
| 742 | 
                                      strstr(end, "duplicate data blocks.")) {
                 | 
              
| 743 | 
                   | 
              
| 744 | 
                              state_str = "NO_NEW_BLOCKS_FOUND";
                 | 
              
| 745 | 
                  } else if (!strncmp(end, "is a match for", 14)) {  | 
              
| 746 | 
                              file->num_blocks_found = file->num_blocks_expected = 1;
                 | 
              
| 747 | 
                  file->verify_progress = 100.0;  | 
              
| 748 | 
                   | 
              
| 749 | 
                              state_str = "FOUND";
                 | 
              
| 750 | 
                          } else {
                 | 
              
| 751 | 
                  g_print(__FILE__ ":%i unknown state for PAR2 message: %s (end = %s)\n", __LINE__, line, end);
                 | 
              
| 752 | 
                              state_str = "MISSING";
                 | 
              
| 753 | 
                  }  | 
              
| 754 | 
                   | 
              
| 755 | 
                  g_free(filename_stripped);  | 
              
| 756 | 
                   | 
              
| 757 | 
                  snprintf(num_blocks_found_str, sizeof(num_blocks_found_str) - 1, "%i", file->num_blocks_found);  | 
              
| 758 | 
                  snprintf(num_blocks_expected_str, sizeof(num_blocks_expected_str) - 1, "%i", file->num_blocks_expected);  | 
              
| 759 | 
                   | 
              
| 760 | 
                          params[0] = priv->collection_name;
                 | 
              
| 761 | 
                          params[1] = priv->par2filename;
                 | 
              
| 762 | 
                          params[2] = file->filename;
                 | 
              
| 763 | 
                          params[3] = state_str;
                 | 
              
| 764 | 
                          params[4] = num_blocks_found_str;
                 | 
              
| 765 | 
                          params[5] = num_blocks_expected_str;
                 | 
              
| 766 | 
                  params[6] = NULL;  | 
              
| 767 | 
                          ng_plugin_emit_event(plugin_data, "par2_file_loaded", params);
                 | 
              
| 768 | 
                  } else if (!strncmp(line, "You need ", 9)) { // You need 3000 more recovery blocks to be able to repair  | 
              
| 769 | 
                  char num_blocks_required_str[16];  | 
              
| 770 | 
                  int num_blocks_required = atoi(line + 9);  | 
              
| 771 | 
                   | 
              
| 772 | 
                  priv->repair_succeeded = FALSE;  | 
              
| 773 | 
                  priv->command_completed_normally = TRUE;  | 
              
| 774 | 
                   | 
              
| 775 | 
                  memset(&num_blocks_required_str, 0, sizeof(num_blocks_required_str));  | 
              
| 776 | 
                  snprintf(num_blocks_required_str, sizeof(num_blocks_required_str) - 1, "%i", num_blocks_required);  | 
              
| 777 | 
                          params[0] = priv->collection_name;
                 | 
              
| 778 | 
                          params[1] = priv->par2filename;
                 | 
              
| 779 | 
                  params[2] = "";  | 
              
| 780 | 
                          params[3] = num_blocks_required_str;
                 | 
              
| 781 | 
                  params[4] = NULL;  | 
              
| 782 | 
                          ng_plugin_emit_event(plugin_data, "par2_repair_failure", params);
                 | 
              
| 783 | 
                  } else if (!strncmp(line, "Repairing:", 10)) {  | 
              
| 784 | 
                  double progress = g_ascii_strtod(line + 11, NULL);  | 
              
| 785 | 
                  if (stamp_last_repair_progress_update != time(NULL)) {  | 
              
| 786 | 
                  snprintf(progress_str, sizeof(progress_str) - 1, "%.2f", progress);  | 
              
| 787 | 
                   | 
              
| 788 | 
                              params[0] = priv->collection_name;
                 | 
              
| 789 | 
                              params[1] = priv->par2filename;
                 | 
              
| 790 | 
                              params[2] = progress_str;
                 | 
              
| 791 | 
                  params[3] = NULL;  | 
              
| 792 | 
                              ng_plugin_emit_event(plugin_data, "par2_repair_progress_update", params);
                 | 
              
| 793 | 
                              stamp_last_repair_progress_update = time(NULL);
                 | 
              
| 794 | 
                  }  | 
              
| 795 | 
                  } else if (!strncmp(line, "Repair complete.", 16)) {  | 
              
| 796 | 
                  priv->repair_succeeded = TRUE;  | 
              
| 797 | 
                  priv->command_completed_normally = TRUE;  | 
              
| 798 | 
                   | 
              
| 799 | 
                          params[0] = priv->collection_name;
                 | 
              
| 800 | 
                          params[1] = priv->par2filename;
                 | 
              
| 801 | 
                          if (more_par2_sets_remaining_in_collection) {
                 | 
              
| 802 | 
                  params[2] = "TRUE";  | 
              
| 803 | 
                          } else {
                 | 
              
| 804 | 
                  params[2] = "FALSE";  | 
              
| 805 | 
                  }  | 
              
| 806 | 
                  params[3] = NULL;  | 
              
| 807 | 
                          ng_plugin_emit_event(plugin_data, "par2_repair_success", params);
                 | 
              
| 808 | 
                  } else if (!strncmp(line, "All files are correct, repair is not required.", 46)) {  | 
              
| 809 | 
                  priv->repair_succeeded = TRUE;  | 
              
| 810 | 
                  priv->command_completed_normally = TRUE;  | 
              
| 811 | 
                   | 
              
| 812 | 
                          params[0] = priv->collection_name;
                 | 
              
| 813 | 
                          params[1] = priv->par2filename;
                 | 
              
| 814 | 
                          if (more_par2_sets_remaining_in_collection) {
                 | 
              
| 815 | 
                  params[2] = "TRUE";  | 
              
| 816 | 
                          } else {
                 | 
              
| 817 | 
                  params[2] = "FALSE";  | 
              
| 818 | 
                  }  | 
              
| 819 | 
                  params[3] = NULL;  | 
              
| 820 | 
                          ng_plugin_emit_event(plugin_data, "par2_no_repair_required", params);
                 | 
              
| 821 | 
                  } else if (!strncmp(line, "Could not read", 14)) {  | 
              
| 822 | 
                  priv->repair_succeeded = FALSE;  | 
              
| 823 | 
                  priv->command_completed_normally = TRUE;  | 
              
| 824 | 
                   | 
              
| 825 | 
                          params[0] = priv->collection_name;
                 | 
              
| 826 | 
                          params[1] = priv->par2filename;
                 | 
              
| 827 | 
                          params[2] = line;
                 | 
              
| 828 | 
                  params[3] = "-1";  | 
              
| 829 | 
                  params[4] = NULL;  | 
              
| 830 | 
                          ng_plugin_emit_event(plugin_data, "par2_repair_failure", params);
                 | 
              
| 831 | 
                  } else if (!strncmp(line, "The recovery file does not exist", 32)) {  | 
              
| 832 | 
                  priv->repair_succeeded = FALSE;  | 
              
| 833 | 
                  priv->command_completed_normally = TRUE;  | 
              
| 834 | 
                   | 
              
| 835 | 
                          params[0] = priv->collection_name;
                 | 
              
| 836 | 
                          params[1] = priv->par2filename;
                 | 
              
| 837 | 
                  params[2] = "Bug in par2cmdline was triggered";  | 
              
| 838 | 
                  params[3] = "-1";  | 
              
| 839 | 
                  params[4] = NULL;  | 
              
| 840 | 
                          ng_plugin_emit_event(plugin_data, "par2_repair_failure", params);
                 | 
              
| 841 | 
                  } else if (strstr(line, "cannot be renamed to")) {  | 
              
| 842 | 
                  priv->repair_succeeded = FALSE;  | 
              
| 843 | 
                  priv->command_completed_normally = TRUE;  | 
              
| 844 | 
                   | 
              
| 845 | 
                          params[0] = priv->collection_name;
                 | 
              
| 846 | 
                          params[1] = priv->par2filename;
                 | 
              
| 847 | 
                  params[2] = "Error: par2cmdline wasn't able to rename a file";  | 
              
| 848 | 
                  params[3] = "-1";  | 
              
| 849 | 
                  params[4] = NULL;  | 
              
| 850 | 
                          ng_plugin_emit_event(plugin_data, "par2_repair_failure", params);
                 | 
              
| 851 | 
                  } else if (!strncmp(line, "pid", 3)) {  | 
              
| 852 | 
                          priv->pid = atoi(line + 3);
                 | 
              
| 853 | 
                      } else {
                 | 
              
| 854 | 
                  // g_print("line = %s - active_file = %#x\n", line, active_file);
                 | 
              
| 855 | 
                  }  | 
              
| 856 | 
                      /* 
                 | 
              
| 857 | 
                  Loading "Top 40 dossier _1994 final_.par2".  | 
              
| 858 | 
                  Loaded 5 new packets including 1 recovery blocks  | 
              
| 859 | 
                  Repair is required.  | 
              
| 860 | 
                  Repair is not possible.  | 
              
| 861 | 
                  You need 3000 more recovery blocks to be able to repair.  | 
              
| 862 | 
                  Loading: 99.9%  | 
              
| 863 | 
                  Loaded 712 new packets  | 
              
| 864 | 
                  No new packets found  | 
              
| 865 | 
                  There are 355 recoverable files and 0 other files.  | 
              
| 866 | 
                  The block size used was 1223232 bytes.  | 
              
| 867 | 
                  There are a total of 3000 data blocks.  | 
              
| 868 | 
                  The total size of the data files is 3453446279 bytes.  | 
              
| 869 | 
                  Verifying source files:  | 
              
| 870 | 
                  Scanning: "post832.rar": 0.5%  | 
              
| 871 | 
                  Target: "post832.rar" - found.  | 
              
| 872 | 
                  Target: "1994_001[CD][ME] Marco Borsato - Dromen Zijn Bedrog.mp3" - missing.  | 
              
| 873 | 
                  Scanning extra files:  | 
              
| 874 | 
                  File: "1994_026_CDS__TW_ Prodigy - No Good _Start The Dance_ _Original Mix_.mp3" - found 12 of 13 data blocks from "1994_026[CDS][TW] Prodigy - No Good (Start The Dance) (Original Mix).mp3".  | 
              
| 875 | 
                  File: "vrllvhnbnz.part032.rar" - no data found.  | 
              
| 876 | 
                  All files are correct, repair is not required.  | 
              
| 877 | 
                  */  | 
              
| 878 | 
                      return file;
                 | 
              
| 879 | 
                  }  | 
              
| 880 | 
                   | 
              
| 881 | 
                  static void  | 
              
| 882 | 
                  par2_repair_file_free(gpointer data)  | 
              
| 883 | 
                  {
                 | 
              
| 884 | 
                      g_slice_free(struct _par2_repair_file, data);
                 | 
              
| 885 | 
                  }  | 
              
| 886 | 
                   | 
              
| 887 | 
                  static gboolean
                 | 
              
| 888 | 
                  verify_par2_extension(const char *filename)  | 
              
| 889 | 
                  {
                 | 
              
| 890 | 
                  char *ptr = g_strrstr(filename, ".");  | 
              
| 891 | 
                   | 
              
| 892 | 
                      if (!ptr) {
                 | 
              
| 893 | 
                          /* File doesn't have an extension */
                 | 
              
| 894 | 
                          return FALSE;
                 | 
              
| 895 | 
                  }  | 
              
| 896 | 
                   | 
              
| 897 | 
                      /* Does the filename have an '.par' or '.par2' extension? */
                 | 
              
| 898 | 
                  if (!g_ascii_strncasecmp(ptr, ".par2", 5) || !g_strncasecmp(ptr, ".par", 4)) {  | 
              
| 899 | 
                          return TRUE;
                 | 
              
| 900 | 
                  }  | 
              
| 901 | 
                   | 
              
| 902 | 
                      /* Test for files with the extension '.par.1' or '.par2.1' */
                 | 
              
| 903 | 
                      ptr = g_strrstr(filename, ".");
                 | 
              
| 904 | 
                      if (!ptr) {
                 | 
              
| 905 | 
                          return FALSE;
                 | 
              
| 906 | 
                  }  | 
              
| 907 | 
                   | 
              
| 908 | 
                  if (!g_ascii_strncasecmp(ptr, ".par2.", 6) || !g_strncasecmp(ptr, ".par.", 5)) {  | 
              
| 909 | 
                          return TRUE;
                 | 
              
| 910 | 
                  }  | 
              
| 911 | 
                   | 
              
| 912 | 
                      return FALSE;
                 | 
              
| 913 | 
                  }  | 
              
| 914 | 
                   | 
              
| 915 | 
                  static gboolean
                 | 
              
| 916 | 
                  par2_start_par2cmdline(NGPlugin *plugin_data, const char *collection_name, const char *directory, const char *par2filename, GHashTable *files, gboolean try_to_disable_concurrent_verify, gboolean *invalid_option_detected, ngboolean more_par2_sets_remaining_in_collection)  | 
              
| 917 | 
                  {
                 | 
              
| 918 | 
                  FILE *fp;  | 
              
| 919 | 
                      char *cmd;
                 | 
              
| 920 | 
                  char buf[1024];  | 
              
| 921 | 
                      int i;
                 | 
              
| 922 | 
                      int c;
                 | 
              
| 923 | 
                  struct _par2_repair_file *active_file = NULL;  | 
              
| 924 | 
                  const char *disable_concurrent_verify_option;  | 
              
| 925 | 
                  gboolean first_line = TRUE;  | 
              
| 926 | 
                  #ifndef WIN32
                 | 
              
| 927 | 
                  gboolean second_line = FALSE;  | 
              
| 928 | 
                  #endif
                 | 
              
| 929 | 
                  PluginPAR2Priv *priv;  | 
              
| 930 | 
                      static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
                 | 
              
| 931 | 
                  NGConfigOpts opts;  | 
              
| 932 | 
                  GList *list;  | 
              
| 933 | 
                  #ifdef WIN32
                 | 
              
| 934 | 
                      char *filename;
                 | 
              
| 935 | 
                  char *dirname = g_locale_from_utf8(directory, -1, NULL, NULL, NULL);  | 
              
| 936 | 
                  char *bindir = g_path_get_dirname(__argv[0]);  | 
              
| 937 | 
                      char *cmd2;
                 | 
              
| 938 | 
                   | 
              
| 939 | 
                      g_return_val_if_fail(dirname != NULL, FALSE);
                 | 
              
| 940 | 
                  #endif
                 | 
              
| 941 | 
                   | 
              
| 942 | 
                  priv = plugin_data->priv;  | 
              
| 943 | 
                   | 
              
| 944 | 
                      if (priv->abort_flag) {
                 | 
              
| 945 | 
                  const char *params[5];  | 
              
| 946 | 
                   | 
              
| 947 | 
                  priv->repair_succeeded = FALSE;  | 
              
| 948 | 
                          params[0] = priv->collection_name;
                 | 
              
| 949 | 
                          params[1] = priv->par2filename;
                 | 
              
| 950 | 
                  params[2] = "Error: par2cmdline aborted unexpectedly";  | 
              
| 951 | 
                  params[3] = "-1";  | 
              
| 952 | 
                  params[4] = NULL;  | 
              
| 953 | 
                          ng_plugin_emit_event(plugin_data, "par2_repair_failure", params);
                 | 
              
| 954 | 
                   | 
              
| 955 | 
                          return FALSE;
                 | 
              
| 956 | 
                  }  | 
              
| 957 | 
                   | 
              
| 958 | 
                  g_static_mutex_lock(&mutex);  | 
              
| 959 | 
                   | 
              
| 960 | 
                      if (try_to_disable_concurrent_verify) {
                 | 
              
| 961 | 
                          disable_concurrent_verify_option = "-t0";
                 | 
              
| 962 | 
                      } else {
                 | 
              
| 963 | 
                          disable_concurrent_verify_option = "";
                 | 
              
| 964 | 
                  }  | 
              
| 965 | 
                   | 
              
| 966 | 
                  #ifdef WIN32
                 | 
              
| 967 | 
                  filename = g_locale_from_utf8(par2filename, -1, NULL, NULL, NULL);  | 
              
| 968 | 
                   | 
              
| 969 | 
                      g_return_val_if_fail(filename != NULL, FALSE);
                 | 
              
| 970 | 
                   | 
              
| 971 | 
                  cmd = g_strdup_printf("\"%s\\par2.exe\" r %s \"%s\" \"%s"G_DIR_SEPARATOR_S"*.*\" 2>&1", bindir, disable_concurrent_verify_option, filename, dirname);  | 
              
| 972 | 
                  g_free(filename);  | 
              
| 973 | 
                  g_free(dirname);  | 
              
| 974 | 
                  g_free(bindir);  | 
              
| 975 | 
                  #else
                 | 
              
| 976 | 
                  cmd = g_strdup_printf("echo \"pid$$\"; par2 r %s \"%s\" \"%s"G_DIR_SEPARATOR_S"\"*.* 2>&1", disable_concurrent_verify_option, par2filename, directory);  | 
              
| 977 | 
                  #endif
                 | 
              
| 978 | 
                      fp = popen(cmd, "r");
                 | 
              
| 979 | 
                   | 
              
| 980 | 
                  #ifdef WIN32
                 | 
              
| 981 | 
                  cmd2 = g_locale_to_utf8(cmd, -1, NULL, NULL, NULL);  | 
              
| 982 | 
                      g_return_val_if_fail(cmd2 != NULL, FALSE);
                 | 
              
| 983 | 
                  ng_plugin_emit_log_msg(plugin_data, 1, _("Launched the command: %s\n"), cmd2);  | 
              
| 984 | 
                  g_free(cmd2);  | 
              
| 985 | 
                  #else
                 | 
              
| 986 | 
                  ng_plugin_emit_log_msg(plugin_data, 1, _("Launched the command: %s\n"), cmd);  | 
              
| 987 | 
                  #endif
                 | 
              
| 988 | 
                   | 
              
| 989 | 
                      if (!fp) {
                 | 
              
| 990 | 
                  ng_plugin_emit_log_msg(plugin_data, 1, _("Error while launching from the par2cmdline process\n%s\n%s"), cmd, strerror(errno));  | 
              
| 991 | 
                  g_free(cmd);  | 
              
| 992 | 
                  g_static_mutex_unlock(&mutex);  | 
              
| 993 | 
                          return FALSE;
                 | 
              
| 994 | 
                  }  | 
              
| 995 | 
                  g_free(cmd);  | 
              
| 996 | 
                   | 
              
| 997 | 
                  priv->collection_name = collection_name;  | 
              
| 998 | 
                  priv->par2filename = par2filename;  | 
              
| 999 | 
                      priv->par2files = NULL;
                 | 
              
| 1000 | 
                      priv->pid = -1;
                 | 
              
| 1001 | 
                   | 
              
| 1002 | 
                      // Assume the repair was succesfull unless it explicit failed
                 | 
              
| 1003 | 
                  priv->repair_succeeded = TRUE;  | 
              
| 1004 | 
                   | 
              
| 1005 | 
                      // Detect abnormal aborts of par2cmdline
                 | 
              
| 1006 | 
                  priv->command_completed_normally = FALSE;  | 
              
| 1007 | 
                   | 
              
| 1008 | 
                  memset(buf, 0, sizeof(buf));  | 
              
| 1009 | 
                      i = 0;
                 | 
              
| 1010 | 
                  while ((c = fgetc(fp)) != EOF) {  | 
              
| 1011 | 
                          if (priv->abort_flag) {
                 | 
              
| 1012 | 
                              break;
                 | 
              
| 1013 | 
                  }  | 
              
| 1014 | 
                   | 
              
| 1015 | 
                  if (c == '\r' || (c == '\n' && i > 0)) {  | 
              
| 1016 | 
                              if (first_line) {
                 | 
              
| 1017 | 
                  first_line = FALSE;  | 
              
| 1018 | 
                  #ifndef WIN32
                 | 
              
| 1019 | 
                  second_line = TRUE;  | 
              
| 1020 | 
                  } else if (second_line) {  | 
              
| 1021 | 
                  second_line = FALSE;  | 
              
| 1022 | 
                  #endif
                 | 
              
| 1023 | 
                                  /* Check if the given options were valid */
                 | 
              
| 1024 | 
                  if (!strncmp(buf, "Invalid option specified", 24)) {  | 
              
| 1025 | 
                                      if (invalid_option_detected) {
                 | 
              
| 1026 | 
                  *invalid_option_detected = TRUE;  | 
              
| 1027 | 
                  }  | 
              
| 1028 | 
                   | 
              
| 1029 | 
                  pclose(fp);  | 
              
| 1030 | 
                   | 
              
| 1031 | 
                                      priv->collection_name = NULL;
                 | 
              
| 1032 | 
                                      priv->par2filename = NULL;
                 | 
              
| 1033 | 
                   | 
              
| 1034 | 
                  g_static_mutex_unlock(&mutex);  | 
              
| 1035 | 
                   | 
              
| 1036 | 
                                      return FALSE;
                 | 
              
| 1037 | 
                  }  | 
              
| 1038 | 
                  }  | 
              
| 1039 | 
                   | 
              
| 1040 | 
                              /* Translate the line to UTF-8 if it isn't already */
                 | 
              
| 1041 | 
                  if (!g_utf8_validate(buf, -1, NULL)) {  | 
              
| 1042 | 
                                  /* Filename is probably in Windows-1252 charset */
                 | 
              
| 1043 | 
                  char *tmp = g_convert(buf, -1, "utf-8", "windows-1252", NULL, NULL, NULL);  | 
              
| 1044 | 
                                  if (tmp) {
                 | 
              
| 1045 | 
                  memset(buf, 0, sizeof(buf));  | 
              
| 1046 | 
                  strncpy(buf, tmp, sizeof(buf) - 1);  | 
              
| 1047 | 
                  g_free(tmp);  | 
              
| 1048 | 
                  }  | 
              
| 1049 | 
                  }  | 
              
| 1050 | 
                   | 
              
| 1051 | 
                              i = 0;
                 | 
              
| 1052 | 
                  active_file = process_message(plugin_data, files, active_file, buf, more_par2_sets_remaining_in_collection);  | 
              
| 1053 | 
                  // imported_funcs.emit_repair_message(buf);
                 | 
              
| 1054 | 
                  memset(buf, 0, sizeof(buf));  | 
              
| 1055 | 
                  } else if (c == '\n') {  | 
              
| 1056 | 
                              // Ignore
                 | 
              
| 1057 | 
                          } else {
                 | 
              
| 1058 | 
                              buf[i] = (char) c;
                 | 
              
| 1059 | 
                  i++;  | 
              
| 1060 | 
                  }  | 
              
| 1061 | 
                  }  | 
              
| 1062 | 
                   | 
              
| 1063 | 
                  pclose(fp);  | 
              
| 1064 | 
                   | 
              
| 1065 | 
                      if (!priv->command_completed_normally) {
                 | 
              
| 1066 | 
                          /* Abnormal abort of par2cmdline detected, send a notification to the frontend */
                 | 
              
| 1067 | 
                  const char *params[5];  | 
              
| 1068 | 
                   | 
              
| 1069 | 
                  priv->repair_succeeded = FALSE;  | 
              
| 1070 | 
                          params[0] = priv->collection_name;
                 | 
              
| 1071 | 
                          params[1] = priv->par2filename;
                 | 
              
| 1072 | 
                  params[2] = "Error: par2cmdline aborted unexpectedly";  | 
              
| 1073 | 
                  params[3] = "-1";  | 
              
| 1074 | 
                  params[4] = NULL;  | 
              
| 1075 | 
                          ng_plugin_emit_event(plugin_data, "par2_repair_failure", params);
                 | 
              
| 1076 | 
                  }  | 
              
| 1077 | 
                   | 
              
| 1078 | 
                      /* Remove the PAR2 files after a successfull verification/recovery if it's enabled */
                 | 
              
| 1079 | 
                  opts = plugin_data->core_funcs.config_get_opts();  | 
              
| 1080 | 
                  list = priv->par2files;  | 
              
| 1081 | 
                      while (list) {
                 | 
              
| 1082 | 
                          char *filename = list->data;
                 | 
              
| 1083 | 
                   | 
              
| 1084 | 
                          /* Make sure that the file really is a PAR/PAR2 file. Due to a bug in par2cmdline, regular files might get marked as PAR2 recovery files */
                 | 
              
| 1085 | 
                          if (priv->repair_succeeded && !more_par2_sets_remaining_in_collection && opts.auto_remove_files_after_repair) {
                 | 
              
| 1086 | 
                              if (verify_par2_extension(filename)) {
                 | 
              
| 1087 | 
                  char *path = g_build_filename(directory, filename, NULL);  | 
              
| 1088 | 
                                  ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_DEBUG, _("Now automatically removing file '%s'"), path);
                 | 
              
| 1089 | 
                  g_unlink(path);  | 
              
| 1090 | 
                  g_free(path);  | 
              
| 1091 | 
                              } else {
                 | 
              
| 1092 | 
                                  ng_plugin_emit_log_msg(plugin_data, NG_LOG_LEVEL_DEBUG, _("File '%s' was marked as a PAR2 recovery file by par2cmdline while it actually isn't. Ignoring"), filename);
                 | 
              
| 1093 | 
                  }  | 
              
| 1094 | 
                  }  | 
              
| 1095 | 
                   | 
              
| 1096 | 
                  g_free(filename);  | 
              
| 1097 | 
                   | 
              
| 1098 | 
                  list = g_list_next(list);  | 
              
| 1099 | 
                  }  | 
              
| 1100 | 
                  g_list_free(priv->par2files);  | 
              
| 1101 | 
                   | 
              
| 1102 | 
                      priv->collection_name = NULL;
                 | 
              
| 1103 | 
                      priv->par2filename = NULL;
                 | 
              
| 1104 | 
                   | 
              
| 1105 | 
                  g_static_mutex_unlock(&mutex);  | 
              
| 1106 | 
                   | 
              
| 1107 | 
                      return priv->repair_succeeded;
                 | 
              
| 1108 | 
                  }  | 
              
| 1109 | 
                   | 
              
| 1110 | 
                  static gboolean
                 | 
              
| 1111 | 
                  nntpgrab_plugin_par2_repair_files(NGPlugin *plugin_data, const char *collection_name, const char *directory, const char *par2filename, ngboolean more_par2_sets_remaining_in_collection)  | 
              
| 1112 | 
                  {
                 | 
              
| 1113 | 
                      char *path;
                 | 
              
| 1114 | 
                      GHashTable *files = NULL;
                 | 
              
| 1115 | 
                  GDir *dir;  | 
              
| 1116 | 
                      GError *error = NULL;
                 | 
              
| 1117 | 
                  const char *filename;  | 
              
| 1118 | 
                      char *filename_stripped;
                 | 
              
| 1119 | 
                  gboolean invalid_option_detected = FALSE;  | 
              
| 1120 | 
                  gboolean repair_succeeded;  | 
              
| 1121 | 
                   | 
              
| 1122 | 
                      path = g_build_filename(directory, par2filename, NULL);
                 | 
              
| 1123 | 
                  g_return_val_if_fail(g_file_test(path, G_FILE_TEST_EXISTS), FALSE);  | 
              
| 1124 | 
                   | 
              
| 1125 | 
                      // Build a list of all the files which are in the directory
                 | 
              
| 1126 | 
                      dir = g_dir_open(directory, 0, &error);
                 | 
              
| 1127 | 
                      if (!dir) {
                 | 
              
| 1128 | 
                  ng_plugin_emit_log_msg(plugin_data, 1, _("PAR2 repair can't be started because the directory with the\ndownloaded files can't be opened: %s"), error->message);  | 
              
| 1129 | 
                  g_error_free(error);  | 
              
| 1130 | 
                          return FALSE;
                 | 
              
| 1131 | 
                  }  | 
              
| 1132 | 
                   | 
              
| 1133 | 
                  files = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, par2_repair_file_free);  | 
              
| 1134 | 
                   | 
              
| 1135 | 
                      while ((filename = g_dir_read_name(dir))) {
                 | 
              
| 1136 | 
                  struct _par2_repair_file *file = g_slice_new0(struct _par2_repair_file);  | 
              
| 1137 | 
                   | 
              
| 1138 | 
                  strncpy(file->filename, filename, sizeof(file->filename) - 1);  | 
              
| 1139 | 
                  filename_stripped = strip_large_filenames(filename);  | 
              
| 1140 | 
                  g_hash_table_insert(files, filename_stripped, file);  | 
              
| 1141 | 
                          /* No need to g_free(filename_stripped) here as that will be done by the GHashTable itself */
                 | 
              
| 1142 | 
                  }  | 
              
| 1143 | 
                   | 
              
| 1144 | 
                  g_dir_close(dir);  | 
              
| 1145 | 
                   | 
              
| 1146 | 
                  ng_plugin_emit_log_msg(plugin_data, 1, "%s", _("Now starting PAR2 repair"));  | 
              
| 1147 | 
                   | 
              
| 1148 | 
                  repair_succeeded = par2_start_par2cmdline(plugin_data, collection_name, directory, path, files, TRUE, &invalid_option_detected, more_par2_sets_remaining_in_collection);  | 
              
| 1149 | 
                      if (!repair_succeeded && invalid_option_detected) {
                 | 
              
| 1150 | 
                          /* The regular par2cmdline was detected, try again with the -t0 option */
                 | 
              
| 1151 | 
                          repair_succeeded = par2_start_par2cmdline(plugin_data, collection_name, directory, path, files, FALSE, NULL, more_par2_sets_remaining_in_collection);
                 | 
              
| 1152 | 
                  }  | 
              
| 1153 | 
                   | 
              
| 1154 | 
                      if (repair_succeeded) {
                 | 
              
| 1155 | 
                  ng_plugin_emit_log_msg(plugin_data, 1, "%s", _("PAR2 repair finished successfully"));  | 
              
| 1156 | 
                      } else {
                 | 
              
| 1157 | 
                  ng_plugin_emit_log_msg(plugin_data, 1, "%s", _("PAR2 repair failed"));  | 
              
| 1158 | 
                  }  | 
              
| 1159 | 
                   | 
              
| 1160 | 
                  g_free(path);  | 
              
| 1161 | 
                  g_hash_table_destroy(files);  | 
              
| 1162 | 
                   | 
              
| 1163 | 
                      return repair_succeeded;
                 | 
              
| 1164 | 
                  }  | 
              
| 1165 | 
                   | 
              
| 1166 | 
                  typedef struct _par2_file_seen {  | 
              
| 1167 | 
                  char filename_without_extension[256];  | 
              
| 1168 | 
                  char filename[256];  | 
              
| 1169 | 
                  } PAR2FileSeen;  | 
              
| 1170 | 
                   | 
              
| 1171 | 
                  /* APARTE THREAD ! */
                 | 
              
| 1172 | 
                  struct _par2_thread_data {
                 | 
              
| 1173 | 
                  NGPlugin *plugin_data;  | 
              
| 1174 | 
                  char collection_name[256];  | 
              
| 1175 | 
                  };  | 
              
| 1176 | 
                   | 
              
| 1177 | 
                  static void  | 
              
| 1178 | 
                  par2_thread_func(gpointer data, gpointer user_data)  | 
              
| 1179 | 
                  {
                 | 
              
| 1180 | 
                  struct _par2_thread_data *thread_data = (struct _par2_thread_data *) data;  | 
              
| 1181 | 
                  NGConfigOpts opts;  | 
              
| 1182 | 
                      char *directory;
                 | 
              
| 1183 | 
                  GList *list;  | 
              
| 1184 | 
                  GList *list2;  | 
              
| 1185 | 
                      GList *files = NULL;
                 | 
              
| 1186 | 
                  GDir *dir;  | 
              
| 1187 | 
                      GError *err = NULL;
                 | 
              
| 1188 | 
                  const char *filename;  | 
              
| 1189 | 
                   | 
              
| 1190 | 
                      /* Do we need to repair? */
                 | 
              
| 1191 | 
                  opts = thread_data->plugin_data->core_funcs.config_get_opts();  | 
              
| 1192 | 
                      if (!opts.enable_par2_repair || thread_data->plugin_data->core_funcs.schedular_get_state() != SCHEDULAR_STATE_RUNNING || ((PluginPAR2Priv*)thread_data->plugin_data->priv)->abort_flag == TRUE) {
                 | 
              
| 1193 | 
                          g_slice_free(struct _par2_thread_data, thread_data);
                 | 
              
| 1194 | 
                          return;
                 | 
              
| 1195 | 
                  }  | 
              
| 1196 | 
                   | 
              
| 1197 | 
                      /* Find out all the unique PAR2 sets in this collection */
                 | 
              
| 1198 | 
                      directory = g_build_filename(opts.download_directory, thread_data->collection_name, NULL);
                 | 
              
| 1199 | 
                      dir = g_dir_open(directory, 0, &err);
                 | 
              
| 1200 | 
                      if (!dir) {
                 | 
              
| 1201 | 
                          ng_plugin_emit_log_msg(thread_data->plugin_data, NG_LOG_LEVEL_WARNING, _("Unable to open directory '%s': %s"), directory, err->message);
                 | 
              
| 1202 | 
                  g_error_free(err);  | 
              
| 1203 | 
                  g_free(directory);  | 
              
| 1204 | 
                          g_slice_free(struct _par2_thread_data, thread_data);
                 | 
              
| 1205 | 
                          return;
                 | 
              
| 1206 | 
                  }  | 
              
| 1207 | 
                   | 
              
| 1208 | 
                      while ((filename = g_dir_read_name(dir))) {
                 | 
              
| 1209 | 
                          char *extension;
                 | 
              
| 1210 | 
                  char filename_without_extension[256];  | 
              
| 1211 | 
                  gboolean filename_seen = FALSE;  | 
              
| 1212 | 
                          char *ptr;
                 | 
              
| 1213 | 
                   | 
              
| 1214 | 
                          extension = g_strrstr(filename, ".");
                 | 
              
| 1215 | 
                          if (!extension) {
                 | 
              
| 1216 | 
                              /* File without extension, ignore */
                 | 
              
| 1217 | 
                              continue;
                 | 
              
| 1218 | 
                  }  | 
              
| 1219 | 
                   | 
              
| 1220 | 
                  if (g_ascii_strncasecmp(extension, ".par", 4) != 0) {  | 
              
| 1221 | 
                              /* Different extension, ignore */
                 | 
              
| 1222 | 
                              continue;
                 | 
              
| 1223 | 
                  }  | 
              
| 1224 | 
                   | 
              
| 1225 | 
                  memset(filename_without_extension, 0, sizeof(filename_without_extension));  | 
              
| 1226 | 
                  strncpy(filename_without_extension, filename, extension - filename);  | 
              
| 1227 | 
                   | 
              
| 1228 | 
                          // Strip any .volXX+YY part
                 | 
              
| 1229 | 
                          ptr = g_strrstr(filename_without_extension, ".");
                 | 
              
| 1230 | 
                  if (ptr && !g_strncasecmp(ptr, ".vol", 4)) {  | 
              
| 1231 | 
                              *ptr = '\0';
                 | 
              
| 1232 | 
                  }  | 
              
| 1233 | 
                   | 
              
| 1234 | 
                  list2 = files;  | 
              
| 1235 | 
                          while (list2) {
                 | 
              
| 1236 | 
                  PAR2FileSeen *file = (PAR2FileSeen*) list2->data;  | 
              
| 1237 | 
                   | 
              
| 1238 | 
                              if (!strcmp(filename_without_extension, file->filename_without_extension)) {
                 | 
              
| 1239 | 
                                  // We've already seen this PAR2 set, ignore
                 | 
              
| 1240 | 
                  filename_seen = TRUE;  | 
              
| 1241 | 
                                  break;
                 | 
              
| 1242 | 
                  }  | 
              
| 1243 | 
                   | 
              
| 1244 | 
                  list2 = g_list_next(list2);  | 
              
| 1245 | 
                  }  | 
              
| 1246 | 
                   | 
              
| 1247 | 
                          if (!filename_seen) {
                 | 
              
| 1248 | 
                  PAR2FileSeen *file_seen = g_slice_new0(PAR2FileSeen);  | 
              
| 1249 | 
                   | 
              
| 1250 | 
                  strncpy(file_seen->filename_without_extension, filename_without_extension, sizeof(file_seen->filename_without_extension) - 1);  | 
              
| 1251 | 
                  strncpy(file_seen->filename, filename, sizeof(file_seen->filename) - 1);  | 
              
| 1252 | 
                   | 
              
| 1253 | 
                  files = g_list_append(files, file_seen);  | 
              
| 1254 | 
                  }  | 
              
| 1255 | 
                  }  | 
              
| 1256 | 
                   | 
              
| 1257 | 
                  g_dir_close(dir);  | 
              
| 1258 | 
                   | 
              
| 1259 | 
                      if (!files) {
                 | 
              
| 1260 | 
                          ng_plugin_emit_log_msg(thread_data->plugin_data, NG_LOG_LEVEL_DEBUG, _("No .par2 files found for collection '%s'"), thread_data->collection_name);
                 | 
              
| 1261 | 
                  }  | 
              
| 1262 | 
                   | 
              
| 1263 | 
                      /* Perform the repair */
                 | 
              
| 1264 | 
                  list = files;  | 
              
| 1265 | 
                      while (list) {
                 | 
              
| 1266 | 
                  PAR2FileSeen *file_seen = (PAR2FileSeen*) list->data;  | 
              
| 1267 | 
                   | 
              
| 1268 | 
                          nntpgrab_plugin_par2_repair_files(thread_data->plugin_data, thread_data->collection_name, directory, file_seen->filename, (list->next != NULL));
                 | 
              
| 1269 | 
                   | 
              
| 1270 | 
                  g_slice_free(PAR2FileSeen, file_seen);  | 
              
| 1271 | 
                  list = g_list_next(list);  | 
              
| 1272 | 
                  }  | 
              
| 1273 | 
                  g_list_free(files);  | 
              
| 1274 | 
                   | 
              
| 1275 | 
                  g_free(directory);  | 
              
| 1276 | 
                      g_slice_free(struct _par2_thread_data, thread_data);
                 | 
              
| 1277 | 
                  }  | 
              
| 1278 | 
                   | 
              
| 1279 | 
                  static void  | 
              
| 1280 | 
                  on_collection_downloaded(NGPlugin *plugin_data, const char *collection_name, gpointer data)  | 
              
| 1281 | 
                  {
                 | 
              
| 1282 | 
                  struct _par2_thread_data *thread_data = g_slice_new0(struct _par2_thread_data);  | 
              
| 1283 | 
                   | 
              
| 1284 | 
                  thread_data->plugin_data = plugin_data;  | 
              
| 1285 | 
                  strncpy(thread_data->collection_name, collection_name, sizeof(thread_data->collection_name) - 1);  | 
              
| 1286 | 
                   | 
              
| 1287 | 
                      g_thread_pool_push(((PluginPAR2Priv *) plugin_data->priv)->thread_pool, thread_data, NULL);
                 | 
              
| 1288 | 
                  }  | 
              
| 1289 | 
                   | 
              
NNTPGrab

