/* * This file is part of nzbget. See . * * Copyright (C) 2007-2019 Andrey Prygunkov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "nzbget.h" #include "CommandLineParser.h" #include "Log.h" #include "MessageBase.h" #include "DownloadInfo.h" #include "FileSystem.h" #include "Util.h" #ifdef HAVE_GETOPT_LONG static struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"configfile", required_argument, 0, 'c'}, {"noconfigfile", no_argument, 0, 'n'}, {"printconfig", no_argument, 0, 'p'}, {"server", no_argument, 0, 's' }, {"daemon", no_argument, 0, 'D' }, {"version", no_argument, 0, 'v'}, {"serverversion", no_argument, 0, 'V'}, {"option", required_argument, 0, 'o'}, {"append", no_argument, 0, 'A'}, {"list", no_argument, 0, 'L'}, {"pause", no_argument, 0, 'P'}, {"unpause", no_argument, 0, 'U'}, {"rate", required_argument, 0, 'R'}, {"system", no_argument, 0, 'B'}, {"log", required_argument, 0, 'G'}, {"top", no_argument, 0, 'T'}, {"edit", required_argument, 0, 'E'}, {"connect", no_argument, 0, 'C'}, {"quit", no_argument, 0, 'Q'}, {"reload", no_argument, 0, 'O'}, {"write", required_argument, 0, 'W'}, {"category", required_argument, 0, 'K'}, {"scan", no_argument, 0, 'S'}, {0, 0, 0, 0} }; #endif static char short_options[] = "c:hno:psvAB:DCE:G:K:LPR:STUQOVW:"; CommandLineParser::CommandLineParser(int argc, const char* argv[]) { InitCommandLine(argc, argv); if (argc == 1) { m_printUsage = true; return; } if (!m_printOptions && !m_printUsage && !m_printVersion) { InitFileArg(argc, argv); } } void CommandLineParser::InitCommandLine(int argc, const char* const_argv[]) { m_clientOperation = opClientNoOperation; // default std::vector argv; argv.reserve(argc); for (int i = 0; i < argc; i++) { argv.emplace_back(const_argv[i]); } // reset getopt optind = 0; while (true) { int c; #ifdef HAVE_GETOPT_LONG int option_index = 0; c = getopt_long(argc, (char**)argv.data(), short_options, long_options, &option_index); #else c = getopt(argc, (char**)argv.data(), short_options); #endif if (c == -1) break; switch (c) { case 'c': m_configFilename = optarg; break; case 'n': m_configFilename = nullptr; m_noConfig = true; break; case 'h': m_printUsage = true; return; case 'v': m_printVersion = true; return; case 'p': m_printOptions = true; break; case 'o': m_optionList.push_back(optarg); break; case 's': m_serverMode = true; break; case 'D': m_serverMode = true; m_daemonMode = true; break; case 'A': m_clientOperation = opClientRequestDownload; while (true) { optind++; optarg = optind > argc ? nullptr : (char*)argv[optind-1]; if (optarg && (!strcasecmp(optarg, "F") || !strcasecmp(optarg, "U"))) { // option ignored (but kept for compatibility) } else if (optarg && !strcasecmp(optarg, "T")) { m_addTop = true; } else if (optarg && !strcasecmp(optarg, "P")) { m_addPaused = true; } else if (optarg && !strcasecmp(optarg, "I")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } m_addPriority = atoi(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "C")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } m_addCategory = std::move(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "N")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } m_addNzbFilename = std::move(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "DK")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } m_addDupeKey = std::move(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "DS")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } m_addDupeScore = atoi(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "DM")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } const char* dupeMode = argv[optind-1]; if (!strcasecmp(dupeMode, "score")) { m_addDupeMode = dmScore; } else if (!strcasecmp(dupeMode, "all")) { m_addDupeMode = dmAll; } else if (!strcasecmp(dupeMode, "force")) { m_addDupeMode = dmForce; } else { ReportError("Could not parse value of option 'A'"); return; } } else { optind--; break; } } break; case 'L': optind++; optarg = optind > argc ? nullptr : (char*)argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_clientOperation = opClientRequestListFiles; optind--; } else if (!strcasecmp(optarg, "F") || !strcasecmp(optarg, "FR")) { m_clientOperation = opClientRequestListFiles; } else if (!strcasecmp(optarg, "G") || !strcasecmp(optarg, "GR")) { m_clientOperation = opClientRequestListGroups; } else if (!strcasecmp(optarg, "O")) { m_clientOperation = opClientRequestPostQueue; } else if (!strcasecmp(optarg, "S")) { m_clientOperation = opClientRequestListStatus; } else if (!strcasecmp(optarg, "H")) { m_clientOperation = opClientRequestHistory; } else if (!strcasecmp(optarg, "HA")) { m_clientOperation = opClientRequestHistoryAll; } else { ReportError("Could not parse value of option 'L'"); return; } if (optarg && (!strcasecmp(optarg, "FR") || !strcasecmp(optarg, "GR"))) { m_matchMode = mmRegEx; optind++; if (optind > argc) { ReportError("Could not parse value of option 'L'"); return; } m_editQueueText = std::move(argv[optind-1]); } break; case 'P': case 'U': optind++; optarg = optind > argc ? nullptr : (char*)argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_clientOperation = c == 'P' ? opClientRequestDownloadPause : opClientRequestDownloadUnpause; optind--; } else if (!strcasecmp(optarg, "D")) { m_clientOperation = c == 'P' ? opClientRequestDownloadPause : opClientRequestDownloadUnpause; } else if (!strcasecmp(optarg, "O")) { m_clientOperation = c == 'P' ? opClientRequestPostPause : opClientRequestPostUnpause; } else if (!strcasecmp(optarg, "S")) { m_clientOperation = c == 'P' ? opClientRequestScanPause : opClientRequestScanUnpause; } else { ReportError(c == 'P' ? "Could not parse value of option 'P'\n" : "Could not parse value of option 'U'"); return; } break; case 'R': m_clientOperation = opClientRequestSetRate; m_setRate = (int)(atof(optarg)*1024); break; case 'B': if (!strcasecmp(optarg, "dump")) { m_clientOperation = opClientRequestDumpDebug; } else if (!strcasecmp(optarg, "trace")) { m_testBacktrace = true; } else if (!strcasecmp(optarg, "webget")) { m_webGet = true; optind++; if (optind > argc) { ReportError("Could not parse value of option 'B'"); return; } optarg = argv[optind-1]; m_webGetFilename = optarg; } else if (!strcasecmp(optarg, "verify")) { m_sigVerify = true; optind++; if (optind > argc) { ReportError("Could not parse value of option 'B'"); return; } optarg = argv[optind-1]; m_pubKeyFilename = optarg; optind++; if (optind > argc) { ReportError("Could not parse value of option 'B'"); return; } optarg = argv[optind-1]; m_sigFilename = optarg; } else { ReportError("Could not parse value of option 'B'"); return; } break; case 'G': m_clientOperation = opClientRequestLog; m_logLines = atoi(optarg); if (m_logLines == 0) { ReportError("Could not parse value of option 'G'"); return; } break; case 'T': m_addTop = true; break; case 'C': m_remoteClientMode = true; break; case 'E': { m_clientOperation = opClientRequestEditQueue; bool group = !strcasecmp(optarg, "G") || !strcasecmp(optarg, "GN") || !strcasecmp(optarg, "GR"); bool file = !strcasecmp(optarg, "F") || !strcasecmp(optarg, "FN") || !strcasecmp(optarg, "FR"); if (!strcasecmp(optarg, "GN") || !strcasecmp(optarg, "FN")) { m_matchMode = mmName; } else if (!strcasecmp(optarg, "GR") || !strcasecmp(optarg, "FR")) { m_matchMode = mmRegEx; } else { m_matchMode = mmId; }; bool post = !strcasecmp(optarg, "O"); bool history = !strcasecmp(optarg, "H"); if (group || file || post || history) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } optarg = argv[optind-1]; } if (post) { // edit-commands for post-processor-queue if (!strcasecmp(optarg, "D")) { m_editQueueAction = DownloadQueue::eaPostDelete; } else { ReportError("Could not parse value of option 'E'"); return; } } else if (history) { // edit-commands for history if (!strcasecmp(optarg, "D")) { m_editQueueAction = DownloadQueue::eaHistoryDelete; } else if (!strcasecmp(optarg, "R")) { m_editQueueAction = DownloadQueue::eaHistoryReturn; } else if (!strcasecmp(optarg, "P")) { m_editQueueAction = DownloadQueue::eaHistoryProcess; } else if (!strcasecmp(optarg, "A")) { m_editQueueAction = DownloadQueue::eaHistoryRedownload; } else if (!strcasecmp(optarg, "F")) { m_editQueueAction = DownloadQueue::eaHistoryRetryFailed; } else if (!strcasecmp(optarg, "O")) { m_editQueueAction = DownloadQueue::eaHistorySetParameter; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_editQueueText = std::move(argv[optind-1]); if (!strchr(m_editQueueText, '=')) { ReportError("Could not parse value of option 'E'"); return; } } else if (!strcasecmp(optarg, "B")) { m_editQueueAction = DownloadQueue::eaHistoryMarkBad; } else if (!strcasecmp(optarg, "G")) { m_editQueueAction = DownloadQueue::eaHistoryMarkGood; } else if (!strcasecmp(optarg, "S")) { m_editQueueAction = DownloadQueue::eaHistoryMarkSuccess; } else { ReportError("Could not parse value of option 'E'"); return; } } else { // edit-commands for download-queue if (!strcasecmp(optarg, "T")) { m_editQueueAction = group ? DownloadQueue::eaGroupMoveTop : DownloadQueue::eaFileMoveTop; } else if (!strcasecmp(optarg, "B")) { m_editQueueAction = group ? DownloadQueue::eaGroupMoveBottom : DownloadQueue::eaFileMoveBottom; } else if (!strcasecmp(optarg, "P")) { m_editQueueAction = group ? DownloadQueue::eaGroupPause : DownloadQueue::eaFilePause; } else if (!strcasecmp(optarg, "A")) { m_editQueueAction = group ? DownloadQueue::eaGroupPauseAllPars : DownloadQueue::eaFilePauseAllPars; } else if (!strcasecmp(optarg, "R")) { m_editQueueAction = group ? DownloadQueue::eaGroupPauseExtraPars : DownloadQueue::eaFilePauseExtraPars; } else if (!strcasecmp(optarg, "U")) { m_editQueueAction = group ? DownloadQueue::eaGroupResume : DownloadQueue::eaFileResume; } else if (!strcasecmp(optarg, "D")) { m_editQueueAction = group ? DownloadQueue::eaGroupDelete : DownloadQueue::eaFileDelete; } else if (!strcasecmp(optarg, "DP")) { m_editQueueAction = DownloadQueue::eaGroupParkDelete; } else if (!strcasecmp(optarg, "SF")) { m_editQueueAction = DownloadQueue::eaGroupSortFiles; } else if (!strcasecmp(optarg, "C") || !strcasecmp(optarg, "K") || !strcasecmp(optarg, "CP")) { // switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions if (!group) { ReportError("Category can be set only for groups"); return; } m_editQueueAction = !strcasecmp(optarg, "CP") ? DownloadQueue::eaGroupApplyCategory : DownloadQueue::eaGroupSetCategory; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_editQueueText = std::move(argv[optind-1]); } else if (!strcasecmp(optarg, "N")) { if (!group) { ReportError("Only groups can be renamed"); return; } m_editQueueAction = DownloadQueue::eaGroupSetName; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_editQueueText = std::move(argv[optind-1]); } else if (!strcasecmp(optarg, "M")) { if (!group) { ReportError("Only groups can be merged"); return; } m_editQueueAction = DownloadQueue::eaGroupMerge; } else if (!strcasecmp(optarg, "S")) { m_editQueueAction = DownloadQueue::eaFileSplit; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_editQueueText = std::move(argv[optind-1]); } else if (!strcasecmp(optarg, "O")) { if (!group) { ReportError("Post-process parameter can be set only for groups"); return; } m_editQueueAction = DownloadQueue::eaGroupSetParameter; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_editQueueText = std::move(argv[optind-1]); if (!strchr(m_editQueueText, '=')) { ReportError("Could not parse value of option 'E'"); return; } } else if (!strcasecmp(optarg, "I")) { if (!group) { ReportError("Priority can be set only for groups"); return; } m_editQueueAction = DownloadQueue::eaGroupSetPriority; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_editQueueText = std::move(argv[optind-1]); if (atoi(m_editQueueText) == 0 && strcmp("0", m_editQueueText)) { ReportError("Could not parse value of option 'E'"); return; } } else { m_editQueueOffset = atoi(optarg); if (m_editQueueOffset == 0) { ReportError("Could not parse value of option 'E'"); return; } m_editQueueAction = group ? DownloadQueue::eaGroupMoveOffset : DownloadQueue::eaFileMoveOffset; } } break; } case 'Q': m_clientOperation = opClientRequestShutdown; break; case 'O': m_clientOperation = opClientRequestReload; break; case 'V': m_clientOperation = opClientRequestVersion; break; case 'W': m_clientOperation = opClientRequestWriteLog; if (!strcasecmp(optarg, "I")) { m_writeLogKind = (int)Message::mkInfo; } else if (!strcasecmp(optarg, "W")) { m_writeLogKind = (int)Message::mkWarning; } else if (!strcasecmp(optarg, "E")) { m_writeLogKind = (int)Message::mkError; } else if (!strcasecmp(optarg, "D")) { m_writeLogKind = (int)Message::mkDetail; } else if (!strcasecmp(optarg, "G")) { m_writeLogKind = (int)Message::mkDebug; } else { ReportError("Could not parse value of option 'W'"); return; } break; case 'K': // switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions m_addCategory = optarg; break; case 'S': optind++; optarg = optind > argc ? nullptr : (char*)argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_clientOperation = opClientRequestScanAsync; optind--; } else if (!strcasecmp(optarg, "W")) { m_clientOperation = opClientRequestScanSync; } else { ReportError("Could not parse value of option 'S'"); return; } break; case '?': m_errors = true; return; } } if (m_serverMode && m_clientOperation == opClientRequestDownloadPause) { m_pauseDownload = true; m_clientOperation = opClientNoOperation; } } void CommandLineParser::PrintUsage(const char* com) { printf("Usage:\n" " %s [switches]\n\n" "Switches:\n" " -h, --help Print this help-message\n" " -v, --version Print version and exit\n" " -c, --configfile Filename of configuration-file\n" " -n, --noconfigfile Prevent loading of configuration-file\n" " (required options must be passed with --option)\n" " -p, --printconfig Print configuration and exit\n" " -o, --option Set or override option in configuration-file\n" " -s, --server Start nzbget as a server in console-mode\n" #ifndef WIN32 " -D, --daemon Start nzbget as a server in daemon-mode\n" #endif " -V, --serverversion Print server's version and exit\n" " -Q, --quit Shutdown server\n" " -O, --reload Reload config and restart all services\n" " -A, --append [] Send file/url to server's\n" " download queue\n" " are (multiple options must be separated with space):\n" " T Add file to the top (beginning) of queue\n" " P Pause added files\n" " C Assign category to nzb-file\n" " N Use this name as nzb-filename\n" " I Set priority (signed integer)\n" " DK Set duplicate key (string)\n" " DS Set duplicate score (signed integer)\n" " DM (score|all|force) Set duplicate mode\n" " -C, --connect Attach client to server\n" " -L, --list [F|FR|G|GR|O|H|S] [RegEx] Request list of items from server\n" " F List individual files and server status (default)\n" " FR Like \"F\" but apply regular expression filter\n" " G List groups (nzb-files) and server status\n" " GR Like \"G\" but apply regular expression filter\n" " O List post-processor-queue\n" " H List history\n" " HA List history, all records (incl. hidden)\n" " S Print only server status\n" " Regular expression (only with options \"FR\", \"GR\")\n" " using POSIX Extended Regular Expression Syntax\n" " -P, --pause [D|O|S] Pause server\n" " D Pause download queue (default)\n" " O Pause post-processor queue\n" " S Pause scan of incoming nzb-directory\n" " -U, --unpause [D|O|S] Unpause server\n" " D Unpause download queue (default)\n" " O Unpause post-processor queue\n" " S Unpause scan of incoming nzb-directory\n" " -R, --rate Set download rate on server, in KB/s\n" " -G, --log Request last lines from server's screen-log\n" " -W, --write \"Text\" Send text to server's log\n" " -S, --scan [W] Scan incoming nzb-directory on server\n" " W Wait until scan completes (synchronous mode)\n" " -E, --edit [F|FN|FR|G|GN|GR|O|H] Edit items\n" " on server\n" " F Edit individual files (default)\n" " FN Like \"F\" but uses names (as \"group/file\")\n" " instead of IDs\n" " FR Like \"FN\" but with regular expressions\n" " G Edit all files in the group (same nzb-file)\n" " GN Like \"G\" but uses group names instead of IDs\n" " GR Like \"GN\" but with regular expressions\n" " O Edit post-processor-queue\n" " H Edit history\n" " is one of:\n" " - for files (F) and groups (G):\n" " <+offset|-offset> Move in queue relative to current position,\n" " offset is an integer value\n" " T Move to top of queue\n" " B Move to bottom of queue\n" " D Delete\n" " P Pause\n" " U Resume (unpause)\n" " - for groups (G):\n" " A Pause all pars\n" " R Pause extra pars\n" " DP Delete but keep downloaded files\n" " I Set priority (signed integer)\n" " C Set category\n" " CP Set category and apply post-process parameters\n" " N Rename\n" " M Merge\n" " SF Sort inner files for optimal order\n" " S Split - create new group from selected files\n" " O = Set post-process parameter\n" " - for post-jobs (O):\n" " D Delete (cancel post-processing)\n" " - for history (H):\n" " D Delete\n" " P Post-process again\n" " R Download remaining files\n" " A Download again\n" " F Retry download of failed articles\n" " O = Set post-process parameter\n" " B Mark as bad\n" " G Mark as good\n" " S Mark as success\n" " Comma-separated list of file- or group- ids or\n" " ranges of file-ids, e. g.: 1-5,3,10-22\n" " List of names (with options \"FN\" and \"GN\"),\n" " e. g.: \"my nzb download%cmyfile.nfo\" \"another nzb\"\n" " List of regular expressions (options \"FR\", \"GR\")\n" " using POSIX Extended Regular Expression Syntax\n", FileSystem::BaseFileName(com), PATH_SEPARATOR); } void CommandLineParser::InitFileArg(int argc, const char* argv[]) { if (optind >= argc) { // no nzb-file passed if (!m_serverMode && !m_remoteClientMode && (m_clientOperation == opClientNoOperation || m_clientOperation == opClientRequestDownload || m_clientOperation == opClientRequestWriteLog)) { if (m_clientOperation == opClientRequestWriteLog) { ReportError("Log-text not specified"); } else { ReportError("Nzb-file or Url not specified"); } } } else if (m_clientOperation == opClientRequestEditQueue) { if (m_matchMode == mmId) { ParseFileIdList(argc, argv, optind); } else { ParseFileNameList(argc, argv, optind); } } else { m_lastArg = argv[optind]; // Check if the file-name is a relative path or an absolute path // If the path starts with '/' its an absolute, else relative const char* fileName = argv[optind]; #ifdef WIN32 m_argFilename = fileName; #else if (fileName[0] == '/' || !strncasecmp(fileName, "http://", 6) || !strncasecmp(fileName, "https://", 7)) { m_argFilename = fileName; } else { // TEST m_argFilename.Reserve(1024 - 1); getcwd(m_argFilename, 1024); m_argFilename.AppendFmt("/%s", fileName); } #endif if (m_serverMode || m_remoteClientMode || !(m_clientOperation == opClientNoOperation || m_clientOperation == opClientRequestDownload || m_clientOperation == opClientRequestWriteLog)) { ReportError("Too many arguments"); } } } void CommandLineParser::ParseFileIdList(int argc, const char* argv[], int optind) { m_editQueueIdList.clear(); while (optind < argc) { CString writableFileIdList = argv[optind++]; char* optarg = strtok(writableFileIdList, ", "); while (optarg) { int editQueueIdFrom = 0; int editQueueIdTo = 0; const char* p = strchr(optarg, '-'); if (p) { BString<100> buf; buf.Set(optarg, (int)(p - optarg)); editQueueIdFrom = atoi(buf); editQueueIdTo = atoi(p + 1); if (editQueueIdFrom <= 0 || editQueueIdTo <= 0) { ReportError("invalid list of file IDs"); return; } } else { editQueueIdFrom = atoi(optarg); if (editQueueIdFrom <= 0) { ReportError("invalid list of file IDs"); return; } editQueueIdTo = editQueueIdFrom; } int editQueueIdCount = 0; if (editQueueIdFrom < editQueueIdTo) { editQueueIdCount = editQueueIdTo - editQueueIdFrom + 1; } else { editQueueIdCount = editQueueIdFrom - editQueueIdTo + 1; } for (int i = 0; i < editQueueIdCount; i++) { if (editQueueIdFrom < editQueueIdTo) { m_editQueueIdList.push_back(editQueueIdFrom + i); } else { m_editQueueIdList.push_back(editQueueIdFrom - i); } } optarg = strtok(nullptr, ", "); } } } void CommandLineParser::ParseFileNameList(int argc, const char* argv[], int optind) { while (optind < argc) { m_editQueueNameList.push_back(argv[optind++]); } } void CommandLineParser::ReportError(const char* errMessage) { m_errors = true; printf("%s\n", errMessage); }