/* * Copyright (c) Alex Allan * All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** * main.cpp * Entry point for CLI taglookup utility. */ #define TAGLOOKUP_VERSION 0.2 #define TAGLOOKUP_NAME "TagLookup" #define TAGLOOKUP_VERSION_DATE 20070929 #define TAGLOOKUP_COPYRIGHT "(c) Alex Allan 2007 alex\100kamaz.org.uk" #define TAGLOOKUP_WEBSITE "http://www.kamaz.org.uk/taglookup" #define TAGLOOKUP_CDDBP_SERVER "freedb.org" #define TAGLOOKUP_CDDBP_PORT 888 #define TAGLOOKUP_HTTP_SERVER "freedb.org" #define TAGLOOKUP_HTTP_PORT 80 #define TAGLOOKUP_HTTP_PROXY_ENV_NAME "http_proxy" #define TAGLOOKUP_HTTP_PROXY_PORT 8080 #include #include #include #include #include #include #include #include #include #include "cddb_query.h" #include "tag.h" #include "command_line.h" using namespace TagLookup; struct PrintProblemFiles : public std::unary_function { std::ostream &os_; PrintProblemFiles(std::ostream &os) : os_(os) { } void operator() (const ProblemFile &pf) { os_ << pf.path << ": " << FileDisc::ProblemTypeToString(pf.type) << std::endl; } }; // PrintProblemFile struct PrintTrack : public std::unary_function { std::ostream &os_; unsigned int s; PrintTrack(std::ostream &os, unsigned int spaces) : os_(os), s(spaces) { } void operator() (const TagLookup::Track &track) { for(unsigned int i = 0; i < s; ++i) os_ << ' '; const unsigned int n = track.getTrackNo(); const unsigned int length = track.getLength(); const unsigned int mins = length / 60; const unsigned int secs = length % 60; os_ << (n < 10 ? " " : "") << n << ". " << track.getName() << " (" << mins << ':' << (secs < 10 ? "0" : "") << secs << ')' << std::endl; } }; // PrintTrack struct PrintDisc : public std::unary_function { std::ostream &os_; unsigned int count; PrintDisc(std::ostream &os) : os_(os), count(0) { } void operator() (const TagLookup::Disc &disc) { ++count; // TODO: This is rubbish os_ << '[' << (count < 10 ? " " : "") << count << "] " << disc.getArtist() << " - " << disc.getTitle() << " (Year: " << disc.getYear() << " Genre: " << disc.getGenre() << ")" << std::endl; const Tracks &ts = disc.getTracks(); std::for_each(ts.begin(), ts.end(), PrintTrack(os_, 5)); } }; // PrintDisc struct PrintOldNameNewName : public std::unary_function { std::ostream &os_; int n_; PrintOldNameNewName(std::ostream &os) : os_(os), n_(1) { } void operator() (const OldNameNewName &onnn) { os_ << "[" << (n_ < 10 ? " " : "") << n_ << "] " << "'" << onnn.old_name << "' -> '" << onnn.new_name << "'" << std::endl; ++n_; } }; // PrintOldNameNewName void banner(std::ostream &os, bool full) { os << TAGLOOKUP_NAME << " " << TAGLOOKUP_VERSION << " Release Date: " << TAGLOOKUP_VERSION_DATE << " Built: " << __DATE__ << ' ' << __TIME__ << std::endl; if(full) { os << TAGLOOKUP_COPYRIGHT << std::endl; os << TAGLOOKUP_WEBSITE << std::endl; } os << std::endl; } void usage(std::ostream &os) { banner(os, true); os << "taglookup [-c cache_mode] [-s hostname] [-p port] [-P protocol] [-h] command [command_arguments] file...\n" << "\n" << "Commands:\n" << "* id cddb_category cddb_id_hex: retrieves the given disc from the CDDB\n" << " database and tags each file by closest length.\n" << "* sequence: generates a CDDB ID, queries the CDDB database\n" << "* rename: renames files based on a template with the following tokens:\n" << " %a = artist, %g = genre. %s = song title, %t = album title\n" << " %y = year, %n = track number\n" << "* help: this help screen\n" << "\n" << "Flags:\n" << "* -c cache_mode: use one of 'on', 'off' or 'only' (default: on)\n" << "* -s hostname: use hostname as the CDDB server (default: " << TAGLOOKUP_CDDBP_SERVER << ")\n" << "* -p port: connect to CDDB server using port (default for cddbp: " << TAGLOOKUP_CDDBP_PORT << " default for http: " << TAGLOOKUP_HTTP_PORT << ")\n" << "* -P protocol: use one of 'cddbp', 'http' or 'proxy' (default: cddbp)" << std::endl << std::endl; } /** * Process the http_proxy environment vairable. */ struct ProxyDetails { std::string hostname; unsigned int port; std::string username; std::string password; }; void parseProxyString(const std::string &ps, ProxyDetails &pd) { std::string prefix = "http://"; if(ps.find_first_of(prefix) != 0) throw ParseException("proxy string must begin http://"); if(ps.length() == prefix.length()) throw ParseException("proxy string must contain at least a hostname"); size_t start_loc = prefix.length(); size_t at_loc = ps.find_first_of("@", start_loc); if(at_loc != (size_t)-1) { // There is a username and password std::size_t colon_loc = ps.find_first_of(":", start_loc); if(colon_loc == (size_t)-1) throw ParseException("Proxy strings with an @ must contain both a username and a password"); pd.username = ps.substr(start_loc, colon_loc - start_loc); pd.password = ps.substr(colon_loc + 1, at_loc - colon_loc - 1); start_loc = at_loc + 1; } std::size_t colon_loc = ps.find_first_of(":", start_loc); if(colon_loc == (size_t)-1) { // no port - default to TAGLOOKUP_HTTP_PROXY_PORT pd.port = TAGLOOKUP_HTTP_PROXY_PORT; pd.hostname = ps.substr(start_loc, ps.length() - start_loc); } else { // port pd.port = std::atoi(ps.substr(colon_loc + 1, ps.length() - colon_loc - 1).c_str()); pd.hostname = ps.substr(start_loc, colon_loc - start_loc); } return; } /** * Set those all-important flags for libcddb */ void setArguments(const CommandLineArguments &cla, CDDBQuery *q) { if(cla.protocol == TAGLOOKUP_PROTOCOL_CDDBP) { q->setProtocolCDDBP( (cla.server_specified ? cla.server : TAGLOOKUP_CDDBP_SERVER), (cla.port_specified ? cla.port : TAGLOOKUP_CDDBP_PORT) ); } if(cla.protocol == TAGLOOKUP_PROTOCOL_HTTP) { q->setProtocolHTTP( (cla.server_specified ? cla.server : TAGLOOKUP_HTTP_SERVER), (cla.port_specified ? cla.port : TAGLOOKUP_HTTP_PORT) ); } if(cla.protocol == TAGLOOKUP_PROTOCOL_HTTP_PROXY) { // Get the environment variable const char *http_proxy_char = std::getenv(TAGLOOKUP_HTTP_PROXY_ENV_NAME); if(http_proxy_char == NULL) throw ParseException("You must have the environemnt variable TAGLOOKUP_HTTP_PROXY_ENV_NAME"); std::string http_proxy(http_proxy_char); ProxyDetails pd; parseProxyString(http_proxy, pd); q->setProtocolHTTP( (cla.server_specified ? cla.server : TAGLOOKUP_HTTP_SERVER), (cla.port_specified ? cla.port : TAGLOOKUP_HTTP_PORT), pd.hostname, pd.port, pd.username, pd.password ); } if(cla.cache_mode_specified) q->setCache(cla.cache_mode); else q->setCache(ON); } int main(int argc, char* argv[]) { // First of all, we need to parse the command-line arguments. CommandLineArguments cla; CommandLineArgumentsParser clap; try { clap = CommandLineArgumentsParser(argc, argv); } catch(ParseException &e) { usage(std::cout); std::cout << e.what() << std::endl; return 1; } cla = clap.getArguments(); CDDBQuery *q = NULL; if(cla.command == TAGLOOKUP_COMMAND_HELP || cla.command == TAGLOOKUP_COMMAND_NONE) { usage(std::cout); return 1; } if(cla.command == TAGLOOKUP_COMMAND_LIST) { banner(std::cout, false); FileDisc fd(cla.files); const ProblemFiles &pfs = fd.checkFiles(); if(!pfs.empty()) { // There's a problem with at least one of our files std::for_each(pfs.begin(), pfs.end(), PrintProblemFiles(std::cerr)); return 3; // ends here } fd.printDetails(std::cout); } if(cla.command == TAGLOOKUP_COMMAND_RENAME) { banner(std::cout, false); // Check for a sane template. if(cla.templ.find(TOKEN) == (size_t)(-1)) { std::cout << "No valid token found in template." << std::endl; return 4; } FileDisc fd(cla.files); // Check for file valididty. const ProblemFiles &pfs = fd.checkFiles(); if(!pfs.empty()) { // There's a problem with at least one of our files std::for_each(pfs.begin(), pfs.end(), PrintProblemFiles(std::cerr)); return 3; // ends here } std::vector onnns = fd.generateNewNames(cla.templ); std::for_each(onnns.begin(), onnns.end(), PrintOldNameNewName(std::cout)); char response; std::cout << std::endl << "Rename [yn] ? "; std::cin >> response; if(response == 'y' || response == 'Y') { fd.renameFiles(onnns); } else { std::cout << std::endl << "Cancelled" << std::endl; } return 0; } if(cla.command == TAGLOOKUP_COMMAND_ID || cla.command == TAGLOOKUP_COMMAND_SEQUENCE) { banner(std::cout, false); FileDisc fd(cla.files); // Check for file valididty. const ProblemFiles &pfs = fd.checkFiles(); if(!pfs.empty()) { // There's a problem with at least one of our files std::for_each(pfs.begin(), pfs.end(), PrintProblemFiles(std::cerr)); return 3; // ends here } try // for CDDBQueryException and TagException { if(cla.command == TAGLOOKUP_COMMAND_ID) { q = new CDDBQuery(cla.category, cla.disc_id); } if(cla.command == TAGLOOKUP_COMMAND_SEQUENCE) { Offsets offsets = fd.generateOffsets(150); q = new CDDBQuery(offsets.length, offsets.offset_list); } assert(q != NULL); setArguments(cla, q); q->query(); const Discs &d = q->getResults(); std::for_each(d.begin(), d.end(), PrintDisc(std::cout)); if(d.size() != 0) { unsigned int choice = 1; do { std::cout << "Which disc [1-" << d.size() << "] ? "; std::cin >> choice; } while(choice < 1 || choice > d.size() + 1); const Disc &chosen_disc = d[choice - 1]; if(cla.command == TAGLOOKUP_COMMAND_ID) { fd.tagByBestFit(chosen_disc); } else if(cla.command == TAGLOOKUP_COMMAND_SEQUENCE) { fd.tagByOrder(chosen_disc); } } else { std::cout << "No results." << std::endl; return 2; // ends here } } // try catch(std::exception &e) { std::cout << "TagLookup: Error: " << e.what() << std::endl; delete q; return 4; } // catch } delete q; return 0; }