ModInfo class after tests
This commit is contained in:
parent
2398d783fc
commit
00219b8424
31
3m.h
31
3m.h
@ -1,6 +1,7 @@
|
|||||||
#ifndef _3M_H
|
#ifndef _3M_H
|
||||||
#define _3M_H
|
#define _3M_H
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
#include "ConfigFile.h"
|
#include "ConfigFile.h"
|
||||||
#include "3mExceptions.h"
|
#include "3mExceptions.h"
|
||||||
#include "LocalModDescription.h"
|
#include "LocalModDescription.h"
|
||||||
@ -21,11 +22,11 @@
|
|||||||
/// \namespace mmm
|
/// \namespace mmm
|
||||||
/// \brief A global namespace for 3m.
|
/// \brief A global namespace for 3m.
|
||||||
namespace mmm {
|
namespace mmm {
|
||||||
/// \fn std::string strip_endl(std::string s)
|
/// \fn inline std::string strip_endl(std::string s)
|
||||||
/// \brief A function that strips endline signs from the string.
|
/// \brief A function that strips endline signs from the string.
|
||||||
/// \param s A string to strip endline signs from.
|
/// \param s A string to strip endline signs from.
|
||||||
/// \return Stripped string.
|
/// \return Stripped string.
|
||||||
std::string strip_endl(std::string s) {
|
inline std::string strip_endl(std::string s) {
|
||||||
int len = s.length();
|
int len = s.length();
|
||||||
for(int i = 0; i < len; i++) {
|
for(int i = 0; i < len; i++) {
|
||||||
if(s[i] == '\n') {
|
if(s[i] == '\n') {
|
||||||
@ -37,17 +38,25 @@ std::string strip_endl(std::string s) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \fn std::string strgetline(std::string *str)
|
/// \fn inline std::vector<std::string> strtovec(std::string *str)
|
||||||
/// \brief A function that gets line from the multiline string and erases it from this string.
|
/// \brief A function that converts a multiline string to a vector of strings.
|
||||||
/// \param[in,out] str A pointer to the string to get line from. The line is erased from the string.
|
/// \param str A string to convert to vector.
|
||||||
/// \return Line from the string.
|
/// \return Vector of strings, each one a line from original string
|
||||||
std::string strgetline(std::string *str) {
|
inline std::vector<std::string>& strtovec(std::string str) {
|
||||||
std::string line = "";
|
std::string line = "";
|
||||||
int i;
|
static std::vector<std::string> vec;
|
||||||
for(i = 0; (*str)[i] != '\n'; i++) {
|
vec.clear();
|
||||||
line += (*str)[i];
|
for(unsigned int i = 0; i < str.length(); i++) {
|
||||||
|
if(str[i] == '\n' || str[i] == '\r') {
|
||||||
|
if(line != "") {
|
||||||
|
vec.push_back(line);
|
||||||
|
line = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
line += str[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return line;
|
return vec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -47,9 +47,9 @@ class BadResponseException: public std::exception {
|
|||||||
private:
|
private:
|
||||||
std::string _response;
|
std::string _response;
|
||||||
public:
|
public:
|
||||||
OutOfBoundsException(std::string response); ///< \brief A constructor with parameters.
|
BadResponseException(std::string response); ///< \brief A constructor with parameters.
|
||||||
///< \param response HTTP response that caused the exception.
|
///< \param response HTTP response that caused the exception.
|
||||||
~OutOfBoundsException() throw(); ///< A destructor, as needed by std::exception.
|
~BadResponseException() throw(); ///< A destructor, as needed by std::exception.
|
||||||
const char* what() const throw(); ///< \brief A function returning error message.
|
const char* what() const throw(); ///< \brief A function returning error message.
|
||||||
/// \return Error message.
|
/// \return Error message.
|
||||||
};
|
};
|
||||||
|
16
ModInfo.cpp
16
ModInfo.cpp
@ -1,6 +1,9 @@
|
|||||||
#include "ModInfo.h"
|
#include "ModInfo.h"
|
||||||
#include "NetSocket++/NetSocketPP.h"
|
#include "NetSocket++/NetSocketPP.h"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <vector>
|
||||||
using namespace mmm;
|
using namespace mmm;
|
||||||
|
|
||||||
ModInfo::ModInfo() {
|
ModInfo::ModInfo() {
|
||||||
@ -27,11 +30,12 @@ ModInfo::ModInfo(ModInfoDescription mid) {
|
|||||||
if(data.getResponse() != "200 OK") {
|
if(data.getResponse() != "200 OK") {
|
||||||
throw BadResponseException(data.getResponse());
|
throw BadResponseException(data.getResponse());
|
||||||
}
|
}
|
||||||
std::string modinfo = data.getContent();
|
std::string smodinfo = data.getContent();
|
||||||
|
std::vector<std::string> modinfo = strtovec(smodinfo);
|
||||||
std::string action = "detect";
|
std::string action = "detect";
|
||||||
for(unsigned int i = 0; i < modinfo.length(); i++) {
|
for(unsigned int i = 0; i < modinfo.size(); i++) {
|
||||||
std::string line = "";
|
std::string line = "";
|
||||||
line = strgetline(&modinfo);
|
line = modinfo[i];
|
||||||
if(line[0] != NULL && line[0] != ' ' && line[0] != '\n' && line[0] != '\r') {
|
if(line[0] != NULL && line[0] != ' ' && line[0] != '\n' && line[0] != '\r') {
|
||||||
if(action == "detect") {
|
if(action == "detect") {
|
||||||
if(line[0] == '{') {
|
if(line[0] == '{') {
|
||||||
@ -40,6 +44,7 @@ for(unsigned int i = 0; i < modinfo.length(); i++) {
|
|||||||
name += line[i];
|
name += line[i];
|
||||||
}
|
}
|
||||||
_name = name;
|
_name = name;
|
||||||
|
_desc.setName(name);
|
||||||
action = "parse";
|
action = "parse";
|
||||||
} else {
|
} else {
|
||||||
std::string msg = "";
|
std::string msg = "";
|
||||||
@ -182,6 +187,7 @@ ModInfo::ModInfo(std::string path) {
|
|||||||
name += line[i];
|
name += line[i];
|
||||||
}
|
}
|
||||||
_name = name;
|
_name = name;
|
||||||
|
_desc.setName(name);
|
||||||
action = "parse";
|
action = "parse";
|
||||||
} else {
|
} else {
|
||||||
std::string msg = "";
|
std::string msg = "";
|
||||||
@ -307,7 +313,7 @@ void ModInfo::write() {
|
|||||||
throw NonEditableException("Tried to write back remotely obtained modinfo file!");
|
throw NonEditableException("Tried to write back remotely obtained modinfo file!");
|
||||||
}
|
}
|
||||||
std::ofstream modinfo(_localPath.c_str());
|
std::ofstream modinfo(_localPath.c_str());
|
||||||
if(!ofstream) {
|
if(!modinfo) {
|
||||||
throw FileException(_localPath, "writing", "Could not open file!");
|
throw FileException(_localPath, "writing", "Could not open file!");
|
||||||
}
|
}
|
||||||
modinfo << "{" << _name << "}" << std::endl << "[description]" << std::endl << _description << std::endl << "[release]" << std::endl << _release << std::endl << "[deps]" << std::endl;
|
modinfo << "{" << _name << "}" << std::endl << "[description]" << std::endl << _description << std::endl << "[release]" << std::endl << _release << std::endl << "[deps]" << std::endl;
|
||||||
@ -326,7 +332,7 @@ void ModInfo::releaseInc() {
|
|||||||
_release++;
|
_release++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModInfo::~ModInfo() {
|
ModInfo::~ModInfo() {
|
||||||
ModInfoDescription emptymid;
|
ModInfoDescription emptymid;
|
||||||
_desc = emptymid;
|
_desc = emptymid;
|
||||||
_edit = false;
|
_edit = false;
|
||||||
|
@ -20,7 +20,7 @@ bool _edit;
|
|||||||
std::string _localPath;
|
std::string _localPath;
|
||||||
public:
|
public:
|
||||||
ModInfo(); ///< A constructor.
|
ModInfo(); ///< A constructor.
|
||||||
ModInfo(ModInfoDescripton mid); ///< \brief A constructor from ModInfoDescription object.
|
ModInfo(ModInfoDescription mid); ///< \brief A constructor from ModInfoDescription object.
|
||||||
///< Tries to download and parse remote modinfo file.
|
///< Tries to download and parse remote modinfo file.
|
||||||
///< \param mid A ModInfoDescription object.
|
///< \param mid A ModInfoDescription object.
|
||||||
ModInfo(std::string path); ///< \brief A construtor from the path to local modinfo file.
|
ModInfo(std::string path); ///< \brief A construtor from the path to local modinfo file.
|
||||||
|
@ -6,6 +6,7 @@ all:
|
|||||||
${CXX} ${CXXFLAGS} -o ModListListTest ModListListTest.cpp ../ModListList.cpp ../ModListDescription.cpp ../3mExceptions.cpp
|
${CXX} ${CXXFLAGS} -o ModListListTest ModListListTest.cpp ../ModListList.cpp ../ModListDescription.cpp ../3mExceptions.cpp
|
||||||
${CXX} ${CXXFLAGS} -o LocalModListTest LocalModListTest.cpp ../LocalModList.cpp ../LocalModDescription.cpp ../ModDescription.cpp ../3mExceptions.cpp ../ConfigFile.cpp
|
${CXX} ${CXXFLAGS} -o LocalModListTest LocalModListTest.cpp ../LocalModList.cpp ../LocalModDescription.cpp ../ModDescription.cpp ../3mExceptions.cpp ../ConfigFile.cpp
|
||||||
${CXX} ${CXXFLAGS} -o RepositoryInfoTest RepositoryInfoTest.cpp ../RepositoryModDescription.cpp ../ModDescription.cpp ../3mExceptions.cpp ../ConfigFile.cpp ../RepositoryInfo.cpp
|
${CXX} ${CXXFLAGS} -o RepositoryInfoTest RepositoryInfoTest.cpp ../RepositoryModDescription.cpp ../ModDescription.cpp ../3mExceptions.cpp ../ConfigFile.cpp ../RepositoryInfo.cpp
|
||||||
|
${CXX} ${CXXFLAGS} -o ModInfoTest ModInfoTest.cpp ../ModInfo.cpp ../ModInfoDescription.cpp ../ModDescription.cpp ../3mExceptions.cpp -lnetsocketpp
|
||||||
debug:
|
debug:
|
||||||
${CXX} ${CXXFLAGS} -o ConfigFileTest ConfigFileTest.cpp ../ConfigFile.cpp ../3mExceptions.cpp -g
|
${CXX} ${CXXFLAGS} -o ConfigFileTest ConfigFileTest.cpp ../ConfigFile.cpp ../3mExceptions.cpp -g
|
||||||
${CXX} ${CXXFLAGS} -o ModDescriptionTest ModDescriptionTest.cpp ../ModDescription.cpp -g
|
${CXX} ${CXXFLAGS} -o ModDescriptionTest ModDescriptionTest.cpp ../ModDescription.cpp -g
|
||||||
@ -13,5 +14,6 @@ debug:
|
|||||||
${CXX} ${CXXFLAGS} -o ModListListTest ModListListTest.cpp ../ModListList.cpp ../ModListDescription.cpp ../3mExceptions.cpp -g
|
${CXX} ${CXXFLAGS} -o ModListListTest ModListListTest.cpp ../ModListList.cpp ../ModListDescription.cpp ../3mExceptions.cpp -g
|
||||||
${CXX} ${CXXFLAGS} -o LocalModListTest LocalModListTest.cpp ../LocalModList.cpp ../LocalModDescription.cpp ../ModDescription.cpp ../3mExceptions.cpp ../ConfigFile.cpp -g
|
${CXX} ${CXXFLAGS} -o LocalModListTest LocalModListTest.cpp ../LocalModList.cpp ../LocalModDescription.cpp ../ModDescription.cpp ../3mExceptions.cpp ../ConfigFile.cpp -g
|
||||||
${CXX} ${CXXFLAGS} -o RepositoryInfoTest RepositoryInfoTest.cpp ../RepositoryModDescription.cpp ../ModDescription.cpp ../3mExceptions.cpp ../ConfigFile.cpp ../RepositoryInfo.cpp -g
|
${CXX} ${CXXFLAGS} -o RepositoryInfoTest RepositoryInfoTest.cpp ../RepositoryModDescription.cpp ../ModDescription.cpp ../3mExceptions.cpp ../ConfigFile.cpp ../RepositoryInfo.cpp -g
|
||||||
|
${CXX} ${CXXFLAGS} -o ModInfoTest ModInfoTest.cpp ../ModInfo.cpp ../ModInfoDescription.cpp ../ModDescription.cpp ../3mExceptions.cpp -lnetsocketpp -g
|
||||||
clean:
|
clean:
|
||||||
rm -rf ConfigFileTest ModDescriptionTest LocalModDescriptionTest ModListListTest LocalModListTest RepositoryInfoTest
|
rm -rf ConfigFileTest ModDescriptionTest LocalModDescriptionTest ModListListTest LocalModListTest RepositoryInfoTest ModInfoTest
|
||||||
|
204
tests/ModInfoTest.cpp
Normal file
204
tests/ModInfoTest.cpp
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
#include "../ModInfo.h"
|
||||||
|
#include "NetSocket++/NetSocketPP.h"
|
||||||
|
#include "../ModDescription.h"
|
||||||
|
#include "../ModInfoDescription.h"
|
||||||
|
#include "../3mExceptions.h"
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdlib>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
char mode;
|
||||||
|
cout << "Select mode: Download and parse (r)emote modinfo file, Open, parse and edit (l)ocal modinfo file, Create (n)ew modinfo file: ";
|
||||||
|
cin >> mode;
|
||||||
|
if(mode == 'r') {
|
||||||
|
string server;
|
||||||
|
string path;
|
||||||
|
cout << "Enter server where modinfo is located: ";
|
||||||
|
cin >> server;
|
||||||
|
cout << "Enter path of the remote modinfo: ";
|
||||||
|
cin >> path;
|
||||||
|
mmm::ModInfoDescription midesc("", server, path);
|
||||||
|
try {
|
||||||
|
mmm::ModInfo mi(midesc);
|
||||||
|
cout << "Got modinfo: " << mi.getName() << "!" << endl << "description: " << mi.getDescription() << endl << "release: " << mi.getReleaseNr() << endl << "dependencies:" << endl;
|
||||||
|
mi.resetDependencyIterator();
|
||||||
|
while(!mi.dependenciesEnd()) {
|
||||||
|
std::string dep = mi.getNextDependency();
|
||||||
|
if(dep != "") {
|
||||||
|
cout << dep << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cout << "repository type: " << mi.getRepositoryType() << endl << "repository address: " << mi.getRepositoryAddress() << endl;
|
||||||
|
cout << "All OK!" << endl;
|
||||||
|
} catch (NetSocketPP::NetworkException &exc) {
|
||||||
|
cerr << "NetSocket++ throwed NetworkException: " << exc.what() << "! Exiting..." << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
} catch (NetSocketPP::SocketException &exc) {
|
||||||
|
cerr << "NetSocket++ throwed SocketException: " << exc.what() << "! Exiting..." << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
} catch (mmm::ParseException &exc) {
|
||||||
|
cerr << "ParseException occured: " << exc.what() << ". That means your remote modinfo file is not formatted properly! Exiting..." << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
} catch (mmm::BadResponseException &exc) {
|
||||||
|
cerr << "BadResponseException occured: " << exc.what() << ". That usually means you have given wrong modinfo server or path. Exiting..." << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
} else if(mode == 'l') {
|
||||||
|
std::string path;
|
||||||
|
cout << "Path to local modinfo file: ";
|
||||||
|
cin >> path;
|
||||||
|
try {
|
||||||
|
mmm::ModInfo mi(path);
|
||||||
|
char action;
|
||||||
|
do {
|
||||||
|
cout << "Select action: (l)ist modinfo, (i)ncrease modinfo release, (e)dit all modinfo fields, (s)ave and exit, (q)uit without saving: ";
|
||||||
|
cin >> action;
|
||||||
|
if(action == 'l') {
|
||||||
|
cout << "name: " << mi.getName() << endl << "description: " << mi.getDescription() << endl << "release: " << mi.getReleaseNr() << endl << "dependencies:" << endl;
|
||||||
|
mi.resetDependencyIterator();
|
||||||
|
while(!mi.dependenciesEnd()) {
|
||||||
|
std::string dep = mi.getNextDependency();
|
||||||
|
if(dep != "") {
|
||||||
|
cout << dep << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cout << "repository type: " << mi.getRepositoryType() << endl << "repository address: " << mi.getRepositoryAddress() << endl;
|
||||||
|
} else if(action == 'i') {
|
||||||
|
mi.releaseInc();
|
||||||
|
} else if(action == 'e') {
|
||||||
|
std::string name, desc, dep, repotype, repoaddr;
|
||||||
|
int release;
|
||||||
|
cout << "name: ";
|
||||||
|
cin >> name;
|
||||||
|
if(name != "") {
|
||||||
|
mi.setName(name);
|
||||||
|
}
|
||||||
|
cout << "description: ";
|
||||||
|
cin >> desc;
|
||||||
|
if(desc != "") {
|
||||||
|
mi.setDescription(desc);
|
||||||
|
}
|
||||||
|
cout << "release: ";
|
||||||
|
cin >> release;
|
||||||
|
if(release > 0) {
|
||||||
|
mi.setReleaseNr(release);
|
||||||
|
}
|
||||||
|
cout << "dependencies ('end' to finish): ";
|
||||||
|
do {
|
||||||
|
cin >> dep;
|
||||||
|
if(dep != "" && dep != "end") {
|
||||||
|
mi.insertDependency(dep);
|
||||||
|
}
|
||||||
|
} while(dep != "end");
|
||||||
|
cout << "repository type: ";
|
||||||
|
cin >> repotype;
|
||||||
|
if(repotype != "") {
|
||||||
|
mi.setRepositoryType(repotype);
|
||||||
|
}
|
||||||
|
cout << "repository address: ";
|
||||||
|
cin >> repoaddr;
|
||||||
|
if(repoaddr != "") {
|
||||||
|
mi.setRepositoryAddress(repoaddr);
|
||||||
|
}
|
||||||
|
} else if(action != 's' && action != 'q') {
|
||||||
|
cout << "Unknown action!" << endl;
|
||||||
|
}
|
||||||
|
} while(action != 's' && action != 'q');
|
||||||
|
if(action == 's') {
|
||||||
|
mi.write();
|
||||||
|
}
|
||||||
|
cout << "All OK!" << endl;
|
||||||
|
} catch(mmm::FileException &exc) {
|
||||||
|
cerr << "FileException occured: " << exc.what() << "! Exiting..." << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
} catch(mmm::ParseException &exc) {
|
||||||
|
cerr << "ParseException occured: " << exc.what() << ". That means your remote modinfo file is not formatted properly! Exiting..." << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
} catch(mmm::NonEditableException &exc) {
|
||||||
|
cerr << "NonEditableException occured: " << exc.what() << "! Exiting..." << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
} else if(mode == 'n') {
|
||||||
|
std::string path;
|
||||||
|
cout << "Path to new modinfo file: ";
|
||||||
|
cin >> path;
|
||||||
|
try {
|
||||||
|
mmm::ModInfo mi;
|
||||||
|
mi.setPath(path);
|
||||||
|
char action;
|
||||||
|
do {
|
||||||
|
cout << "Select action: (l)ist modinfo, (i)ncrease modinfo release, (e)dit all modinfo fields, (s)ave and exit, (q)uit without saving: ";
|
||||||
|
cin >> action;
|
||||||
|
if(action == 'l') {
|
||||||
|
cout << "name: " << mi.getName() << endl << "description: " << mi.getDescription() << endl << "release: " << mi.getReleaseNr() << endl << "dependencies:" << endl;
|
||||||
|
mi.resetDependencyIterator();
|
||||||
|
while(!mi.dependenciesEnd()) {
|
||||||
|
std::string dep = mi.getNextDependency();
|
||||||
|
if(dep != "") {
|
||||||
|
cout << dep << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cout << "repository type: " << mi.getRepositoryType() << endl << "repository address: " << mi.getRepositoryAddress() << endl;
|
||||||
|
} else if(action == 'i') {
|
||||||
|
mi.releaseInc();
|
||||||
|
} else if(action == 'e') {
|
||||||
|
std::string name, desc, dep, repotype, repoaddr;
|
||||||
|
int release;
|
||||||
|
cout << "name: ";
|
||||||
|
cin >> name;
|
||||||
|
if(name != "") {
|
||||||
|
mi.setName(name);
|
||||||
|
}
|
||||||
|
cout << "description: ";
|
||||||
|
cin >> desc;
|
||||||
|
if(desc != "") {
|
||||||
|
mi.setDescription(desc);
|
||||||
|
}
|
||||||
|
cout << "release: ";
|
||||||
|
cin >> release;
|
||||||
|
if(release > 0) {
|
||||||
|
mi.setReleaseNr(release);
|
||||||
|
}
|
||||||
|
cout << "dependencies ('end' to finish): ";
|
||||||
|
do {
|
||||||
|
cin >> dep;
|
||||||
|
if(dep != "" && dep != "end") {
|
||||||
|
mi.insertDependency(dep);
|
||||||
|
}
|
||||||
|
} while(dep != "end");
|
||||||
|
cout << "repository type: ";
|
||||||
|
cin >> repotype;
|
||||||
|
if(repotype != "") {
|
||||||
|
mi.setRepositoryType(repotype);
|
||||||
|
}
|
||||||
|
cout << "repository address: ";
|
||||||
|
cin >> repoaddr;
|
||||||
|
if(repoaddr != "") {
|
||||||
|
mi.setRepositoryAddress(repoaddr);
|
||||||
|
}
|
||||||
|
} else if(action != 's' && action != 'q') {
|
||||||
|
cout << "Unknown action!" << endl;
|
||||||
|
}
|
||||||
|
} while(action != 's' && action != 'q');
|
||||||
|
if(action == 's') {
|
||||||
|
mi.write();
|
||||||
|
}
|
||||||
|
cout << "All OK!" << endl;
|
||||||
|
} catch(mmm::FileException &exc) {
|
||||||
|
cerr << "FileException occured: " << exc.what() << "! Exiting..." << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
} catch(mmm::ParseException &exc) {
|
||||||
|
cerr << "ParseException occured: " << exc.what() << ". That means your remote modinfo file is not formatted properly! Exiting..." << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
} catch(mmm::NonEditableException &exc) {
|
||||||
|
cerr << "NonEditableException occured: " << exc.what() << "! Exiting..." << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cerr << "Unknown mode: " << mode << "! Exiting...";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user