mp3check-0.8.7/000755 000766 000766 00000000000 11763233643 012361 5ustar00ovov000000 000000 mp3check-0.8.7/configure000755 000766 000766 00000000115 11763233643 014265 0ustar00ovov000000 000000 #!/bin/sh eval `grep ^TARGET TARGET` echo Configuring $TARGET ... echo done mp3check-0.8.7/COPYING000644 000766 000766 00000043076 11763233643 013426 0ustar00ovov000000 000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. mp3check-0.8.7/crc16.cc000640 000766 000766 00000002212 11763233643 013577 0ustar00ovov000000 000000 /*GPL*START* * * crc engine for use with audio mpeg streams * * Copyright (C) 1998-2001 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * *GPL*END*/ #include "crc16.h" // create crc engine and reset to 0 (build shift table) CRC16::CRC16(unsigned int polynom): c(0) { for(unsigned int i = 0; i < 256; ++i) { unsigned int x = i << 9; for(int j=0; j < 8; ++j, x <<= 1) if(x & 0x10000) x ^= polynom; tab[i] = x>>1; } } mp3check-0.8.7/crc16.h000640 000766 000766 00000003654 11763233643 013454 0ustar00ovov000000 000000 /*GPL*START* * * crc engine for use with audio mpeg streams header file * * Copyright (C) 1998-2001 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * *GPL*END*/ // 1998: // 21 Jan started // 22 Jan first usable version: modified and tested for use with audio mpeg // 23 Jan bit reversal in init removed // 27 Jan doc and init c(0) in cons // 2000: // 10 Jul 01:00 ttypes.h removed // 2001: // 26 Feb 00:00 Khali added file crc16.cc for better code structure // 26 Feb 00:00 reset(), add() and crc() made inline again for speed reasons class CRC16 { public: enum { // CRC-16 polynom: x^16 + x^15 + x^2 + x^0 CRC_16 = 0x18005, // works for audio mpeg // CCITT polynom: x^16 + x^12 + x^5 + x^0 CCITT = 0x11021 // (untested) }; // create crc engine and reset to 0 (build shift table) CRC16(unsigned int polynom); // reset engine to init void reset(unsigned short init = 0) { c = init; } // add 8 bits void add(unsigned char b) { c = tab[(c>>8)^b] ^ (c<<8); } // get current crc value unsigned short crc() const { return c; } private: // private data unsigned short tab[256]; // shift table unsigned short c; // current crc value }; mp3check-0.8.7/id3tag.cc000640 000766 000766 00000021343 11763233643 014042 0ustar00ovov000000 000000 /*GPL*START* * * id3tag.cc - id3 tag detection and analysis * * Copyright (C) 2000 by Jean Delvare * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * *GPL*END*/ #include "string.h" #include "id3tag.h" /************************ ** Auxiliary functions ** ************************/ // Returns wether or not a field complies with the specification. bool Tagv1::valid_tag_field_strict(const unsigned char *p, const int len) { int i; bool filling; for(i=0, filling=false; isetTarget(p+i); if(tag->isValidGuess()) { delete tag; return(i); } } delete tag; return(-1); // not found } /**************** ** Tagv1 class ** ****************/ // Constructors and destructor Tagv1::Tagv1() { target=NULL; al=false; } Tagv1::Tagv1(const unsigned char *p) { target=(unsigned char *)p; al=false; } Tagv1::~Tagv1() { if(al&&target) delete [] target; } // Version of the tag, 1.0 or 1.1 so far. The value is return as a 16-bits // unsigned integer. The method for detecting the v1.1 tags was found in // [1]. short unsigned int Tagv1::version() const { if(target[125]==0&&target[126]!=0) return(0x0101); else return(0x0100); } // First level of validity. We only check if the tag begins with 'TAG'. // It is enough if the tag is at the very end of the file. bool Tagv1::isValid() const { return(!(bool)memcmp(target,"TAG",3)); } // Second level of validity. We want to know if what we have has chances to be // a tag. This verification is looser than isValidSpecs(), since we stop // reading the fields after the first binary zero. bool Tagv1::isValidGuess() const { // The tag must begin with 'TAG'. if(memcmp(target,"TAG",3)) return(false); // Next 30 bytes: title, filled with binary zeroes. if(!Tagv1::valid_tag_field_loose(target+3,30)) return(false); // Next 30 bytes: artist, filled with binary zeroes. if(!Tagv1::valid_tag_field_loose(target+33,30)) return(false); // Next 30 bytes: album, filled with binary zeroes. if(!Tagv1::valid_tag_field_loose(target+63,30)) return(false); // Next 4 bytes: year. if(!Tagv1::valid_tag_field_loose(target+93,4)) return(false); // Next 30 chars: comment, filled with binary zeroes. // In fact, the 30th char is the track number for v1.1, we can't assume // anything on it. if(!Tagv1::valid_tag_field_loose(target+97,29)) return(false); // Last one char: byte-coded genre. // We accept all values here, in case some new genres have been added. // All tests successful. return(true); } // Third level of validity. This time, we want to know if the tag complies // with the specifications as found in [1] (see comments). Note that we // treat the year field as a normal string. bool Tagv1::isValidSpecs() const { // The tag must begin with 'TAG'. This has probably already been checked // by either isValid() or isValidGuess(), but maybe not. if(memcmp(target,"TAG",3)) return(false); // Next 30 bytes: title, filled with binary zeroes. if(!Tagv1::valid_tag_field_strict(target+3,30)) return(false); // Next 30 bytes: artist, filled with binary zeroes. if(!Tagv1::valid_tag_field_strict(target+33,30)) return(false); // Next 30 bytes: album, filled with binary zeroes. if(!Tagv1::valid_tag_field_strict(target+63,30)) return(false); // Next 4 bytes: year. if(!Tagv1::valid_tag_field_strict(target+93,4)) return(false); // Next 30 chars: comment, filled with binary zeroes. // In fact, the 30th char is the track number for v1.1, we can't assume // anything on it. if(!Tagv1::valid_tag_field_strict(target+97,29)) return(false); // Last one char: byte-coded genre. if((target[127]>=Tagv1::genres_count)&&(target[127]!=0xff)) return(false); // All tests successful. return(true); } void Tagv1::setTarget(const unsigned char *p) { target=(unsigned char *)p; } bool Tagv1::store() { if(!al) { unsigned char *p=new unsigned char[128]; memcpy(p,target,128); target=p; al=true; return(true); } else return(false); } bool Tagv1::restore(unsigned char *p) { if(al) { memcpy(p,target,128); delete [] target; target=p; al=false; return(true); } else return(false); } void Tagv1::copyStringField(char *dest, const unsigned char *src, int len) { memcpy(dest,src,len); dest[len]='\0'; for(int i=0;i0) && (dest[i]<32)) dest[i]='!'; else if(dest[i] & 128) dest[i]='?'; } } void Tagv1::fillFields() { fields_version=version(); fields_spacefilled=false; Tagv1::copyStringField(field_title,target+3,30); Tagv1::copyStringField(field_artist,target+33,30); Tagv1::copyStringField(field_album,target+63,30); Tagv1::copyStringField(field_year,target+93,4); fields_spacefilled|=Tagv1::spacefilled_tag_field(field_title,30); fields_spacefilled|=Tagv1::spacefilled_tag_field(field_artist,30); fields_spacefilled|=Tagv1::spacefilled_tag_field(field_album,30); fields_spacefilled|=Tagv1::spacefilled_tag_field(field_year,4); field_genre=target[127]; if((fields_version&0xff)==1) { field_track=target[126]; Tagv1::copyStringField(field_comment,target+97,28); fields_spacefilled|=Tagv1::spacefilled_tag_field(field_comment,28); } else { field_track=0; Tagv1::copyStringField(field_comment,target+97,30); fields_spacefilled|=Tagv1::spacefilled_tag_field(field_comment,30); } } // the following table comes from xmms/mpeg123 const char * const Tagv1::id3_genres[] = { "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "Alt", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta Rap", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychedelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A Cappella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", "Synthpop" }; const int Tagv1::genres_count = sizeof(id3_genres) / sizeof(*id3_genres); mp3check-0.8.7/id3tag.h000640 000766 000766 00000006551 11763233643 013710 0ustar00ovov000000 000000 /*GPL*START* * * id3tag.h - id3 tag detection and analysis header file * * Copyright (C) 2000 by Jean Delvare * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * *GPL*END*/ /*COMMENTS * * Reference docs are : * [1] Id3 made easy, http://www.id3.org/id3v1.html * [2] Xmms sources (xmms-1.2.3/Input/mpg123/mpg123.c) * * The specification [1] is somehow incomplete, so I'd like to add: * 1- Most software don't fill the fields with binary zeroes but with spaces * (Ox20). This doesn't really go against the specification. * 2- The specification doesn't say what characters may be used in an id3 tag. * I've considered that the use of non-printable chars (0x01 to 0x1f) is * prohibited. * 3- As far as the year is concerned, the specification doesn't say wether * only digits must be used of not. I suggest that the only two accepted * possibilities are: not set (and thus filled with binary zeroes) or * set to a four-digit year. But we also will accept four spaces as a * correct value, since most software fill blanks with spaces. When only * guessing, we treat that field exactly like any other, however. * 4- As seen in [2], there are more than 80 genres as seen in [1]. Also, * the value 0xff seems to be used when genre is not set. Thus, we will * accept all these values (0x00 to 0x93, plus 0xff). * * *COMMENTS*END*/ #ifndef _id3tag_h_ #define _id3tag_h_ class Tagv1 { public: // Default constructor. Tagv1(); // Constructor from pointer. Note that it is really only a pointer, the // data is not duplicated. This is probably not the safest way, but the // fastest. Tagv1(const unsigned char *p); // Destructor, doing nothing. ~Tagv1(); // Methods. short unsigned int version() const; bool isValid() const; bool isValidSpecs() const; bool isValidGuess() const; void setTarget(const unsigned char *p); bool store(); bool restore(unsigned char *p); void fillFields(); // Static functions. static bool valid_tag_field_strict(const unsigned char *p, const int len); static bool valid_tag_field_loose(const unsigned char *p, const int len); static int find_next_tag(const unsigned char *p, int len); static void copyStringField(char *dest, const unsigned char *src, int len); unsigned short int fields_version; bool fields_spacefilled; char field_title[31]; char field_artist[31]; char field_album[31]; char field_year[5]; char field_comment[31]; unsigned char field_genre; unsigned char field_track; static const char * const id3_genres[]; static const int genres_count; private: static bool spacefilled_tag_field(const char *p, const unsigned int len); unsigned char *target; bool al; // wether memory is allocated or not }; #endif mp3check-0.8.7/INSTALL000644 000766 000766 00000000075 11763233643 013414 0ustar00ovov000000 000000 Installation: ============= ./configure make make install mp3check-0.8.7/Makefile000644 000766 000766 00000003260 11763233643 014022 0ustar00ovov000000 000000 # Copyright (C) 2008 by Johannes Overmann # Please see COPYING for license. # --- config ---------------------------------------------------------------- WARN = -Wall -W -g #OPT = -O2 OPT = CPPFLAGS = $(ADDITIONAL_CPPFLAGS) CXXFLAGS = $(WARN) $(OPT) CXX = g++ CC = $(CXX) # --- default target default: all # --- target definition ----------------------------------------------------- -include Makefile.init # indirectly include TARGET include TARGET SRC := $(wildcard *.cc) $(ADDITIONAL_SOURCES) VERSION := $(shell grep '\#define VERSION' $(TARGET).cc | sed 's/.*"\([^"]*\)".*/\1/g') DISTFILES := $(SRC) $(wildcard *.h) TARGET INSTALL COPYING Makefile configure $(wildcard $(TARGET).1) # --- common rules ---------------------------------------------------------- OBJ := $(SRC:.cc=.o) all: $(TARGET) usage: @echo "Targets: $(TARGET) strip install dist clean" $(TARGET): $(OBJ) strip: strip $(TARGET) install: all strip cp $(TARGET) /usr/local/bin PACKAGE = $(TARGET)-$(VERSION) dist: rm -rf $(PACKAGE) mkdir $(PACKAGE) cp $(DISTFILES) $(PACKAGE) tar czvhf $(PACKAGE).tgz $(PACKAGE) clean: rm -f $(OBJ) $(DEP) $(TARGET) *~ $(PACKAGE).tgz rm -rf $(PACKAGE) $(ADDITIONAL_CLEANFILES) svnclean: clean .PHONY: default all clean strip dist svnclean install usage # --- meta object compiler for qt ------------------------------------------- moc_%.cc: %.h moc -o $@ $< # --- dependency generation ------------------------------------------------- .dep.%: %.cc $(CXX) $(CPPFLAGS) -MM -MT "$@ $(<:%.cc=%.o)" $< -o $@ DEP := $(SRC:%.cc=.dep.%) ifeq ($(findstring $(MAKECMDGOALS),clean),) ifeq ($(findstring $(MAKECMDGOALS),svnclean),) -include $(DEP) endif endif mp3check-0.8.7/mp3check.1000644 000766 000766 00000020365 11763233643 014146 0ustar00ovov000000 000000 .\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH MP3CHECK 1 "March 1, 2001" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME mp3check \- check mp3 files for consistency .SH SYNOPSIS .B mp3check [\-03ABCEFGIKLMNPRSTWYZabcdefghlmopqrst] [\-\-accept=LIST] [\-\-alt-color] [\-\-anomaly-check] [\-\-anomaly-check] [\-\-any-bitrate] [\-\-any\-crc] [\-\-any\-emphasis] [\-\-any-layer] [\-\-any-mode] [\-\-any-sampling] [\-\-any\-version] [\-\-ascii\-only] [\-\-color] [\-\-compact-list] [\-\-cut-junk-end] [\-\-cut-junk-start] [\-\-cut-tag-end] [\-\-dummy] [\-\-dump\-tag] [\-\-dump-header] [\-\-dump-tag] [\-\-edit\-frame\-byte=P] [\-\-error-check] [\-\-error\-check] [\-\-filelist=FILE] [\-\-fix-crc] [\-\-fix-headers] [\-\-help] [\-\-ign-bitrate-sw] [\-\-ign\-constant\-sw] [\-\-ign\-crc\-error] [\-\-ign-junk-end] [\-\-ign-junk-start] [\-\-ign\-non\-ampeg] [\-\-ign\-resync] [\-\-ign-tag128] [\-\-ign-truncated] [\-\-list] [\-\-log-file=FILE] [\-\-max-errors=NUM] [\-\-only\-mp3] [\-\-print\-files] [\-\-progress] [\-\-quiet] [\-\-raw\-elem\-sep=NUM] [\-\-raw\-line\-sep=NUM] [\-\-raw-list] [\-\-recursive] [\-\-reject=LIST] [\-\-show\-valid] [\-\-single-line] [\-\-version] [\-\-xdev] [\-\-] [FILES...] .br .SH DESCRIPTION This manual page documents briefly the .B mp3check command. This manual page was written for the Debian GNU/Linux distribution because the original program does not have a manual page. .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invoke bold face and italics, .\" respectively. \fBmp3check\fP is a program that checks mp3 files for consistency and prints several errors and warnings. It lists stream attributes (color). Layer 1,2,3, mpeg1.0+2.0 are currently supported. CRC check for layer 3. \fBmp3check\fP is very useful for incomplete mp3 detection as it can be used to scan through your mp3 collection and find all mp3s that aren't perfect. Good for use with Napster and other bulk downloading of mp3s. .SH OPTIONS These programs follow the usual GNU command line syntax, with long options starting with two dashes (`-'). Options can be specified in any order and mixed with files. Option scanning stops after a double dash (\-\-) to allow files beginning with a dash. A summary of options is included below. .TP \fBmode:\fP .TP .B \-l \-\-list list parameters by examining the first valid header and size .TP .B \-c \-\-compact-list list parameters of one file per line in a very compact format: version (l=1.0, L=2.0), layer, sampling frequency [kHz] (44=44.1), bitrate [kbit/s], mode (js=joint stereo, st=stereo, sc=single channel, dc=dual channel), emphasis (n=none, 5=50/15 usecs, J=CCITT J.17), COY (has [C]rc, [O]riginal, cop[Y]right), length [min:sec], filename (poss. truncated) .TP .B \-e \-\-error-check check crc and headers for consistency and print several error messages .TP .B \-m \-\-max-errors= with -e: set maximum number of errors to print per file (0==infinity) (range=[0..]) .TP .B \-a \-\-anomaly-check report all differences from these parameters: layer 3, 44.1kHz, 128kbps, joint stereo, no emphasis, has crc .TP .B \-d \-\-dump-header dump all possible header with sync=0xfff .TP .B \-t \-\-dump-tag dump all possible tags of known version .TP .B \-\-raw-list list parameters in raw output format for use with external programs .TP .B \-\-raw-elem-sep=NUM separate elements in one line by char NUM (numerical ASCII code) (default="0x09") .TP .B \-\-raw-line-sep=NUM separate lines by char NUM (numerical ASCII code) (default="0x0a") .TP .B \-\-edit\-frame\-byte=P modify a single byte of a specific frame at a specific offset; B has the format 'frame,offset,byteval', (use 0xff for hex or 255 for dec or 0377 for octal); this mode operates on all given files and is useful for your own experiment with broken streams or while testing this toll ;-) .TP \fBfix errors:\fP .TP .B \-\-cut-junk-start remove junk before first frame .TP .B \-\-cut-junk-end remove junk after last frame .TP .B \-\-fix-headers fix invalid headers (prevent constant parameter switching), implies -e, use with care .TP .B \-\-fix-crc fix crc (set crc to the calculated one), implies -e, use with care (note: it is not possible to add crc to files which have been created without crc) .TP \fBdisable error messages for -e --error-check:\fP .TP .B \-G \-\-ign-tag128 ignore 128 byte TAG after last frame .B \-R \-\-ign-resync ignore invalid frame header .TP .B \-E \-\-ign-junk-end ignore junk after last frame .TP .B \-Z \-\-ign-crc-error ignore crc errors .TP .B \-N \-\-ign-non-ampeg ignore non audio mpeg streams .TP .B \-T \-\-ign-truncated ignore truncated last frames .TP .B \-S \-\-ign-junk-start ignore junk before first frame .TP .B \-B \-\-ign-bitrate-sw ignore bitrate switching and enable VBR support .TP .B \-W \-\-ign-constant-sw ignore switching of constant parameters, such as sampling frequency .TP .B \-\-show\-valid print the message 'valid audio mpeg stream' for all files which error free (after ignoring errors) .TP \fBdisable anomaly messages for -a --anomaly-check:\fP .TP .B \-C \-\-any-crc ignore crc anomalies .TP .B \-M \-\-any-mode ignore mode anomalies .TP .B \-L \-\-any-layer ignore layer anomalies .TP .B \-K \-\-any-bitrate ignore bitrate anomalies .TP .B \-I \-\-any-version ignore version anomalies .TP .B \-F \-\-any-sampling ignore sampling frequency anomalies .TP .B \-P \-\-any-emphasis ignore emphasis anomalies .TP \fBfile options:\fP .TP .B \-r \-\-recursive process any given directories recursively (the default is to ignore all directories specified on the command line) .TP .B \-f \-\-filelist=FILE process all files specified in FILE (one filename per line) in addition to the command line .TP .B \-A \-\-accept=LIST process only files with filename extensions specified by comma separated LIST .TP .B \-R \-\-reject=LIST do not process files with a filename extension specified by comma separated LIST .TP .B \-3 \-\-only-mp3 same as .B \-\-accept mp3,MP3 .TP .B \-\-xdev do not descend into other filesystems when recursing directories (doesn't work in Cygwin environment) .TP .B \-\-print\-files just print all filenames without processing them, then exit .TP \fBoutput options:\fP .TP .B \-s \-\-single-line print one line per file and message instead of splitting into several lines .TP .B \-\-no\-summary suppress the summary printed below all messages if multiple files are given .TP .B \-g \-\-log-file=FILE print names of erroneous files to FILE, one per line .TP .B \-q \-\-quiet quiet mode, hide messages about directories, non-regular or non-existing files .TP .B \-o \-\-color colorize output with ANSI sequences .TP .B \-b \-\-alt-color colorize: do not use bold ANSI sequences .TP .B \-\-ascii-only generally all unprintable characters in filenames etc are replaced by '!' (ASCII 0-31) and '?' (ASCII 127-159), with this option present the range ASCII 160-255 (which is usually printable: e.g. ISO-8859) is also printed as '?' .TP .B \-p \-\-progress show progress information on stderr .TP \fBcommon options:\fB .TP .B \-0 \-\-dummy do not write/modify anything other than the logfile .TP .B \-h \-\-help print this help message, then exit successfully .TP .B \-\-version print version, then exit successfully .SH AUTHOR This original manual page was written by Klaus Kettner , for the Debian GNU/Linux system. The current version of this manpage is maintained by Johannes Overmann , the author of mp3check. mp3check-0.8.7/mp3check.cc000644 000766 000766 00000206546 11763233643 014402 0ustar00ovov000000 000000 /*GPL*START* * mp3check - check mp3 file for consistency and print infos * * Copyright (C) 1998-2005,2008-2009 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #include #include #include #include #include #include #include #include "tappconfig.h" #include "crc16.h" #include "id3tag.h" #include "tfiletools.h" #define ONLY_MP3 "mp3,MP3,Mp3,mP3" // please update also HISTORY #define VERSION "0.8.7" const char *options[] = { "#usage='Usage: [OPTIONS, FILES AND DIRECTORIES] [--] [FILES AND DIRECTORIES]\n\n" "this program checks audio mpeg layer 1,2 and 3 (*.mp3) files for\n" "consistency (headers and crc) and anomalies'", "#trailer='\n%n version %v *** (C) 1998-2003,2005,2008-2009,2012 by Johannes Overmann\ncomments, bugs and suggestions welcome: %e\n%gpl'", "#stopat--", "#onlycl", // options "name=list , type=switch, char=l, help='list parameters by examining the first valid header and size', headline=mode:", "name=compact-list , type=switch, char=c, help='list parameters of one file per line in a very compact format: " "version (l=1.0, L=2.0), layer, sampling frequency [kHz] (44=44.1), bitrate [kbit/s], mode (js=joint stereo, st=stereo, sc=single channel, dc=dual channel), " "emphasis (n=none, 5=50/15 usecs, J=CCITT J.17), COY (has [C]rc, [O]riginal, cop[Y]right), length [min:sec], filename (poss. truncated)'", "name=error-check , type=switch, char=e, help='check crc and headers for consistency and print several error messages'", "name=max-errors , type=int , char=m, param=N, lower=0, help='with -e: set maximum number of errors N to print per file (default 0==infinity)'", "name=anomaly-check , type=switch, char=a, help='report all differences from these parameters: layer 3, 44.1kHz, 128kB, joint stereo, no emphasis, has crc'", "name=dump-header , type=switch, char=d, help='dump all possible header with sync=0xfff'", "name=dump-tag , type=switch, char=t, help='dump all possible tags of known version'", "name=raw-list , type=switch, , help='list parameters in raw output format for use with external programs'", "name=raw-elem-sep , type=string, , default=0x09, param=N, help='separate elements in one line by char N (numerical ASCII code)'", "name=raw-line-sep , type=string, , default=0x0a, param=N, help='separate lines by char N (numerical ASCII code)'", "name=edit-frame-b , type=string, , param=P, help='modify a single byte of a specific frame at a specific offset; B has the format \\'frame,offset,byteval\\', (use 0xff for hex or 255 for dec or 0377 for octal); this mode operates on all given files and is useful for your own experiment with broken streams or while testing this tool ;-)'", "name=cut-junk-start , type=switch, , help='remove junk before first frame', headline='fix errors:'", "name=cut-junk-end , type=switch, , help='remove junk after last frame'", "name=cut-tag-end , type=switch, , help='remove trailing tag'", "name=fix-headers , type=switch, , help='fix invalid headers (prevent constant parameter switching), implies -e, use with care'", "name=fix-crc , type=switch, , help='fix crc (set crc to the calculated one), implies -e, use with care\n(note: it is not possible to add crc to files which have been created without crc)'", "name=add-tag , type=switch, , help='add ID3 v1.1 tag calculated from filename and path using simple heuristics if a file does not already have a tag'", "name=ign-tag128 , type=switch, char=G, help='ignore 128 byte TAG after last frame', headline='disable error messages for -e --error-check:'", "name=ign-resync , type=switch, char=Y, help='ignore synchronization errors (invalid frame header/frame too long/short)'", "name=ign-junk-end , type=switch, char=E, help='ignore junk after last frame'", "name=ign-crc-error , type=switch, char=Z, help='ignore crc errors'", "name=ign-non-ampeg , type=switch, char=N, help='ignore non audio mpeg streams'", "name=ign-truncated , type=switch, char=T, help='ignore truncated last frames'", "name=ign-junk-start , type=switch, char=S, help='ignore junk before first frame'", "name=ign-bitrate-sw , type=switch, char=B, help='ignore bitrate switching and enable VBR support'", "name=ign-constant-sw , type=switch, char=W, help='ignore switching of constant parameters, such as sampling frequency'", "name=show-valid , type=switch, , help='print the message \\'valid audio mpeg stream\\' for all files which appear to be error free (after ignoring errors)", "name=any-crc , type=switch, char=C, help='ignore crc anomalies', headline='disable anomaly messages for -a --anomaly-check'", "name=any-mode , type=switch, char=M, help='ignore mode anomalies'", "name=any-layer , type=switch, char=L, help='ignore layer anomalies'", "name=any-bitrate , type=switch, char=K, help='ignore bitrate anomalies'", "name=any-version , type=switch, char=I, help='ignore version anomalies'", "name=any-sampling , type=switch, char=F, help='ignore sampling frequency anomalies'", "name=any-emphasis , type=switch, char=P, help='ignore emphasis anomalies'", "name=recursive , type=switch, char=r, help='process any given directories recursively (the default is to ignore all directories specified on the command line)', headline='file options:'", "name=filelist , type=string, char=f, param=FILE, help='process all files specified in FILE (one filename per line) in addition to the command line'", "name=accept , type=string, char=A, param=LIST, help='process only files with filename extensions specified by comma separated LIST'", "name=reject , type=string, char=R, param=LIST, help='do not process files with a filename extension specified by comma separated LIST'", "name=only-mp3 , type=switch, char=3, help='same as --accept " ONLY_MP3 "'", #ifndef __CYGWIN__ "name=xdev , type=switch, help='do not descend into other filesystems when recursing directories'", #endif "name=print-files , type=switch, help='just print all filenames without processing them, then exit (for debugging purposes, also useful to create files for --filelist)'", "name=single-line , type=switch, char=s, help='print one line per file and message instead of splitting into several lines', headline='output options:'", "name=no-summary , type=switch, , help='suppress the summary printed below all messages if multiple files are given'", "name=log-file , type=string, char=g, param=FILE, help='print names of erroneous files to FILE, one per line'", "name=quiet , type=switch, char=q, help='quiet mode, hide messages about directories, non-regular or non-existing files'", "name=color , type=switch, char=o, help='colorize output with ANSI sequences'", "name=alt-color , type=switch, char=b, help='colorize: do not use bold ANSI sequences'", "name=ascii-only , type=switch, help='replace the range of ASCII chars 160-255 (which is usually printable: e.g. ISO-8859) by \\'?\\''", "name=progress , type=switch, char=p, help='show progress information on stderr'", "name=verbose , type=switch, char=v, help='be more verbose'", "name=no-mmap , type=switch, , help='do not use mmap (e.g. when you get \\'mmap: No such device\\')'", "name=dummy , type=switch, char=0, help='do not write/modify anything other than the logfile', headline=common options:", "EOL" // end of list }; // default value for systems which do not define O_BINARY #ifndef O_BINARY #define O_BINARY 0 #endif // color config: // 30 black // 31 red // 32 green // 33 yellow // 34 blue // 35 magenta // 36 cyan // 37 white const char *cfil = "\033[1;37m"; const char *cano = "\033[1;33m"; const char *cerror = "\033[1;31m"; const char *cval = "\033[1;34m"; const char *cok = "\033[0;32m"; const char *cnor = "\033[0m"; const char *c_fil = "\033[37m"; const char *c_ano = "\033[33m"; const char *c_err = "\033[31m"; const char *c_val = "\033[34m"; const char *c_ok = "\033[32m"; const char *c_nor = "\033[0m"; // minimum number of sequential valid and constant frame headers to validate header const int MIN_VALID = 6; const int LIST_MAX_HEADER_SEARCH = 1024*1024; // search max 1MB for --list --compact-list --raw-list // global data bool progress = false; bool single_line = false; bool dummy = false; int max_errors = 0; unsigned int columns = 0; bool show_valid_files = false; bool quiet = false; bool only_ascii = false; bool ano_any_crc = false; bool ano_any_bit = false; bool ano_any_emp = false; bool ano_any_rate = false; bool ano_any_mode = false; bool ano_any_layer = false; bool ano_any_ver = false; bool ign_crc = false; bool ign_start = false; bool ign_end = false; bool ign_tag = false; bool ign_bit = false; bool ign_const = false; bool ign_trunc = false; bool ign_noamp = false; bool ign_sync = false; // header info int layer_tab[4]= {0, 3, 2, 1}; const int FREEFORMAT = 0; const int FORBIDDEN = -1; int bitrate1_tab[16][3] = { {FREEFORMAT, FREEFORMAT, FREEFORMAT}, {32, 32, 32}, {64, 48, 40}, {96, 56, 48}, {128, 64, 56}, {160, 80, 64}, {192, 96, 80}, {224, 112, 96}, {256, 128, 112}, {288, 160, 128}, {320, 192, 160}, {352, 224, 192}, {384, 256, 224}, {416, 320, 256}, {448, 384, 320}, {FORBIDDEN, FORBIDDEN, FORBIDDEN} }; // int bitrate2_tab[16][3] = { {FREEFORMAT, FREEFORMAT, FREEFORMAT}, { 32, 8, 8}, { 48, 16, 16}, { 56, 24, 24}, { 64, 32, 32}, { 80, 40, 40}, { 96, 48, 48}, {112, 56, 56}, {128, 64, 64}, {144, 80, 80}, {160, 96, 96}, {176,112,112}, {192,128,128}, {224,144,144}, {256,160,160}, {FORBIDDEN, FORBIDDEN, FORBIDDEN} }; double sampd1_tab[4]={44.1, 48.0, 32.0, 0.0}; int samp_1_tab[4]={44100, 48000, 32000, 50000}; double sampd2_tab[4]={22.05, 24.0, 16.0, 0.0}; int samp_2_tab[4]={22050, 24000, 16000, 50000}; const unsigned int CONST_MASK = 0xffffffff; struct Header { #ifdef WORDS_BIGENDIAN unsigned int syncword: 12, // fix must 0xfff ID: 1, // fix 1==mpeg1.0 0==mpeg2.0 layer_index: 2, // fix 0 reserved protection_bit: 1, // fix bitrate_index: 4, // 15 forbidden sampling_frequency: 2,// fix 3 reserved padding_bit: 1, // private_bit: 1, // mode: 2, // fix mode_extension: 2, // (not fix!) copyright: 1, // fix original: 1, // fix emphasis: 2; // fix 2 reserved #else unsigned int emphasis: 2, // fix 2 reserved original: 1, // fix copyright: 1, // fix mode_extension: 2, // (not fix!) mode: 2, // fix private_bit: 1, // padding_bit: 1, // sampling_frequency: 2,// fix 3 reserved bitrate_index: 4, // 15 forbidden protection_bit: 1, // fix layer_index: 2, // fix 0 reserved ID: 1, // fix 1==mpeg1.0 0==mpeg2.0 syncword: 12; // fix must 0xfff #endif // ifdef BIGENDIAN bool isValid() const { if(syncword!=0xfff) return false; if(ID==1) { // mpeg 1.0 if((layer_index!=0) && (bitrate_index!=15) && (sampling_frequency!=3) && (emphasis!=2)) return true; return false; } else { // mpeg 2.0 if((layer_index!=0) && (bitrate_index!=15) && (sampling_frequency!=3) && (emphasis!=2)) return true; return false; } } bool sameConstant(Header h) const { const unsigned int *p1 = (unsigned int*)this; const unsigned int *p2 = (unsigned int*)(&h); if(*p1 == *p2) return true; if((syncword ==h.syncword ) && (ID ==h.ID ) && (layer_index ==h.layer_index ) && (protection_bit ==h.protection_bit ) && // (bitrate_index ==h.bitrate_index ) && (sampling_frequency==h.sampling_frequency) && (mode ==h.mode ) && // (mode_extension ==h.mode_extension ) && (copyright ==h.copyright ) && (original ==h.original ) && (emphasis ==h.emphasis ) && 1) return true; else return false; } int bitrate() const { if(ID) return bitrate1_tab[bitrate_index][layer()-1]; else return bitrate2_tab[bitrate_index][layer()-1]; } int layer() const {return layer_tab[layer_index];} tstring print() const { tstring s; s.sprintf("(%03x,ID%d,l%d,prot%d,%2d,%4.1fkHz,pad%d,priv%d,mode%d,ext%d,copy%d,orig%d,emp%d)", syncword, ID, layer(), protection_bit, bitrate_index, samp_rate(), padding_bit, private_bit, mode, mode_extension, copyright, original, emphasis); return s; } double version() const { if(ID) return 1.0; else return 2.0; } enum {STEREO, JOINT_STEREO, DUAL_CHANNEL, SINGLE_CHANNEL}; const char *mode_str() const { switch(mode) { case STEREO: return "stereo"; case JOINT_STEREO: return "joint stereo"; case DUAL_CHANNEL: return "dual channel"; case SINGLE_CHANNEL: return "single chann"; } return 0; } const char *short_mode_str() const { switch(mode) { case STEREO: return "st"; case JOINT_STEREO: return "js"; case DUAL_CHANNEL: return "dc"; case SINGLE_CHANNEL: return "sc"; } return 0; } enum {emp_NONE, emp_50_15_MICROSECONDS, emp_RESERVED, emp_CCITT_J_17}; const char *emphasis_str() const { switch(emphasis) { case emp_NONE: return "no emph"; case emp_50_15_MICROSECONDS: return "50/15us"; case emp_RESERVED: return "reservd"; case emp_CCITT_J_17: return "C. J.17"; } return 0; } const char *short_emphasis_str() const { switch(emphasis) { case emp_NONE: return "n"; case emp_50_15_MICROSECONDS: return "5"; case emp_RESERVED: return "!"; case emp_CCITT_J_17: return "J"; } return 0; } double samp_rate() const { if(ID) return sampd1_tab[sampling_frequency]; else return sampd2_tab[sampling_frequency]; } int samp_int_rate() const { if(ID) return samp_1_tab[sampling_frequency]; else return samp_2_tab[sampling_frequency]; } // this should be not affected by endianess int get_int() const {return *((const int *)this);} }; // get header from pointer inline Header get_header(const unsigned char *p) { Header h; unsigned char *q = (unsigned char *)&h; #ifdef WORDS_BIGENDIAN q[0]=p[0]; q[1]=p[1]; q[2]=p[2]; q[3]=p[3]; #else q[0]=p[3]; q[1]=p[2]; q[2]=p[1]; q[3]=p[0]; #endif return h; } // set header to pointer inline void set_header(unsigned char *p, Header h) { unsigned char *q = (unsigned char *)&h; #ifdef WORDS_BIGENDIAN p[0]=q[0]; p[1]=q[1]; p[2]=q[2]; p[3]=q[3]; #else p[0]=q[3]; p[1]=q[2]; p[2]=q[1]; p[3]=q[0]; #endif } // set header from header // preserves padding bit and mode extension (under conditions), // among other things void set_header(Header &to, Header from) { if(to.mode!=from.mode) { to.mode = from.mode; to.mode_extension = from.mode_extension; } to.ID = from.ID; to.layer_index = from.layer_index; to.protection_bit = from.protection_bit; to.sampling_frequency = from.sampling_frequency; to.copyright = from.copyright; to.original = from.original; to.emphasis = from.emphasis; } // set crc to pointer inline bool set_crc_value(unsigned char *p, unsigned short c) { p[1] = (unsigned char) c; p[0] = (unsigned char) (c>>8); return true; } // return length of frame in bytes inline int frame_length(Header h) { if(h.version() == 1.0) { switch(h.layer()) { case 1: return (((12000*h.bitrate()) / h.samp_int_rate()) + h.padding_bit) * 4; default: return ((144000*h.bitrate()) / h.samp_int_rate()) + h.padding_bit; } } else { switch(h.layer()) { case 1: return (((6000*h.bitrate()) / h.samp_int_rate()) + h.padding_bit) * 4; default: return ((72000*h.bitrate()) / h.samp_int_rate()) + h.padding_bit; } } } // return duration of frame in ms inline double frame_duration(Header h) { return (((double)frame_length(h)*8) / h.bitrate()); } // return next pos of min_valid sequential valid and constant header // or -1 if not found inline int find_next_header(const unsigned char *p, int len, int min_valid) { int i; const unsigned char *q = p; const unsigned char *t; int rest, k, l; Header h, h2; for(i=0; i < len-3; i++, q++) { if(*q==255) { h = get_header(q); l = frame_length(h); if(h.isValid() && (l>=21)) { t = q + l; rest = len - i - l; for(k=1; (k < min_valid) && (rest >= 4); k++) { h2 = get_header(t); if(!h2.isValid()) break; if(!h2.sameConstant(h)) break; l = frame_length(h2); if(l < 21) break; t += l; rest -= l; } if(k == min_valid) return i; } } } return -1; // not found } // return pointer to beginning of nth frame from start (0 is start) or 0 if frame not found const unsigned char *skip_n_frames(const unsigned char *start, int len, int n) { while(1) { // skip invalid data int s = find_next_header(start, len, MIN_VALID); if(s < 0) return 0; start += s; len -= s; Header h = get_header(start); while(1) { if(len < 4) return 0; h = get_header(start); if(h.isValid()) { if(n <= 0) return start; int l = frame_length(h); start += l; len -= l; n--; } else { break; } } } } #ifdef __GNUC__ void fmes(const char *name, const char *format, ...) __attribute__ ((format(printf,2,3))); #endif void fmes(const char *name, const char *format, ...) { if(quiet) return; va_list ap; static tstring lastname; tstring pname = name; pname.replaceUnprintable(only_ascii); if(progress) putc('\r', stderr); if(strcmp(format, "\n") == 0) { printf("%s%s%s:\n", cfil, pname.c_str(), cnor); return; } if(single_line) { printf("%s%s%s: ", cfil, pname.c_str(), cnor); } else { if(name != lastname) { lastname = name; tstring s = pname.shortFilename(columns-1); printf("%s%s%s:\n", cfil, s.c_str(), cnor); } } va_start(ap, format); vprintf(format, ap); va_end(ap); } // returns true on error bool error_check(const char *name, const unsigned char *stream, int len, CRC16& crc, bool fix_headers, bool fix_crc) { int errors = 0; const unsigned char *p = stream; int start = find_next_header(p, len, MIN_VALID); int rest = len; int frame=0; double time=0.0; int l=0,s; Tagv1* tag; if(start<0) { if(!ign_noamp) { fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor); errors++; } } else { // check for junk at beginning if(start>0) { int pos=0; if(!ign_start) { fmes(name, "%s%d%s %sbyte%s of junk before first frame header%s\n", cval, start, cnor, cerror, (start>1)?"s":"", cnor); errors++; // check for possible id3 tags within the junk while(start-pos >= 128) { int offset=Tagv1::find_next_tag(p+pos, start-pos); if(offset!=-1) { pos+=offset; tag=new Tagv1(p+pos); fmes(name, "in leading junk: %spossible %s id3 tag v%u.%u%s at %s0x%08x%s\n", cerror, (tag->isValidSpecs()?"valid":"invalid"), (tag->version())>>8, (tag->version())&0xff, cnor, cval, pos, cnor); delete tag; pos+=3; } else { pos=start; } } } } // check for TAG trailers // Note that we emit a warning if we found more than one tag, even // if ign_tag is set, unless ign_end is also set. tag=new Tagv1(p+rest-128); int tag_counter=0; while((rest>=128)&&tag->isValid()) { tag_counter++; if((!ign_tag)||((tag_counter>1)&&!ign_end)) { fmes(name, "%s%s%s id3 tag trailer v%u.%u found%s\n", cerror, (tag_counter>1?"another ":""), (tag->isValidSpecs()?"valid":"invalid"), (tag->version())>>8, (tag->version())&0xff, cnor); errors++; } rest-=128; tag->setTarget(p+rest-128); } delete tag; // check whole file rest -= start; p += start; Header head = get_header(p); while(rest>=4) { Header h = get_header(p); if(progress) { if((frame%1000)==0) { putc('.', stderr); fflush(stderr); } } if(!(h.isValid()&&(frame_length(h)>=21))) { // invalid header // search for next valid header if(!l) { printf("ERROR! Invalid header with no previous frame. Needs debuging.\n"); } // search within previous frame p-=(l-4); start-=(l-4); rest+=(l-4); // first look for any isolated frame with the same header s = find_next_header(p, rest, 1); if(s<0) { // error: junk at eof p+=(l-4); start+=(l-4); rest-=(l-4); break; } // else look for a regular stream h = get_header(p+s); if(!head.sameConstant(h)) s = find_next_header(p, rest, MIN_VALID); if(s<0) { // error: junk at eof p+=(l-4); start+=(l-4); rest-=(l-4); break; } if(!ign_sync) { if(s1)?"s":""); errors++; } else { fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %ssync error (frame too long)%s at %s0x%08x%s, skipping %s%d%s byte%s at %s0x%08x%s\n", cval, frame - 1, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, start - 4, cnor, cval, s-l+4, cnor, (s-l+4>1)?"s":"", cval, start - 4 + l, cnor); errors++; } } // try to fix header including sync information if(fix_headers && (s>l-4)) { unsigned int old_padding_bit = head.padding_bit; head.padding_bit = 0; if(s-l+4 == frame_length(head)) { fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sfixing header (including sync)%s\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor); set_header((unsigned char *)(p+l-4), head); frame++; // we just created a new frame time+=frame_duration(head); l = s-l+4; } else { head.padding_bit = 1; if(s-l+4 == frame_length(head)) { fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sfixing header (including sync)%s\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor); set_header((unsigned char *)(p+l-4), head); frame++; // we just created a new frame time+=frame_duration(head); l = s-l+4; } else { // prevent possible side effect head.padding_bit = old_padding_bit; } } } // position on next frame p += s; rest -= s; start += s; } else { // valid header // check for constant parameters if(!head.sameConstant(h)) { if(!ign_const) { fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sconstant parameter switching%s at %s0x%08x%s (%s0x%08x%s -> %s0x%08x%s)\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, start, cnor, cval, head.get_int()&CONST_MASK, cnor, cval, h.get_int()&CONST_MASK, cnor); if(h.ID!=head.ID) printf("frame %s%5d%s/%s%2u:%02u%s: %sMPEG version switching%s (MPEG %s%1.1f%s -> MPEG %s%1.1f%s)\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, head.version(), cnor, cval, h.version(), cnor); if(h.layer_index!=head.layer_index) printf("frame %s%5d%s/%s%2u:%02u%s: %sMPEG layer switching%s (layer %s%1d%s -> layer %s%1d%s)\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, head.layer(), cnor, cval, h.layer(), cnor); if(h.samp_rate()!=head.samp_rate()) printf("frame %s%5d%s/%s%2u:%02u%s: %ssampling frequency switching%s (%s%f%skHz -> %s%f%skHz)\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, head.samp_rate(), cnor, cval, h.samp_rate(), cnor); if(h.mode!=head.mode) printf("frame %s%5d%s/%s%2u:%02u%s: %smode switching%s (%s%s%s -> %s%s%s)\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, head.mode_str(), cnor, cval, h.mode_str(), cnor); if(h.protection_bit!=head.protection_bit) printf("frame %s%5d%s/%s%2u:%02u%s: %sprotection bit switching%s (%s%s%s -> %s%s%s)\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, head.protection_bit?"no crc":"crc", cnor, cval, h.protection_bit?"no crc":"crc", cnor); if(h.copyright!=head.copyright) printf("frame %s%5d%s/%s%2u:%02u%s: %scopyright bit switching%s (%s%s%s -> %s%s%s)\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, head.copyright?"copyright":"no copyright", cnor, cval, h.copyright?"copyright":"no copyright", cnor); if(h.original!=head.original) printf("frame %s%5d%s/%s%2u:%02u%s: %soriginal bit switching%s (%s%s%s -> %s%s%s)\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, head.original?"original":"not original", cnor, cval, h.original?"original":"not original", cnor); errors++; } if(fix_headers) { fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sfixing header%s\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor); // fix only what should be set_header(h, head); set_header((unsigned char *)p, h); } } if(head.bitrate_index != h.bitrate_index) { if(!ign_bit) { fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sbitrate switching%s (%s%d%s -> %s%d%s)\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, head.bitrate(), cnor, cval, h.bitrate(), cnor); errors++; if(fix_headers) { fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sfixing header%s\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor); // fix only what should be h.bitrate_index=head.bitrate_index; set_header((unsigned char *)p, h); } } } head = h; // check crc16 if((!ign_crc)&&(h.protection_bit==0)&&(rest>=32+6)) { // reset crc checker crc.reset(0xffff); // get length of side info s = 0; if(h.version()==1.0) { // mpeg 1.0 switch(h.layer()) { case 3: // layer 3 if(h.mode==Header::SINGLE_CHANNEL) s = 17; else s = 32; break; case 1: // layer 1 switch(h.mode) { case Header::SINGLE_CHANNEL: s = 16; break; case Header::DUAL_CHANNEL: s = 32; break; case Header::STEREO: s = 32; break; case Header::JOINT_STEREO: s = 18+h.mode_extension*2; break; } break; default: s = 0; // mpeg 1.0 layer 2 not yet supported break; } } else { // mpeg 2.0 or 2.5 if(h.layer()==3) { // layer 3 if(h.mode==Header::SINGLE_CHANNEL) s = 9; else s = 17; } else { s = 0; // mpeg 2.0 or 2.5 layer 1 and 2 not yet supported } } if(s) { // calc crc crc.add(p[2]); crc.add(p[3]); for(int i=0; i < s; i++) crc.add(p[i+6]); // check crc unsigned short c = p[5] | ((unsigned short)(p[4])<<8); int fixed_crc = 0; if(c != crc.crc()) { if(fix_crc) { fixed_crc = set_crc_value((unsigned char *) &(p[4]), crc.crc()); } fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %scrc error%s (%s0x%04x%s!=%s0x%04x%s)%s\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, c, cnor, cval, crc.crc(), cnor, fixed_crc ? " fixed" : ""); errors++; } // printf("frame=%d, pos=%d, s=%d, c=%04x crc.crc()=%04x\n", frame, start, s, c, crc.crc()); } } // skip to next frame l = frame_length(h); p += l; rest -= l; start += l; frame++; time+=frame_duration(h); } // maximum number of error reached? if(max_errors && (errors >= max_errors)) { fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %smaximum number of errors exceeded%s\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor); rest = 0; break; } } // check for truncated file if(rest < 0) { if(!ign_trunc) { fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %sfile truncated%s, %s%d%s byte%s missing for last frame\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cerror, cnor, cval, -rest, cnor, (-rest)>1?"s":""); errors++; } } // check for trailing junk if(rest > 0) { if(!ign_end) { fmes(name, "frame %s%5d%s/%s%2u:%02u%s: %s%d%s %sbyte%s of junk after last frame%s at %s0x%08x%s\n", cval, frame, cnor, cval, (unsigned int)(time/1000)/60, (unsigned int)(time/1000)%60, cnor, cval, rest, cnor, cerror, (rest>1)?"s":"", cnor, cval, start, cnor); errors++; // check for possible id3 tags within the junk while(rest >= 128) { int offset=Tagv1::find_next_tag(p, rest); if(offset!=-1) { start+=offset; p+=offset; tag=new Tagv1(p); fmes(name, "in trailing junk: %spossible %s id3 tag v%u.%u%s at %s0x%08x%s\n", cerror, (tag->isValidSpecs()?"valid":"invalid"), (tag->version())>>8, (tag->version())&0xff, cnor, cval, start, cnor); delete tag; p+=3; start+=3; rest-=(offset+3); } else { start+=rest; p+=rest; rest=0; } } } } } if(progress) { fputs("\r \r", stderr); fflush(stderr); } if((errors == 0) && show_valid_files) fmes(name, "%svalid audio mpeg stream%s\n", cok, cnor); return errors > 0; } // returns true on anomaly bool anomaly_check(const char *name, const unsigned char *p, int len, bool err_check, int& err) { bool had_ano = false; int start = find_next_header(p, len, MIN_VALID); if(start>=0) { Header h = get_header(p+start); if(!ano_any_ver) { if(h.version()!=1.0) { fmes(name, "%sanomaly%s: audio mpeg version %s%3.1f%s stream\n", cano, cnor, cval, h.version(), cnor); had_ano = true; } } if(!ano_any_layer) { if(h.layer()!=3) { fmes(name, "%sanomaly%s: audio mpeg %slayer %d%s stream\n", cano, cnor, cval, h.layer(), cnor); had_ano = true; } } if(!ano_any_rate) { if(h.samp_rate()!=44.1) { fmes(name, "%sanomaly%s: sampling rate %s%4.1fkHz%s\n", cano, cnor, cval, h.samp_rate(), cnor); had_ano = true; } } if(!ano_any_bit) { if(h.bitrate()!=128) { fmes(name, "%sanomaly%s: bitrate %s%3dkbit/s%s\n", cano, cnor, cval, h.bitrate(), cnor); had_ano = true; } } if(!ano_any_mode) { if(h.mode!=Header::JOINT_STEREO) { fmes(name, "%sanomaly%s: mode %s%s%s\n", cano, cnor, cval, h.mode_str(), cnor); had_ano = true; } } if(!ano_any_crc) { if(h.protection_bit==1) { fmes(name, "%sanomaly%s: %sno crc%s\n", cano, cnor, cval, cnor); had_ano = true; } } if(!ano_any_emp) { if(h.emphasis!=Header::emp_NONE) { fmes(name, "%sanomaly%s: emphasis %s%s%s\n", cano, cnor, cval, h.emphasis_str(), cnor); had_ano = true; } } } else { if((!err_check)&&(!ign_noamp)) { fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor); ++err; } } return had_ano; } // returns the stream duration in ms // also returns minimum, maximum and average bitrates if told to unsigned int stream_duration(const unsigned char *p, int len, unsigned short int *minbr, unsigned short int *maxbr, unsigned short int *avgbr) { int next = find_next_header(p, len, MIN_VALID); int rest = len - next; double duration = 0.0; unsigned short int min = 2048, max = 0; unsigned long int bytes = 0; if(next<0) return 0; while(rest>=4) { Header h=get_header(p+next); if(!h.isValid()) { int old = next; next = find_next_header(p+old, rest, MIN_VALID); if(next<0) break; rest -= next; next += old; } else { int l=frame_length(h); int br=h.bitrate(); if(br>max) max=br; if(br) int start = find_next_header(p, len, MIN_VALID); int rest = len; int frame=0; int l,s; Tagv1 *tag = 0; bool have_a_tag = false; if(start<0) { if(!ign_noamp) { fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor); err++; } return false; } else { // check for TAG trailer if(rest >= 128) { tag = new Tagv1(p + rest - 128); if(tag->isValid()) { if(progress) putc('\r', stderr); if(tag->store()) { fmes(name, "%scut-junk-end: %s id3 tag trailer v%u.%u found, protecting it%s\n", cok, (tag->isValidSpecs()?"valid":"invalid"), (tag->version())>>8, (tag->version())&0xff, cnor); have_a_tag = true; } else { fmes(name, "%scut-junk-end: %s id3 tag trailer v%u.%u found, could not protect it, sorry%s\n", cok, (tag->isValidSpecs()?"valid":"invalid"), (tag->version())>>8, (tag->version())&0xff, cnor); } } } // check whole file rest -= start; p += start; Header head = get_header(p); l = frame_length(head); p += l; rest -= l; start += l; while(rest>=4) { Header h = get_header(p); frame++; if(progress) { if((frame%1000)==0) { putc('.', stderr); fflush(stderr); } } if(!(h.isValid()&&(frame_length(h)>=21))) { // invalid header // search for next valid header s = find_next_header(p, rest, MIN_VALID); if(s<0) break; // error: junk at eof // skip s invalid bytes p += s; rest -= s; start += s; } else { // valid header head = h; // skip to next frame l = frame_length(h); p += l; rest -= l; start += l; } } // in case we had found a tag if((rest >= 128) && have_a_tag) { rest-=128; } // remove incomplete last frames //if((rest < 0) && remove_truncated_last_frame) //{ // rest += l; //} // remove trailing junk if(rest > 0) { fmes(name, "%scut-junk-end: removing last %s%d%s byte%s, %sretrying%s\n", cok, cval, rest, cok, (rest>1)?"s":"", dummy?"not (due to dummy) ":"", cnor); if(!dummy) { // rewrite tag if(have_a_tag) { if(tag->restore((unsigned char*)p)) { fmes(name, "%scut-junk-end: tag successfully restored%s\n", cok, cnor); } else { fmes(name, "%scut-junk-end: unable to restore tag, sorry%s\n", cok, cnor); } delete tag; } // unmap file if(munmap((char*)free_p, len)) { perror("munmap"); userError("can't unmap file '%s'!\n", name); } // truncate file and close len-=rest; #ifndef __STRICT_ANSI__ if(ftruncate(fd, len) < 0) { perror("ftruncate"); } #else userError("cannot truncate file since this executable was compiled with __STRICT_ANSI__ defined!\n"); #endif close(fd); } return true; } else { fmes(name, "%scut-junk-end: no junk found%s\n", cok, cnor); return false; } } } // return true if trailing tag was found and cut bool cut_tag_end(const char *name, const unsigned char *p, int len, int fd, int& err) { // this is basically the cut_junk_end routine that only looks for a 128 bytes trailing tag // (implemented by Jean Delvare ) int start = find_next_header(p, len, MIN_VALID); Tagv1* tag; if(start<0) { if(!ign_noamp) { fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor); err++; } return false; } else { // check for TAG trailer if(len>=128) { tag=new Tagv1(p+len-128); if(tag->isValid()) { if(progress) putc('\r', stderr); fmes(name, "%scut-tag-end: %s id3 tag trailer v%u.%u found and removed, %sretrying%s\n", cok, (tag->isValidSpecs()?"valid":"invalid"), (tag->version())>>8, (tag->version())&0xff, dummy?"not (due to dummy) ":"", cnor); if(!dummy) { // unmap file if(munmap((char*)p, len)) { perror("munmap"); userError("can't unmap file '%s'!\n", name); } // truncate file and close len-=128; #ifndef __STRICT_ANSI__ if(ftruncate(fd, len) < 0) { perror("ftruncate"); } #else userError("cannot truncate file since this executable was compiled with __STRICT_ANSI__ defined!\n"); #endif close(fd); } delete tag; return true; } delete tag; } fmes(name, "%scut-tag-end: no tag found%s\n", cok, cnor); return false; } } // check for ID3 v1.x tag bool checkForID3V1(const unsigned char *p, size_t len) { if(len < 128) return false; return memcmp(p + len - 128, "TAG", 3) == 0; } // check for ID3 v2.x tag bool checkForID3V2(const unsigned char *p, size_t len) { if(len < 10) return false; return (memcmp(p + len - 10, "3DI", 3) == 0) || (memcmp(p, "ID3", 3) == 0); } // check for plausible string bool isValidStr(const unsigned char *p, size_t len) { for(size_t i = 0; (i < len) && (i < 30); i++) { if(p[i] == 0) return i > 2; if(!isprint(p[i])) return false; } return true; } // split artist and title void splitArtistTitle(const tstring& title, tstring &artistOut, tstring &titleOut) { tstring origtitle = title; const char *start = title.c_str(); int patlen = 3; const char *p = strstr(start, " - "); if(p == 0) { patlen = 1; p = strstr(start, "-"); } if(p) { size_t len = p - start; artistOut = title.substr(0, len); titleOut = title.substr(len + patlen); if((strcasecmp(titleOut.substr(0, 3).c_str(), "Vol") == 0) || (strcasecmp(titleOut.substr(0, 3).c_str(), "CD1") == 0) || (strcasecmp(titleOut.substr(0, 3).c_str(), "CD3") == 0) || (strcasecmp(titleOut.substr(0, 3).c_str(), "CD4") == 0) || (strcasecmp(titleOut.substr(0, 3).c_str(), "CD2") == 0)) { titleOut = origtitle; artistOut = ""; } if(artistOut == "Various") artistOut = ""; if(artistOut == "Sampler") artistOut = ""; } else { artistOut = ""; titleOut = origtitle; } } // check for possible tags more sloppy bool checkForTagsSloppy(const unsigned char *p, size_t len) { if(len < 10) return false; size_t max = 1024*1024*1024; if(max > len - 3) max = len - 3; for(size_t i = 0; i <= max; i++) { if( ((memcmp(p + i, "TAG", 3) == 0) && (isValidStr(p + i + 3, len - i - 3))) || ( ( (memcmp(p + i, "ID3", 3) == 0) || (memcmp(p + i, "3DI", 3) == 0) ) && (p[i + 3] < 10) && (p[i + 4] < 10) && ((p[i + 5] & 0xf) == 0) && (p[i + 6] < 128) && (p[i + 7] < 128) && (p[i + 8] < 128) && (p[i + 9] < 128))) { printf("%swarning: possible %c%c%c tag found at offset %d (%d from end) (%02x %02x %02x %02x %02x %02x %02x)%s\n", cano, p[i+0], p[i+1], p[i+2], int(i), int(len - 3 - i), p[i+3]&255, p[i+4]&255, p[i+5]&255, p[i+6]&255, p[i+7]&255, p[i+8]&255, p[i+9]&255, cnor); return true; } } return false; } // capitalize string void capitalize(tstring &str) { bool lastAlpha = false; for(size_t i = 0; i < str.length(); i++) { if((!lastAlpha) && islower(str[i])) str[i] = toupper(str[i]); lastAlpha = isalpha(str[i]); } } // main int main(int argc, char *argv[]) { // get parameters TAppConfig ac(options, "options", argc, argv, 0, 0, VERSION); // get the terminal width if available struct winsize win; if(ioctl(1, TIOCGWINSZ, &win) == 0) columns = win.ws_col; const char* scolumns=getenv("COLUMNS"); if(scolumns) columns=atoi(scolumns)-1; if(columns<30) columns=79; // setup options quiet = ac("quiet"); dummy = ac("dummy"); progress = ac("progress"); max_errors = ac.getInt("max-errors"); show_valid_files = ac("show-valid"); bool nommap = ac("no-mmap"); int opt=0; // alt mode if(ac("error-check")) opt=1; if(ac("fix-headers")) opt=1; if(ac("fix-crc")) opt=1; if(ac("add-tag")) opt=1; if(ac("anomaly-check")) opt=1; if(ac("cut-junk-start")) opt=1; if(ac("cut-junk-end")) opt=1; if(ac("cut-tag-end")) opt=1; if(!ac.getString("edit-frame-b").empty()) opt=1; // main mode if(ac("dump-header")) opt++; if(ac("dump-tag")) opt++; if(ac("list")) opt++; if(ac("compact-list")) opt++; if(ac("raw-list")) opt++; if(ac("print-files")) opt=1; // check for mode if(opt==0) userError("you must specify the mode of operation! (try --help for more info)\n"); if(opt>1) userError("incompatible modes specified! (try --help for more info)\n"); // color if(!ac("color")) cval = cnor = cano = cerror = cfil = cok = ""; if(ac("alt-color")) { cval = c_val; cano = c_ano; cerror = c_err; cfil = c_fil; cnor = c_nor; cok = c_ok; } tstring rawsepstr = ac.getString("raw-elem-sep"); char rawsep = strtol(rawsepstr.c_str(), 0, 0); if(!rawsepstr.empty()) if(!isdigit(rawsepstr[0])) rawsep = rawsepstr[0]; tstring rawlinesepstr = ac.getString("raw-line-sep"); char rawlinesep = strtol(rawlinesepstr.c_str(), 0, 0); if(!rawlinesepstr.c_str()) if(!isdigit(rawlinesepstr[0])) rawsep = rawlinesepstr[0]; if(ac("raw-list")) { quiet=true; progress=false; } bool edit_frame_byte = !ac.getString("edit-frame-b").empty(); int efb_value = 0; int efb_offset = 0; int efb_frame = 0; if(edit_frame_byte) { tvector a = split(ac.getString("edit-frame-b"), ","); if((a.size() != 3) || !a[0].toInt(efb_frame) || !a[1].toInt(efb_offset) || !a[2].toInt(efb_value)) userError("format of parameter for --edit-frame-b is 'frame,offset,byteval'!\n"); } bool recursive = ac("recursive"); #ifdef __CYGWIN__ bool cross_filesystems = true; #else bool cross_filesystems = !ac("xdev"); #endif tstring extensions = ac.getString("accept"); tstring reject_extensions = ac.getString("reject"); if(ac("only-mp3")) { if(extensions.empty()) extensions = ONLY_MP3; else extensions += "," ONLY_MP3; } single_line = ac("single-line"); only_ascii = ac("ascii-only"); // check params if(ac.numParam()==0) userError("need at least one file or directory! (try --help for more info)\n"); // setup ignores/anys ano_any_crc = ac("any-crc"); ano_any_mode = ac("any-mode"); ano_any_layer = ac("any-layer"); ano_any_bit = ac("any-bitrate"); ano_any_emp = ac("any-emphasis"); ano_any_rate = ac("any-sampling"); ano_any_ver = ac("any-version"); ign_crc = ac("ign-crc-error"); ign_start = ac("ign-junk-start"); ign_end = ac("ign-junk-end"); ign_tag = ac("ign-tag128"); ign_bit = ac("ign-bitrate-sw"); ign_const = ac("ign-constant-sw"); ign_trunc = ac("ign-truncated"); ign_noamp = ac("ign-non-ampeg"); ign_sync = ac("ign-resync"); // get file list tvector filelist; // from command line (perhaps recurse directories) for(size_t i = 0; i < ac.numParam(); i++) { if(recursive) { try { TFile f(ac.param(i)); if(f.isdir()) { TSubTreeContext context(cross_filesystems); TDir d(f, context); filelist += findFilesRecursive(d); continue; } } catch(...) {} } filelist.push_back(ac.param(i)); } // read filenames from text file tstring filelistfile = ac.getString("filelist"); if(!filelistfile.empty()) { try { filelist += loadTextFile(filelistfile.c_str()); } catch(...) { userError("cannot open file '%s' for reading!\n", filelistfile.c_str()); } } // filter filenames if(!extensions.empty()) filelist = filterExtensions(filelist, split(extensions, ",;:")); if(!reject_extensions.empty()) filelist = filterExtensions(filelist, split(reject_extensions, ",;:"), true); // print filelist if(ac("print-files")) { for(size_t i = 0; i < filelist.size(); i++) printf("%s\n", filelist[i].c_str()); exit(0); } // check all files int err=0; int checked=0; int num_ano=0; int num_tagsadded = 0; CRC16 crc(CRC16::CRC_16); FILE *log = NULL; if(!ac.getString("log-file").empty()) { log = fopen(ac.getString("log-file").c_str(), "a"); if(log==NULL) userError("can't open logfile '%s'!\n", ac.getString("log-file").c_str()); } for(size_t i = 0; i < filelist.size(); i++) { const char *name = filelist[i].c_str(); // ignore all files starting with ._ which are apple metafiles { tstring t = name; t.extractFilename(); if((t[0] == '.') && (t[1] == '_')) continue; } // check for file struct stat buf; if(stat(name, &buf)) { fmes(name, "%scan't stat file (dangling symbolic link?)%s\n", cerror, cnor); continue; } if(S_ISDIR(buf.st_mode)) { fmes(name, "%signoring directory%s\n", cerror, cnor); continue; } if(!S_ISREG(buf.st_mode)) { fmes(name, "%signoring non regular file%s\n", cerror, cnor); continue; } off_t len = buf.st_size; // open file int flags = O_RDONLY; int prot = PROT_READ; if(!dummy) { if(ac("fix-headers")||ac("cut-junk-start")||ac("fix-crc")||ac("cut-junk-end")||ac("cut-tag-end")||edit_frame_byte) { flags = O_RDWR; prot |= PROT_WRITE; if(nommap) userError("option --no-mmap does not yet support options which may modify a file (e.g --fix-crc)!\n"); } } flags |= O_BINARY; int fd = open(name, flags); if(fd==-1) { perror("open"); userError("can't open file '%s' for reading!\n", name); } // mmap or read file const unsigned char *p; const unsigned char *free_p = 0; if(nommap) { // read file free_p = p = new unsigned char[len]; if(read(fd, (void*)p, len) != len) { perror("read"); userError("error while reading file '%s'!\n", name); } } else { // mmap file if(len) { free_p = p = (const unsigned char *) mmap(0, len, prot, MAP_SHARED, fd, 0); } else { p = NULL; } if(p==(const unsigned char *)MAP_FAILED) { perror("mmap"); userError("can't map file '%s'!\n", name); } } // edit single byte of a frame if(edit_frame_byte) { if(progress) { tstring s = tstring(name).shortFilename(79); fprintf(stderr, "%-79.79s\r", s.c_str()); fflush(stderr); } unsigned char *pp = const_cast(skip_n_frames(free_p, len, efb_frame)); if(pp) { if(!dummy) pp[efb_offset] = efb_value; } else { fmes(name, "%sframe %s%d%s not found%s\n", cerror, cval, efb_frame, cerror, cnor); err++; } } // list if(ac("list")||ac("compact-list")||ac("raw-list")) { // speed up list of very large files (like *.wav) int maxl = LIST_MAX_HEADER_SEARCH; int start = find_next_header(p, len=82?" ":""), cfil, name, cnor); } else { fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor); } err++; } } else { Header h = get_header(p+start); unsigned short int minbr = 0, maxbr = 0, avgbr; unsigned int l_min = (ign_bit?stream_duration(p, len, &minbr, &maxbr, &avgbr):len/(h.bitrate()/8)); unsigned int l_mil = l_min%1000; l_min/=1000; unsigned int l_sec = l_min%60; l_min/=60; tstring l_str; if(l_min >= 60) l_str.sprintf("%2u:%02u", l_min/60, l_min%60); else l_str.sprintf(" %2u", l_min); Tagv1 *tag=new Tagv1(p+len-128); unsigned short int tag_version=0; if(tag->isValid()) tag_version=tag->version(); if(ac("list")) { unsigned int xwidth = 0; tstring n = single_line?tstring(name):tstring(name).shortFilename(columns-1); fmes(name, "mpeg %s%3.1f%s layer %s%d%s %s%2.1f%skHz %s%3d%skbps", h.version()==1.0?cval:cano, h.version(), cnor, h.layer()==3?cval:cano, h.layer(), cnor, h.samp_rate()==44.1?cval:cano, h.samp_rate(), cnor, (h.bitrate()==128&&minbr==maxbr)?cval:cano, minbr!=maxbr?avgbr:h.bitrate(), cnor); if(ign_bit && columns>=83) { printf(" %s%s%s", minbr!=maxbr?cano:cval,minbr!=maxbr?"VBR":"CBR",cnor); xwidth+=4; } printf(" %s%-12.12s%s %s%-7.7s%s %s%s%s %s%s%s %s%s%s %s%s:%02u.%02u%s", h.mode==Header::JOINT_STEREO?cval:cano, h.mode_str(), cnor, h.emphasis==Header::emp_NONE?cval:cano, h.emphasis_str(), cnor, h.protection_bit?cano:cval, h.protection_bit?"---":"crc", cnor, h.original?cval:cano, h.original?"orig":"----", cnor, cval, h.copyright?"copy":"----", cnor, cval, l_str.c_str(), l_sec, l_mil/10, cnor); if(columns>=87+xwidth) { if(tag_version) printf(" id3 %s%1u.%1u%s", cval, tag_version>>8, tag_version&0xff,cnor); // xwidth+=8; } printf("\n"); } else if(ac("compact-list")) { unsigned int xwidth = 0; printf("%s%c%s%s%d%s %s%2.0f%s %s%3d%s", h.version()==1.0?cval:cano, h.version()==1.0?'l':'L', cnor, h.layer()==3?cval:cano, h.layer(), cnor, h.samp_rate()==44.1?cval:cano, h.samp_rate(), cnor, (h.bitrate()==128&&minbr==maxbr)?cval:cano, minbr!=maxbr?avgbr:h.bitrate(), cnor); if(ign_bit && columns>=80) { printf("%s%c%s", minbr==maxbr?cval:cano, minbr==maxbr?' ':'V', cnor); xwidth+=1; } printf(" %s%s%s %s%s%s %s%s%s%s%s%s%s%s%s", h.mode==Header::JOINT_STEREO?cval:cano, h.short_mode_str(), cnor, h.emphasis==Header::emp_NONE?cval:cano, h.short_emphasis_str(), cnor, h.protection_bit?cano:cval, h.protection_bit?"-":"C", cnor, h.original?cval:cano, h.original?"O":"-", cnor, cval, h.copyright?"Y":"-", cnor); if(columns>=81+xwidth) { if(tag_version) printf(" %s%1u%s",cval,tag_version>>8,cnor); else printf(" %s-%s",cval,cnor); xwidth+=2; } tstring n = tstring(name).shortFilename(columns-(26+xwidth)); n.replaceUnprintable(only_ascii); printf(" %s%3u:%02u%s %s%s%s\n", cval, l_min, l_sec, cnor, cfil, n.c_str(), cnor); } else if(ac("raw-list")) { printf("valid_stream%c%.1f%c%d%c%.1f%c%d%c%s%c%s%c%s%c%s%c%s%c%s%c%u%c%u%c%u%c%s%c%c", rawsep, h.version(), rawsep, h.layer(), rawsep, h.samp_rate(), rawsep, minbr!=maxbr?avgbr:h.bitrate(), rawsep, ign_bit?(minbr!=maxbr?"VBR":"CBR"):"?", rawsep, h.mode_str(), rawsep, h.emphasis_str(), rawsep, h.protection_bit?"---":"crc", rawsep, h.original?"orig":"copy", rawsep, h.copyright?"cprgt":"-----", rawsep, l_min, rawsep, l_sec, rawsep, l_mil, rawsep, name, rawsep, rawlinesep); } delete tag; } } // cut-junk-start if(ac("cut-junk-start")) { int start = find_next_header(p, len, MIN_VALID); if(start<0) { fmes(name, "%s%s%s\n", cerror, (len?"not an audio mpeg stream":"empty file"), cnor); err++; } else if(start==0) { fmes(name, "%scut-junk-start: no junk found%s\n", cok, cnor); } else { fmes(name, "%scut-junk-start: removing first %s%d%s byte%s, %sretrying%s\n", cok, cval, start, cok, (start>1)?"s":"", dummy?"not (due to dummy) ":"", cnor); if(!dummy) { // move start to begining and truncate the file memmove((char*)free_p, free_p + start, len - start); if(munmap((char*)free_p, len)) { perror("munmap"); userError("can't unmap file '%s'!\n", name); } len-=start; #ifndef __STRICT_ANSI__ if(ftruncate(fd, len) < 0) { perror("ftruncate"); } #else userError("cannot truncate file since this executable was compiled with __STRICT_ANSI__ defined!\n"); #endif close(fd); // retry this file --i; continue; } } } // cut-tag-end if(ac("cut-tag-end")) { // lots of side effects: perhaps unmaps and closes file if(cut_tag_end(name, p, len, fd, err)) { // retry this file if not dummy if(!dummy) { --i; continue; } } } // cut-junk-end if(ac("cut-junk-end")) { // lots of side effects: perhaps unmaps and closes file if(cut_junk_end(name, p, len, free_p, fd, err)) { // retry this file if not dummy if(!dummy) { --i; continue; } } } // check for errors if(ac("error-check") || ac("fix-headers") || ac("fix-crc")) { if(progress) { tstring s = tstring(name).shortFilename(79); fprintf(stderr, "%-79.79s\r", s.c_str()); fflush(stderr); } if(error_check(name, p, len, crc, ac("fix-headers"), ac("fix-crc"))) { if(log) fprintf(log, "%s\n", name); ++err; } } // check for anomalies if(ac("anomaly-check")) { if(progress) { tstring s = tstring(name).shortFilename(79); fprintf(stderr, "%-79.79s\r", s.c_str()); fflush(stderr); } if(anomaly_check(name, p, len, ac("error-check"), err)) ++num_ano; } // dump header if(ac("dump-header")) { fmes(name, "\n"); for(int k=0; k=21) { p+=l; p--; k+=l; k--; } } } } } // dump tag if(ac("dump-tag")) { unsigned int err_thisfile=0; fmes(name, "\n"); Tagv1 *tag=new Tagv1; for(int k=0; ksetTarget(p+k); if(tag->isValidGuess()) { printf(" Found at: %s0x%08x%s (%s%s%s)\n", cval, k, cnor, (k==len-128?cok:cerror), ((k==len-128)||!(++err_thisfile)?"end":"in the stream"), cnor); tag->fillFields(); printf(" Version: %s%u.%u%s\n", cval, tag->fields_version>>8, tag->fields_version&0xff, cnor); printf(" Conforms to specification: %s%s%s\n", (tag->isValidSpecs()&&!tag->fields_spacefilled?cok:cerror), (tag->isValidSpecs()||!(++err_thisfile)?(tag->fields_spacefilled?"space filled":"yes"):"no"), cnor); printf(" Title: \"%s%s%s\"\n", cval, tag->field_title, cnor); printf(" Artist: \"%s%s%s\"\n", cval, tag->field_artist, cnor); printf(" Album: \"%s%s%s\"\n", cval, tag->field_album, cnor); printf(" Year: \"%s%s%s\"\n", cval, tag->field_year, cnor); printf(" Comment: \"%s%s%s\"\n", cval, tag->field_comment, cnor); if(tag->field_genre==0xff) // not set printf(" Genre: not set\n"); else { printf(" Genre: %s%s%s\n", ((tag->field_genre>=Tagv1::genres_count)?cerror:cval), ((tag->field_genrefield_genre]):"unknown"), cnor); } if(tag->field_track) printf(" Track: %s%u%s\n", cval, tag->field_track, cnor); k+=2; } } delete tag; if(err_thisfile) ++err; } // --add-id3 bool addTag = false; if(ac("add-tag")) { if(progress) { tstring s = tstring(name).shortFilename(79); fprintf(stderr, "%-79.79s\r", s.c_str()); fflush(stderr); } // check for existing tag if(checkForID3V1(p, len)) { if(ac("verbose")) fmes(name, "id3 tag v1.x found, not adding anything\n"); } else if(checkForID3V2(p, len)) { if(ac("verbose")) fmes(name, "id3 tag v2.x found, not adding anything\n"); } #if 0 // this wqs just here to make sure we do not mis something else if(checkForTagsSloppy(p, len)) { fmes(name, "some tag found\n"); } #endif else { // no tag found: add tag addTag = true; } } if(nommap) { // free mem delete[] free_p; } else { // unmap file and close if((free_p!=NULL)&&munmap((char*)free_p, len)) { perror("munmap"); userError("can't unmap file '%s'!\n", name); } } // close file close(fd); // add tag? (see above) if(addTag) { // extract data from filename tstring title; // 30 tstring artist; // 30 tstring album; // 30 tstring comment;// 28 char track = 0; // 1 tstring fname = name; fname.translateChar('_', ' '); fname.searchReplace("cd 1", "cd1"); fname.searchReplace("cd 2", "cd2"); fname.searchReplace("cd 3", "cd3"); fname.searchReplace("cd 4", "cd4"); fname.searchReplace(" - ms/disc1/", "/cd1 - "); fname.searchReplace(" - ms/disc2/", "/cd2 - "); fname.searchReplace("/cd1/", " - cd1/"); fname.searchReplace("/cd2/", " - cd2/"); fname.searchReplace("/cd3/", " - cd3/"); fname.searchReplace("/cd4/", " - cd4/"); fname.searchReplace("zance - a decade of dance from ztt", "zance decade of dance from ztt"); fname.searchReplace("/captain future soundtrack - ", "/"); fname.searchReplace("-ms/", "/"); fname.collapseSpace(); title = fname; album = fname; comment = fname; title.extractFilename(); album.extractPath(); album.removeDirSlash(); album.extractFilename(); comment.extractPath(); comment.removeDirSlash(); comment.extractPath(); comment.removeDirSlash(); comment.extractFilename(); // title // remove album if(title != album) title.searchReplace(album, ""); // remove .mp3 if((title.length() >= 4) && strcasecmp(title.c_str() + title.length(), ".mp3")) title.truncate(title.length() - 4); // check for cd1 - if((strcasecmp(title.substr(0, 3).c_str(), "CD1") == 0) || (strcasecmp(title.substr(0, 3).c_str(), "CD1") == 0) || (strcasecmp(title.substr(0, 3).c_str(), "CD2") == 0)) { album += " " + title.substr(0,3); title = title.substr(3); // skip separator while(strchr(" -.", title.c_str()[0])) title = title.substr(1); } // check for AA-TT format if(isdigit(title[0]) && isdigit(title[1]) && isdigit(title[3]) && isdigit(title[4]) && (title[2] == '-')) { album += " cd" + title.substr(1,2); title = title.substr(3); } // check for 'audio ' tstring ltitle = title; ltitle.lower(); if(ltitle.substr(0, 5) == "audio") title = title.substr(5) + " " + title.substr(0, 5); else if(ltitle.substr(0, 10) == "track cd -") { title = title.substr(10); album += " track cd"; } else if(ltitle.substr(0, 8) == "mix cd -") { title = title.substr(8); album += " mix cd"; } else if(ltitle.substr(0, 6) == "track-") title = title.substr(6) + " " + title.substr(0, 6); else if(ltitle.substr(0, 5) == "track") title = title.substr(5) + " " + title.substr(0, 5); title.cropSpace(); title.collapseSpace(); // scan track size_t pos = 0; while(isdigit(title.c_str()[pos])) pos++; if((pos >= 1) && (pos <=2)) { int t = 0; title.substr(0, pos).toInt(t, 10); track = t; } else if(pos > 2) pos = 0; // skip separator while(strchr(" -.", title.c_str()[pos])) pos++; title = title.substr(pos); // split artist - title splitArtistTitle(title, artist, title); title.cropSpace(); // album tstring ar; // split artist - album if((album == "alben") || (album == "cdrom2") || (album == "cdrom") || (album == "misc1") || (album == "mp3") || (album == "ov") || (album == "all")) album = ""; splitArtistTitle(album, ar, album); if((!artist.empty()) && (!ar.empty())) { if(artist != ar) { title = artist + "-" + title; artist = ar; } } else if(artist.empty()) { artist = ar; ar = ""; } if(artist.empty()) { artist = album; album = ""; } album.cropSpace(); artist.cropSpace(); if(artist == "diverse") artist = ""; // comment comment.cropSpace(); if((comment == "alben") || (comment == "cdrom2") || (comment == "cdrom") || (comment == "mp3") || (comment == "ov") || (comment == "chr music") || (comment == "mp3 dl") || (comment == "diverse") || (comment.substr(0, 2) == "M0") || (comment.substr(0, 7) == "Unknown") || (comment.substr(0, 7) == "Various") || (comment == "all")) comment = ""; // capitalize strings capitalize(title); capitalize(artist); capitalize(album); capitalize(comment); // move rest of long strings into comment if(title.length() > 30) title.searchReplace(" - ", "-"); if(artist.length() > 30) artist.searchReplace(" - ", "-"); if(album.length() > 30) album.searchReplace(" - ", "-"); if(title.length() > 30) { comment = "T:" + title.substr(30); title.truncate(30); } if(artist.length() > 30) { comment = "R:" + artist.substr(30); artist.truncate(30); } if(album.length() > 30) { comment = "L:" + album.substr(30); album.truncate(30); } // print and check tag // fmes(name, "appending id3 tag v1.1:\ntitle= '%s%s%s'\nartist= '%s%s%s'\nalbum= '%s%s%s'\ncomment='%s%s%s'\ntrack= %s%d%s\n", // cval, title.c_str(), cnor, cval, artist.c_str(), cnor, cval, album.c_str(), cnor, cval, comment.c_str(), cnor, cval, track, cnor); fmes(name, "'%s%-30.30s%s' '%s%-30.30s%s' '%s%-30.30s%s' '%s%-28.28s%s' %s%d%s\n", cval, title.c_str(), cnor, cval, artist.c_str(), cnor, cval, album.c_str(), cnor, cval, comment.c_str(), cnor, cval, track, cnor); if(title.length() > 30) printf("%swarning%s: title > 30 chars\n", cerror, cnor); if(artist.length() > 30) printf("%swarning%s: artist > 30 chars\n", cerror, cnor); if(album.length() > 30) printf("%swarning%s: album > 30 chars\n", cerror, cnor); if(comment.length() > 28) printf("%swarning%s: comment > 28\n", cerror, cnor); if(track == 0) printf("%swarning%s: no track\n", cerror, cnor); // write tag char tag[128]; memset(tag, 0, 128); tag[0] = 'T'; tag[1] = 'A'; tag[2] = 'G'; strncpy(tag + 3, title.c_str(), 30); strncpy(tag + 3 + 30, artist.c_str(), 30); strncpy(tag + 3 + 60, album.c_str(), 30); strncpy(tag + 3 + 90 + 4, comment.c_str(), 28); tag[126] = track; tag[127] = -1; num_tagsadded++; #if 1 if(!dummy) { // append to file FILE *f = fopen(name, "ab"); if(f == 0) userError("can't open file '%s' for writing!\n", name); if(fwrite(tag, 1, 128, f) != 128) userError("error while writing to file '%s'\n", name); fclose(f); } #endif } ++checked; } // for all params // print final statistics if((filelist.size()>1)&&(!ac("raw-list")) && (!ac("no-summary")) && (!quiet)) { printf("-- \n" "%s%d%s file%s %s, %s%d%s erroneous file%s found\n", cval, checked, cnor, checked==1?"":"s", ac("list")?"listed":"checked", cval, err, cnor, err==1?"":"s"); if(num_ano) printf("(%s%d%s %sanomal%s%s found)\n", cval, num_ano, cnor, cano, num_ano==1?"y":"ies", cnor); if(num_tagsadded) printf("(%s%d%s tags added)\n", cval, num_tagsadded, cnor); } // end if(log!=NULL) fclose(log); return (err || num_ano)?1:0; } mp3check-0.8.7/tappconfig.cc000644 000766 000766 00000202764 11763233643 015035 0ustar00ovov000000 000000 /*GPL*START* * * tappconfig.cc - console application framework * * Copyright (C) 1998 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #include #include #include #include #include #include #include #include #include #include "tappconfig.h" // config: // global email of author: const char *TAPPCONFIG_EMAIL = "Johannes.Overmann@gmx.de"; // gpl message const char *TAPPCONFIG_GPL = "this program is distributed under the terms of the GNU General Public License version 2"; // usage: // ====== // // TAppConfig(const char *optionlist[], const char *listname, /* option list (see below) */ // int argc, char *av[], /* argc and argv from main or qt */ // const char *envstrname, /* environment variable for options (0=none) */ // const char *rcname, /* name for config file (0=none) (use "foorc" to get ~/.foorc and /etc/foorc */ // const string& version); /* application version */ // // the name of the application is taken from argv[0] // the whole magic is in optionlist: // optionlist is a list of c-strings (usually static string constants) // which is terminated by the string "EOL" (end of list, three chars), // each string may contain either an option item or a meta command // // meta commands: (order and position does not matter) // (all meta commands begin with a '#' char) // --------------------------------------------------- // #usage="string" /* set usage, printed on top of --help */ // #trailer="string" /* set trailer, printed at the bottom of --help */ // #onlycl /* this application does not use config files */ // #stopat-- /* stop option parsing after a doubledash (--) */ // #remove-- /* remove the first doubledash from parameterlist (--) */ // #ignore_negnum /* something like -20 or -.57 is not treated as option */ // #commonheadline /* headline on top of --help */ // // the usage and the trailer strings may contain the following variable names: // %n is replaced by the application name // %v is replaced by the application version // %e is replaved by the authors email address // %gpl is replaced by a one line version of the GNU General Public License // // // option item: // ------------ // an option item string contains a comma separated list of tags. some of // the tags take an argument (commas must be quoted by a backslash or // quotes). some tags are optional, others required. // each such line defines an option item (i.e. a command line options) // // required tags: // -------------- // name="name" /* the long option name and also the internal name of that option */ // type=type: /* type of the option */ // string expected parameter is a character string of arbitrary length, a fallback type for all kinds of parameters // int expected parameter is an integer // double expected parameter is a double precision floating point value // switch no parameter is expected: this is more like a switch (like -l in ls -l) // bool expected parameter is a truth value: 1/t/on/yes/true for 'true' and 0/f/off/no/false for 'false' (case insensitive) // help="help" /* the help message (for --help), the text will be formatted correctly at runtime by TAppConfig */ // /* so do not enter newlines unless you really want to end a paragraph */ // // optional tags: // -------------- // char=c /* the short option name (like -c) */ // param=PAR /* what to print after an option that takes an argument in --help (i.e. --width=NUM) */ // headline="foo options:" /* headline, printed before this option in --help */ // default=val /* default value, if option item is not specified by teh user. this should be of correct type */ // /* this default to empty string/0/0.0/false, not possible for type=switch */ // upper=val /* upper bound (inclusive) of numeric value (only int/double), default +INF */ // lower=val /* lower bound (inclusive) of numeric value (only int/double), default -INF */ // alias=aliaslist /* comma separated list (in quotes!) of aliases for this option (use - for single char alias) */ // musthave /* this option item is required (so it's not really an option, but a named parameter) */ // shouldhave /* this option prints a warning, if it is not specified (it is recommended) */ // string-mode-append /* for string type option items, multiple parameters are concatenated (see string-append-separator) */ // string-mode-once /* for string type option items, only one parameter is allowed (else an error message is printed) */ // string-append-separator=string /* this string separates multiple parameters for string-mode-append */ // onlycl /* this option may only be specified on the command line */ // save /* this option item and its value is saved in the config file if save() is called */ // optional_param /* the parameter for this option is optional, if it is missing the default is used */ // hide,hidden /* do not print option in --help, --hhelp will print it but --hhelp itself is hidden */ // onlyapp /* for internal use only: application private variable (like application version/name) */ // configopt /* for internal use only: application private option item (like --create-rc) */ // // example of a small option list: #if 0 const char *options[] = { "#usage='Usage: %n [OPTIONS and FILES] -- [FILES]\n\n" // no comma here! "this program does very strange things\n", "#trailer='\n%n version %v *** (C) 1997 by Johannes Overmann\ncomments, bugs and suggestions welcome: %e\n%gpl'", "#onlycl", // only command line options "#stopat--", // stop option scanning after a -- parameter // options "name=recursive , type=switch, char=r, help=recurse directories, headline=file options:", "name=fileext , type=string, char=e, help=process only files with extension in comma separated LIST, string-mode-append, string-append-separator=',', param=LIST", "name=ignore-case , type=switch, char=i, help=ignore case, headline=matching options:", "name=lower-case , type=switch, help=lower replacements", "name=upper-case , type=switch, help=upper replacements", "name=dummy-mode , type=switch, char=0, help='do *not* write/change anything, just test', headline=common options:", "name=quiet , type=switch, char=q, help=quiet execution\\, do not say anything", "name=progress , type=int, char=P, param=NUM, default=0, lower=0, help='progress indicator, useful with large files, print one dot per event", // --help and --version will appear automatically here "EOL" // end of list }; #endif // // notes: // ------ // to make it possible for the application to process control characters during runtime every // string is compiled from a c-string format (with \n, \xff and such things) at runtime. // if you enter the option list as string constants, then the c compiler already compiles // those strings. so you have to protect the backslash sequences by backslashes. example: (not worst case, but ugly) // to get a single backslash in the output (i.e. in --help) the string must contain two backslashes (\\), but // you must protect them in your string constant -- so the source looks like this: \\\\ :) // (of course, if you want to print this help with printf you have to write \\\\\\\\, tricky eh? :) // to make things more complicated, you have to protect commas in your text by quotes or backslashes which must // also be protected from the compiler to ... oh no. // just take a look at the example above (dummy-mode and quiet), not that worse, isnt it? :) // history: // ======== // 1997 // 01:45 Jun 11 stop on '--' feature added (929 lines) // 02:00 Jul 3 string once/append feature added (968 lines) // 12:00 Aug 7 application set/get support started (1103 lines) // 13:00 Aug 7 application set/get support finished (untested) (1164 lines) // 13:00 Aug 7 autosave support started // 21:00 Aug 7 autosave canceled: save support instead // 23:30 Aug 7 save support finished (1327 lines) // ??? get default/ranges // 1998 // 17:00 Jan 10 improved rc file parsing (string scan tools) // 18:47 Aug 04 fixed %s bug (printf->puts) // 22:48 Aug 05 added %e for authors email // 21:00 Oct 01 documentation (1595) // 21:29 Nov 12 allow %n in help (application name) (1607) // 20:51 Dec 01 ignore_negnum added (1617) // 1999 // 13:58 Jan 01 remove-- added (1623) // 15:01 Jan 01 type==STRING/OVERRIDE, rc-file/cmdline not set bug fixed (1625) // 23:00 Feb 03 single char alias added // 01:16 Feb 11 help fit terminal width added (1650) // 15:08 Feb 15 tobject type info removed // 12:29 Apr 02 optional_param added // 22:55 Jun 04 string_append bug fixed // 19:00 Sep 19 environment NULL pointer segfault fixed // 20:58 Sep 20 hide option added // 21:56 Sep 29 --hhelp added (oh how I like this ... :), hidden=hide (203/1677) // 2000: // 01:35 Jul 08 misc bugfixes // 2001: // 22:58 Jun 11 userErrorExitStatus added (273/1946) // 2007: // Jan 28 wasSetByUser() added // Jan 29 print warning for unknown rc-file parameters instead of error (1977 lines) // global data: /// global TAppConfig pointer to the only instance TAppConfig TAppConfig *tApp = 0; // global application name (pseudo invisible to the outside) const char *terrorApplicationName; // standard options static const char *self_conflist[] = { // standard options "type=switch, name=help, char=h, onlycl, help='print this help message, then exit successfully'", "type=switch, name=hhelp, onlycl, hide, help='like help but also show hidden options'", "type=switch, name=version, onlycl, help='print version, then exit successfully'", // configuration options "type=switch, name=verbose-config, onlycl, configopt, help='print all options, values and where they were specified, then exit', headline='config file options:'", "type=string, name=rc-file, onlycl, configopt, param=FILE, help='use FILE as rcfile'", "type=string, name=create-rc, onlycl, configopt, param=FILE, help='create a FILE with default values for all options'", // pseudovariables only visible to the application, not to the user "type=string, name=application-version, onlyapp", "type=string, name=application-name, onlyapp", "EOL" }; /// global exit status on userError() static int userErrorExitStatus = 1; // helpers template inline bool tOutOfRange(const T& value, const T& lower, const T& upper) { if((valueupper)) return true; else return false;} // file tools bool fisdir(const char *fname) { struct stat buf; if(stat(fname, &buf)) return false; if(S_ISDIR(buf.st_mode)) return true; else return false; } bool fisregular(const char *fname) { struct stat buf; if(stat(fname, &buf)) return false; if(S_ISREG(buf.st_mode)) return true; else return false; } bool fexists(const char *fname) { struct stat buf; if(stat(fname, &buf)) return false; else return true; } bool fissymlink(const char *fname) { #ifdef __STRICT_ANSI__ fname = fname; return false; #else struct stat buf; if(lstat(fname, &buf)) return false; if(S_ISLNK(buf.st_mode)) return true; else return false; #endif } off_t flen(const char *fname) { struct stat buf; if(stat(fname, &buf)) throw TFileOperationErrnoException(fname, "stat"); return buf.st_size; } off_t flen(int fdes) { struct stat buf; if(fstat(fdes, &buf)) throw TFileOperationErrnoException("", "fstat"); return buf.st_size; } off_t flen(FILE *file) { #ifdef __STRICT_ANSI__ long c = ftell(file); if(c == -1) throw TFileOperationErrnoException("", "ftell"); if(fseek(file, 0, SEEK_END)) throw TFileOperationErrnoException("", "fseek"); long r = ftell(file); if(r == -1) throw TFileOperationErrnoException("", "ftell"); if(fseek(file, c, SEEK_SET)) throw TFileOperationErrnoException("", "fseek"); return (off_t)r; #else struct stat buf; if(fstat(fileno(file), &buf)) throw TFileOperationErrnoException("", "fstat"); return buf.st_size; #endif } // errors void userWarning(const char *message, ...) { va_list ap; va_start(ap, message); if(terrorApplicationName) fprintf(stderr, "\r%s: warning: ", terrorApplicationName); else fprintf(stderr, "\rwarning: "); vfprintf(stderr, message, ap); va_end(ap); } // returns old status int setUserErrorExitStatus(int status) { int old = userErrorExitStatus; userErrorExitStatus = status; return old; } void userError(const char *message, ...) { va_list ap; va_start(ap, message); if(terrorApplicationName) fprintf(stderr, "\r%s: ", terrorApplicationName); else fprintf(stderr, "\rerror: "); vfprintf(stderr, message, ap); va_end(ap); exit(userErrorExitStatus); } #ifdef CHECKPOINTS #ifdef KEYWORD_CHECKPOINTS #undef for #undef do #undef else #undef switch #undef break #undef return #endif #define MAX_CP 64 const char *checkpoint_file[MAX_CP]; const char *checkpoint_func[MAX_CP]; int checkpoint_line[MAX_CP]; static int num_cp = -1; int addCheckpoint(const char *file, int line, const char *func) { fprintf(stderr, "%s:%d:%s%s checkpoint\n", file, line, func?func:"", func?":":""); if(num_cp < 0) { // initialize memset(checkpoint_file, 0, sizeof(checkpoint_file)); memset(checkpoint_line, 0, sizeof(checkpoint_line)); memset(checkpoint_func, 0, sizeof(checkpoint_func)); num_cp = 0; } else { checkpoint_file[num_cp] = file; checkpoint_line[num_cp] = line; checkpoint_func[num_cp] = func; num_cp++; if(num_cp >= MAX_CP) num_cp = 0; } return 1; } static void printCheckpoints() { if(num_cp < 0) { fprintf(stderr, "\ncheckpoint list not yet initialized\n"); return; } int n = num_cp - 1; for(int i = 0; i < MAX_CP; i++, n--) { if(n < 0) n += MAX_CP; if(checkpoint_file[n]) fprintf(stderr, "%s:%d:%s%s checkpoint\n", checkpoint_file[n], checkpoint_line[n], checkpoint_func[n]?checkpoint_func[n]:"", checkpoint_func[n]?":":""); } } #endif #ifdef __GNUC__ #define fatalError(format) fatalError_func(__FILE__,__LINE__,__PRETTY_FUNCTION__,format) #define fatalError1(format,a) fatalError_func(__FILE__,__LINE__,__PRETTY_FUNCTION__,format, a) #define fatalError2(format,a,b) fatalError_func(__FILE__,__LINE__,__PRETTY_FUNCTION__,format, a, b) #define fatalError3(format,a,b,c) fatalError_func(__FILE__,__LINE__,__PRETTY_FUNCTION__,format, a, b, c) #define fatalError4(format,a,b,c,d) fatalError_func(__FILE__,__LINE__,__PRETTY_FUNCTION__,format, a, b, c, d) #else #define fatalError(format) fatalError_func(__FILE__,__LINE__,0,format) #define fatalError1(format,a) fatalError_func(__FILE__,__LINE__,0,format, a) #define fatalError2(format,a,b) fatalError_func(__FILE__,__LINE__,0,format, a, b) #define fatalError3(format,a,b,c) fatalError_func(__FILE__,__LINE__,0,format, a, b, c) #define fatalError4(format,a,b,c,d) fatalError_func(__FILE__,__LINE__,0,format, a, b, c, d) #endif static void fatalError_func(const char *file, int line, const char *function, const char *message, ...) #ifdef __GNUC__ __attribute__ ((noreturn,format(printf,4,5))) #endif ; static void fatalError_func(const char *file, int line, const char *function, const char *message, ...) { #ifdef CHECKPOINTS printCheckpoints(); #endif if(function) fprintf(stderr, "\n%s:%d: fatal error in function '%s':\n", file, line, function); else fprintf(stderr, "\n%s:%d: fatal error:\n", file, line); va_list ap; va_start(ap, message); vfprintf(stderr, message, ap); va_end(ap); exit(1); } // TAppConfigItem implementation TAppConfigItem::TAppConfigItem(): must_have(false), should_have(false), only_cl(false), configopt(false), only_app(false), save(false), optional_param(false), hide(false), type(TACO_TYPE_NONE), set_in(NEVER), string_mode(OVERRIDE), string_sep(""), double_value(0), double_upper(0), double_lower(0), double_default(0), int_value(0), int_upper(0), int_lower(0), int_default(0), bool_value(false), bool_default(false), printed(false), name(), context(), help(), headline(), char_name(), par(), alias(), type_str(), upper(), lower(), def(), string_value(), string_default() {} TAppConfigItem::TAppConfigItem(const char *str, const char *line_context, bool privat): must_have(false), should_have(false), only_cl(false), configopt(false), only_app(false), save(false), optional_param(false), hide(false), type(TACO_TYPE_NONE), set_in(NEVER), string_mode(OVERRIDE), string_sep(""), double_value(0), double_upper(0), double_lower(0), double_default(0), int_value(0), int_upper(0), int_lower(0), int_default(0), bool_value(false), bool_default(false), printed(false), name(), context(line_context), help(), headline(), char_name(), par(), alias(), type_str(), upper(), lower(), def(), string_value(), string_default() { tvector comp = split(str, ",", true); for(size_t i = 0; i < comp.size(); i++) { setComp(split(comp[i], "=", true, true), privat); } validate(line_context); } inline static bool isalphaorul(char c) { if(isalpha(c) || c=='_') return true; else return false; } static tstring Range2Str(int lower, int upper) { if((lower != INT_MIN) || (upper != INT_MAX)) { tstring lstr; tstring ustr; if(lower != INT_MIN) lstr = tstring(lower); if(upper != INT_MAX) ustr = tstring(upper); return "[" + lstr + ".." + ustr + "]"; } else { return ""; } } static tstring Range2Str(double lower, double upper) { if((lower!=-DBL_MAX) || (upper!=DBL_MAX)) { tstring lstr; tstring ustr; if(lower != -DBL_MAX) lstr = tstring(lower); if(upper != DBL_MAX) ustr = tstring(upper); return "[" + lstr + ".." + ustr + "]"; } else { return ""; } } void TAppConfigItem::validate(const char *line_context) { // name if(name.len()<2) fatalError2("%s: name too short! (was %d, must have a least two chars)\n", line_context, int(name.len())); if(!isalphaorul(name[0])) fatalError2("%s: name should begin with alpha char or '_'! (was %c, must have a least two chars)\n", line_context, name[0]); if(char_name.len() > 1) fatalError1("%s: only one character may be specified as char name!\n", line_context); // help, alias and flags if((help=="")&&(!only_app)&&(!hide)) fatalError1("%s: you must give a help for each non nidden option!\n", line_context); for(size_t i = 0; i < alias.size(); i++) if(alias[i].len() < 1) fatalError1("%s: alias must not be empty!\n", line_context); if(only_app&&only_cl) fatalError1("%s: only one out of [onlyapp,onlycl] may be specified!\n", line_context); if(should_have&&must_have) fatalError1("%s: only one out of [shouldhave,musthave] may be specified!\n", line_context); if((!def.empty()) && must_have && only_cl) fatalError1("%s: default value for required command line option makes no sense!\n", line_context); // type if(type_str=="bool") type=BOOL; else if(type_str=="int") type=INT; else if(type_str=="switch") type=SWITCH; else if(type_str=="double") type=DOUBLE; else if(type_str=="string") type=STRING; else fatalError2("%s: illegal/unknown type '%s'!\n", line_context, type_str.c_str()); // string mode if((string_mode!=OVERRIDE) && (type!=STRING)) fatalError1("%s: string-mode-... makes only sense with strings!\n", line_context); if((!string_sep.empty()) && (type!=STRING)) fatalError1("%s: string-append-separator makes only sense with strings!\n", line_context); if((!string_sep.empty()) && (string_mode!=APPEND)) fatalError1("%s: string-append-separator makes only sense with string-mode-append!\n", line_context); // each type switch(type) { case SWITCH: if(must_have||should_have) fatalError1("%s: switch can't be reqired/recommended!\n", line_context); if(!def.empty()) fatalError1("%s: switch can't have a defaut (is always false)!\n", line_context); if(!upper.empty()) fatalError1("%s: switch can't have an upper limit!\n", line_context); if(!lower.empty()) fatalError1("%s: switch can't have a lower limit!\n", line_context); bool_value = false; break; case BOOL: if(!def.empty()) { if(!def.toBool(bool_default)) fatalError2("%s: illegal/unknown bool value for default '%s'! (can be [true|yes|on|t|1|false|no|off|f|0])\n", line_context, def.c_str()); } else bool_default = false; bool_value = bool_default; if(!upper.empty()) fatalError1("%s: bool can't have an upper limit!\n", line_context); if(!lower.empty()) fatalError1("%s: bool can't have a lower limit!\n", line_context); break; case INT: if(!def.empty()) { if(!def.toInt(int_default, 0)) fatalError2("%s: illegal chars for default value in '%s'!\n", line_context, def.c_str()); } else int_default = 0; int_value = int_default; if(!upper.empty()) { if(!upper.toInt(int_upper, 0)) fatalError2("%s: illegal chars for upper bound in '%s'!\n", line_context, upper.c_str()); } else int_upper = INT_MAX; if(!lower.empty()) { if(!lower.toInt(int_lower, 0)) fatalError2("%s: illegal chars for lower bound in '%s'!\n", line_context, lower.c_str()); } else int_lower = INT_MIN; if(tOutOfRange(int_default, int_lower, int_upper)) fatalError3("%s: default value out of range! (%d not in %s)!\n", line_context, int_default, Range2Str(int_lower, int_upper).c_str()); break; case DOUBLE: if(!def.empty()) { if(!def.toDouble(double_default)) fatalError2("%s: illegal chars for default value in '%s'!\n", line_context, def.c_str()); } else double_default = 0.0; double_value = double_default; if(!upper.empty()) { if(!upper.toDouble(double_upper)) fatalError2("%s: illegal chars for upper bound in '%s'!\n", line_context, upper.c_str()); } else double_upper = DBL_MAX; if(!lower.empty()) { if(!lower.toDouble(double_lower)) fatalError2("%s: illegal chars for lower bound in '%s'!\n", line_context, lower.c_str()); } else double_lower = -DBL_MAX; if(tOutOfRange(double_default, double_lower, double_upper)) fatalError3("%s: default value out of range! (%g not in %s)!\n", line_context, double_default, Range2Str(double_lower, double_upper).c_str()); break; case STRING: // all strings are valid: most generic type! string_default = def; string_value = string_default; break; case TACO_TYPE_NONE: fatalError("internal error! (invalid type id)\n"); } set_in = DEFAULT; } void TAppConfigItem::setComp(const tvector& a, bool privat) { if(a.size() > 2) fatalError2("%s: syntax error near component '%s'! (too many '=')\n", context.c_str(), join(a,"=").c_str()); if(a.size() == 0) fatalError2("%s: syntax error near component '%s'! (no component)\n", context.c_str(), join(a,"=").c_str()); if((a.size()==1) && (a[0].consistsOfSpace())) return; tstring comp = a[0]; tstring param; if(a.size()>1) { param=a[1]; param.unquote(); } if(comp=="name") { name = param; } else if(comp=="help") { help = param; } else if(comp=="headline") { headline = param; } else if(comp=="param") { par = param; } else if(comp=="char") { char_name = param; } else if(comp=="alias") { alias = split(param, "; ,", true, true); } else if(comp=="type") { type_str = param; } else if(comp=="default") { def = param; } else if(comp=="upper") { upper = param; } else if(comp=="lower") { lower = param; } else if(comp=="musthave") { must_have = true; } else if(comp=="shouldhave") { should_have = true; } else if(comp=="string-mode-append") { string_mode = APPEND; } else if(comp=="string-mode-once") { string_mode = ONCE; } else if(comp=="string-append-separator") { string_sep = param; } else if(privat && comp=="configopt") { configopt = true; } else if(comp=="save") { save = true; } else if(comp=="optional_param") { optional_param = true; } else if(comp=="hide") { hide = true; } else if(comp=="hidden") { hide = true; } else if(comp=="onlycl") { only_cl = true; } else if(privat && comp=="onlyapp") { only_app = true; } else fatalError2("%s: unknown component '%s'!\n", context.c_str(), comp.c_str()); } tstring TAppConfigItem::getWasSetStr(TACO_SET_IN setin, const tstring& env, const tstring& rcfile) const { switch(setin) { case DEFAULT: return "by default"; case COMMAND_LINE: return "on command line"; case ENVIRONMENT: return "in environment variable '" + env + "'"; case RC_FILE: return "in rc file '" + rcfile + "'"; default: fatalError1("illegal value %d in setin! (0==NEVER)\n", setin); #ifndef __GNUC__ return ""; #endif } } tstring TAppConfigItem::getWasSetStr(const tstring& env, const tstring& rcfile) const { return getWasSetStr(set_in, env, rcfile); } void TAppConfigItem::printValue(const tstring& env, const tstring& rcfile) const { switch(type) { case SWITCH: if(bool_value) printf("switch %s was set %s\n", name.c_str(), getWasSetStr(env, rcfile).c_str()); else printf("switch %s was not set\n", name.c_str()); break; case BOOL: printf("bool %s was set to %s %s\n", name.c_str(), bool_value?"true":"false", getWasSetStr(env, rcfile).c_str()); break; case INT: printf("int %s was set to %d %s\n", name.c_str(), int_value, getWasSetStr(env, rcfile).c_str()); break; case DOUBLE: printf("double %s was set to %g %s\n", name.c_str(), double_value, getWasSetStr(env, rcfile).c_str()); break; case STRING: { tstring s(string_value); s.expandUnprintable('"'); printf("string %s was set to \"%s\" %s\n", name.c_str(), s.c_str(), getWasSetStr(env, rcfile).c_str()); } break; default: fatalError1("printValue(): Illegal value %d in type! (0==TACO_TYPE_NONE)\n", type); } } tstring TAppConfigItem::getParamStr() const { if(!par.empty()) return par; switch(type) { case BOOL: return ""; case INT: return ""; case DOUBLE: return ""; case STRING: return ""; case SWITCH: return ""; default: fatalError1("getParamStr(): Illegal value %d in type! (0==TACO_TYPE_NONE)\n", type); #ifndef __GNUC__ return ""; #endif } } tstring TAppConfigItem::getTypeStr() const { switch(type) { case BOOL: return "bool"; case INT: return "int"; case DOUBLE: return "double"; case STRING: return "string"; case SWITCH: return "switch"; default: fatalError1("getTypeStr(): Illegal value %d in type! (0==TACO_TYPE_NONE)\n", type); #ifndef __GNUC__ return ""; #endif } } int TAppConfigItem::getOptLen() const { int l = getParamStr().len(); if(l) l++; if(l) if(optional_param) l += 2; return name.len() + l; } tstring TAppConfigItem::getFlagsStr(const tstring& optprefix, bool globalonlycl) const { tstring h; tstring range; switch(type) { case DOUBLE: range = Range2Str(double_lower, double_upper); break; case INT: range = Range2Str(int_lower, int_upper); break; default: break; } tstring defval; tstring string_mod; switch(type) { case DOUBLE: if(double_default!=0.0) defval = def; break; case INT: if(int_default!=0) defval = def; break; case STRING: { tstring s(string_default); s.expandUnprintable('"'); if(!s.empty()) defval = "\"" + s + "\""; } switch(string_mode) { case APPEND: string_mod = "multiple allowed"; break; case ONCE: string_mod = "only once"; break; default: break; } break; case BOOL: if(!def.empty()) defval = def; // take string to allow: yes true on 1 ... break; default: break; } if((only_cl && (!globalonlycl)) || must_have || should_have || (alias.size()>0) || (!range.empty()) || (!defval.empty()) || (!string_mod.empty()) || optional_param || hide) { h += "("; if(only_cl && (!globalonlycl)) h += "only command line"; if(optional_param) { if(h.lastChar()!='(') h+= ", "; h += "parameter is optional"; } if(must_have) { if(h.lastChar()!='(') h+= ", "; h += "required"; } if(should_have) { if(h.lastChar()!='(') h+= ", "; h += "recommended"; } if(hide) { if(h.lastChar()!='(') h+= ", "; h += "hidden"; } if(alias.size()==1) { if(h.lastChar()!='(') h+= ", "; if((alias[0].len()==2) && (alias[0][0]=='-')) h += "alias=" + alias[0]; else h += "alias=" + optprefix + alias[0]; } if(alias.size()>1) { if(h.lastChar()!='(') h+= ", "; h += "aliases={"; for(size_t i=0; i < alias.size(); i++) { if((alias[i].len()==2) && (alias[i][0]=='-')) h += alias[i]; else h += optprefix + alias[i]; if(i < alias.size()-1) h+=", "; } h += "}"; } if(!range.empty()) { if(h.lastChar()!='(') h+= ", "; h += "range=" + range; } if(!defval.empty()) { if(h.lastChar()!='(') h+= ", "; h += "default=" + defval; } if(!string_mod.empty()) { if(h.lastChar()!='(') h+= ", "; h += string_mod; } h += ")"; } return h; } void TAppConfigItem::printItemToFile(FILE *f) const { if(!headline.empty()) { // print headline fprintf(f, "\n# %.76s\n# %.76s\n\n", headline.c_str(), tstring('=', headline.len()).c_str()); } tstring h(help + "\n"); if(type == SWITCH) { h += "uncomment next line to activate switch"; } else { h += "parameter is of type " + getTypeStr(); } h += " " + getFlagsStr("", false); while(!h.empty()) { fprintf(f, "# %s\n", h.getFitWords(80 - 2 - 1).c_str()); } tstring defval; switch(type) { case DOUBLE: defval = tstring(double_default); break; case INT: defval = tstring(int_default); break; case STRING: { tstring s(string_default); s.expandUnprintable('"'); defval = "\"" + s + "\""; } break; case BOOL: if(!def.empty()) defval = def; // take string to allow yes true on 1 ... else defval = bool_default?"true":"false"; break; default: break; } if(type == SWITCH) { fprintf(f, "#%s\n", name.c_str()); } else { fprintf(f, "%s = %s\n", name.c_str(), defval.c_str()); } fprintf(f, "\n"); } void TAppConfigItem::printCurItemToFile(FILE *f, bool simple) const { if(!simple) { if(!headline.empty()) { // print headline fprintf(f, "\n# %.76s\n# %.76s\n\n", headline.c_str(), tstring('=', headline.len()).c_str()); } tstring h(help + "\n"); if(type==SWITCH) h += "parameter is a switch"; else { h += "parameter is of type " + getTypeStr(); h += " " + getFlagsStr("", false); } while(!h.empty()) { fprintf(f, "# %s\n", h.getFitWords(80 - 2 - 1).c_str()); } } tstring val; switch(type) { case DOUBLE: val = tstring(double_value); break; case INT: val = tstring(int_value); break; case STRING: { tstring s(string_value); s.expandUnprintable('"'); val = "\"" + s + "\""; } break; case BOOL: val = bool_value?"true":"false"; break; default: break; } if(type == SWITCH) { if(bool_value) fprintf(f, "%s\n", name.c_str()); else fprintf(f, "#%s\n", name.c_str()); } else { fprintf(f, "%s = %s\n", name.c_str(), val.c_str()); } if(!simple) fprintf(f, "\n"); } void TAppConfigItem::printHelp(int max_optlen, bool globalonlycl) const { int hcol = max_optlen + 5 + 2; char buf[256]; int width = 80; #ifndef NOWINSIZE struct winsize win; if(ioctl(1, TIOCGWINSZ, &win) == 0) { width = win.ws_col; } #endif if(width < hcol + 8) width = hcol + 8; if(!headline.empty()) { printf("\n%s\n", headline.c_str()); } sprintf(buf, "%c%c --%s%s%s%s", (!char_name.empty())?'-':' ', (!char_name.empty())?char_name[0]:' ', name.c_str(), optional_param?"[":"", type==SWITCH?"":("=" + getParamStr()).c_str(), optional_param?"]":""); tstring h(help); h += " " + getFlagsStr("--", globalonlycl); printf("%*s%s\n", -hcol, buf, h.getFitWords(width - hcol - 1).c_str()); while(!h.empty()) { printf("%*s%s\n", -hcol, "", h.getFitWords(width - hcol - 1).c_str()); } } void TAppConfigItem::setValue(const tstring& parameter, TACO_SET_IN setin, bool verbose, const tstring& env, const tstring& rcfile, const tstring& line_context) { if(only_app) userError("internal parameter name '%s' is private to the application may not be set by the user!\n", name.c_str()); if(only_cl && (!setin==COMMAND_LINE)) userError("parameter name '%s' is only available on the command line!\n", name.c_str()); // remember verbatim value for non string parameters in string_value if(type != STRING) string_value = parameter; // prepare option string tstring option; if(setin==COMMAND_LINE) { option = "option -" + char_name + ", --" + name; } else { option = "option '" + name + "'"; } if(set_in!=DEFAULT) { if(!((type==STRING)&&(string_mode!=OVERRIDE))) { if(verbose) printf("warning: %s (set %s) is overridden %s\n", option.c_str(), getWasSetStr(setin, env, rcfile).c_str(), getWasSetStr(env, rcfile).c_str()); return; } } switch(type) { case SWITCH: if(!parameter.empty()) userError("%s: %s is a switch and takes no parameter!\n", line_context.c_str(), option.c_str()); bool_value = true; break; case BOOL: if(!parameter.toBool(bool_value)) userError("%s: illegal/unknown bool value '%s' for %s!\n(can be [true|yes|on|t|1|false|no|off|f|0])\n", line_context.c_str(), parameter.c_str(), option.c_str()); break; case INT: if(!parameter.toInt(int_value)) userError("%s: illegal int value format for %s in '%s'!\n", line_context.c_str(), option.c_str(), parameter.c_str()); if(tOutOfRange(int_value, int_lower, int_upper)) userError("%s: value out of range for %s! (%d not in %s)!\n", line_context.c_str(), option.c_str(), int_value, Range2Str(int_lower, int_upper).c_str()); break; case DOUBLE: if(!parameter.toDouble(double_value)) userError("%s: illegal double value format for %s in '%s'!\n", line_context.c_str(), option.c_str(), parameter.c_str()); if(tOutOfRange(double_value, double_lower, double_upper)) userError("%s: value out of range for %s! (%g not in %s)!\n", line_context.c_str(), option.c_str(), double_value, Range2Str(double_lower, double_upper).c_str()); break; case STRING: switch(string_mode) { case ONCE: if(set_in != DEFAULT) userError("%s: %s may only be set once! (was already set to '%s' and was tried to set to '%s')\n", line_context.c_str(), option.c_str(), string_value.c_str(), parameter.c_str()); string_value = parameter; break; case APPEND: if((set_in != DEFAULT)||(!string_value.empty())) string_value += string_sep; string_value += parameter; break; case OVERRIDE: string_value = parameter; // overwrite old value, the default break; } break; default: fatalError1("setValue(): Illegal value %d in type! (0==TACO_TYPE_NONE)\n", type); } set_in = setin; } void TAppConfigItem::setValueFromApp(bool parameter) { if(only_app) fatalError1("internal parameter name '%s' is private to tapplication may not be set by the user!\n", name.c_str()); if(only_cl) fatalError1("parameter name '%s' is only available on the command line!\n", name.c_str()); set_in = APPLICATION; if((type==SWITCH)||(type==BOOL)) { bool_value=parameter; } else { fatalError1("setValueFromApp(bool/switch): type mismatch: type was %s\n", getTypeStr().c_str()); } } // returns true if value is valid, else false bool TAppConfigItem::setValueFromApp(int i) { if(only_app) fatalError1("internal parameter name '%s' is private to tapplication may not be set by the user!\n", name.c_str()); if(only_cl) fatalError1("parameter name '%s' is only available on the command line!\n", name.c_str()); set_in = APPLICATION; if(type==INT) { if(i > int_upper) { int_value = int_upper; return false; } if(i < int_lower) { int_value = int_lower; return false; } int_value = i; return true; } else { fatalError1("setValueFromApp(int): type mismatch: type was %s\n", getTypeStr().c_str()); #ifndef __GNUC__ return false; #endif } } // returns true if value is valid, else false bool TAppConfigItem::setValueFromApp(double d) { if(only_app) fatalError1("internal parameter name '%s' is private to tapplication may not be set by the user!\n", name.c_str()); if(only_cl) fatalError1("parameter name '%s' is only available on the command line!\n", name.c_str()); set_in = APPLICATION; if(type==DOUBLE) { if(d > double_upper) { double_value = double_upper; return false; } if(d < double_lower) { double_value = double_lower; return false; } double_value = d; return true; } else { fatalError1("setValueFromApp(double): type mismatch: type was %s\n", getTypeStr().c_str()); #ifndef __GNUC__ return false; #endif } } void TAppConfigItem::setValueFromApp(const tstring& str) { if(only_app) fatalError1("internal parameter name '%s' is private to tapplication may not be set by the user!\n", name.c_str()); if(only_cl) fatalError1("parameter name '%s' is only available on the command line!\n", name.c_str()); set_in = APPLICATION; if(type==STRING) { string_value = str; } else { fatalError1("setValueFromApp(string): type mismatch: type was %s\n", getTypeStr().c_str()); } } // returns true if value is valid, else false // sets value from string according to any type (switch == bool here) bool TAppConfigItem::setValueFromAppFromStr(const tstring& parameter) { if(only_app) fatalError1("internal parameter name '%s' is private to tapplication may not be set by the user!\n", name.c_str()); if(only_cl) fatalError1("parameter name '%s' is only available on the command line!\n", name.c_str()); set_in = APPLICATION; switch(type) { case SWITCH: case BOOL: if(!parameter.toBool(bool_value)) fatalError2("SetValueFromAppFromStr: illegal/unknown bool value '%s' for %s!\n(can be [true|yes|on|t|1|false|no|off|f|0])\n", parameter.c_str(), name.c_str()); return true; case INT: if(!parameter.toInt(int_value)) fatalError2("SetValueFromAppFromStr: illegal int value format for %s in '%s'!\n", name.c_str(), parameter.c_str()); return setValueFromApp(int_value); case DOUBLE: if(!parameter.toDouble(double_value)) fatalError2("SetValueFromAppFromStr: illegal double value format for %s in '%s'!\n", name.c_str(), parameter.c_str()); return setValueFromApp(double_value); case STRING: string_value = parameter; return true; default: fatalError1("SetValueFromStrFromApp(): Illegal value %d in type! (0==TACO_TYPE_NONE)\n", type); #ifndef __GNUC__ return false; #endif } } // TAppConfig implementation TAppConfig::TAppConfig(const char *conflist[], const char *listname, int argc, char *argv[], const char *envstrname, const char *rcname, const tstring& version): _params(), name(), opt(), alias(), envname(), rc_name(), rc_str(rcname?rcname:""), verbose_conf(false), onlycl(false), stopatdd(false), removedd(false), ignore_negnum(false), usage(), trailer(), commonhead() { if(tApp) fatalError("only exactly one instance of TAppConfig is allowd!\n"); for(int i = 0; i < 256; i++) char2index[i] = -1; stopatdd = onlycl = verbose_conf = false; addConfigItems(conflist, listname, false); addConfigItems(self_conflist, "self_conflist", true); doCommandLine(argc, argv, version); for(size_t i=0; i found; for(size_t i = 0; i < opt.size(); i++) { // search for prefixes if(opt[i].name.hasPrefix(str) && (!opt[i].only_app)) found += opt[i].name; } if(found.size()==0) { if (errorIfNotFound) { userError("%sunknown option '%s'! (try --help)\n", context.c_str(), (optprefix+str).c_str()); } else { userWarning("%sunknown option '%s'!\n", context.c_str(), (optprefix+str).c_str()); return ""; } } if(found.size() != 1) { tstring option = optprefix + str; tstring list = optprefix + join(found, (", "+optprefix)); userError("%sambiguous option '%s' matches '%s'!\n", context.c_str(), option.c_str(), list.c_str()); } return found[0]; // uniq abbrevation } bool TAppConfig::getBool(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if((a.type==TAppConfigItem::BOOL) || (a.type==TAppConfigItem::SWITCH)) return a.bool_value; else fatalError2("type mismatch in call for %s '%s' as bool!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return false; #endif } void TAppConfig::setValue(const tstring &n, bool b) { if(name.contains(n)) { opt[name[n]].setValueFromApp(b); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); } void TAppConfig::setValue(const tstring &n, const tstring& str) { if(name.contains(n)) { opt[name[n]].setValueFromApp(str); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); } bool TAppConfig::setValue(const tstring &n, int i) { if(name.contains(n)) { return opt[name[n]].setValueFromApp(i); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return false; #endif } bool TAppConfig::setValue(const tstring &n, double d) { if(name.contains(n)) { return opt[name[n]].setValueFromApp(d); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return false; #endif } bool TAppConfig::setValueFromStr(const tstring &n, const tstring& str) { if(name.contains(n)) { return opt[name[n]].setValueFromAppFromStr(str); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return false; #endif } const tstring& TAppConfig::getString(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); return a.string_value; } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ static tstring s; return s; #endif } int TAppConfig::getInt(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if(a.type==TAppConfigItem::INT) return a.int_value; else fatalError2("type mismatch in call for %s '%s' as int!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return 0; #endif } int TAppConfig::intUpper(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if(a.type==TAppConfigItem::INT) return a.int_upper; else fatalError2("type mismatch in call for %s '%s' as int!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return 0; #endif } int TAppConfig::intLower(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if(a.type==TAppConfigItem::INT) return a.int_lower; else fatalError2("type mismatch in call for %s '%s' as int!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return 0; #endif } int TAppConfig::intDefault(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if(a.type==TAppConfigItem::INT) return a.int_default; else fatalError2("type mismatch in call for %s '%s' as int!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return 0; #endif } double TAppConfig::getDouble(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if(a.type==TAppConfigItem::DOUBLE) return a.double_value; else fatalError2("type mismatch in call for %s '%s' as double!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return 0; #endif } double TAppConfig::doubleUpper(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if(a.type==TAppConfigItem::DOUBLE) return a.double_upper; else fatalError2("type mismatch in call for %s '%s' as double!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return 0; #endif } double TAppConfig::doubleLower(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if(a.type==TAppConfigItem::DOUBLE) return a.double_lower; else fatalError2("type mismatch in call for %s '%s' as double!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return 0; #endif } double TAppConfig::doubleDefault(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if(a.type==TAppConfigItem::DOUBLE) return a.double_default; else fatalError2("type mismatch in call for %s '%s' as double!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return 0; #endif } tstring TAppConfig::stringDefault(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if(a.type==TAppConfigItem::STRING) return a.string_default; else fatalError2("type mismatch in call for %s '%s' as string!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ static tstring s; return s; #endif } bool TAppConfig::boolDefault(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); if(a.type==TAppConfigItem::BOOL) return a.bool_default; else fatalError2("type mismatch in call for %s '%s' as bool!\n", a.getTypeStr().c_str(), n.c_str()); } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ return false; #endif } bool TAppConfig::getSwitch(const tstring &n) const { return getBool(n); } bool TAppConfig::operator() (const tstring &n) const { return getBool(n); } TAppConfigItem::TACO_SET_IN TAppConfig::wasSetIn(const tstring &n) const { if(name.contains(n)) { const TAppConfigItem& a(opt[name[n]]); return a.set_in; } else fatalError1("unknown parameter name '%s'\n", n.c_str()); #ifndef __GNUC__ // will never come here, avoid warning return TAppConfigItem::NEVER; #endif } bool TAppConfig::wasSetByUser(const tstring &n) const { TAppConfigItem::TACO_SET_IN set = wasSetIn(n); return (set == TAppConfigItem::COMMAND_LINE) || (set == TAppConfigItem::RC_FILE); } void TAppConfig::setFromStr(const tstring& str, const tstring& context, TAppConfigItem::TACO_SET_IN setin) { // prepare name n and parameter par tstring n; tstring par; size_t scanner=0; str.skipSpace(scanner); n = str.scanUpTo(scanner, " \t\n="); str.skipSpace(scanner); str.perhapsSkipOneChar(scanner, '='); str.skipSpace(scanner); par = str.scanRest(scanner); par.cropSpace(); // set value if(!n.empty()) { // get name, ignore unknown parameters in config files with a warning n = getName(n, context, "", setin != TAppConfigItem::RC_FILE); if (n.empty()) return; // ignore unknown parameters (warning already printed) TAppConfigItem& a(opt[name[n]]); if(a.type == TAppConfigItem::SWITCH) { // need no param if(str.contains('=')) par = "t"; // force error } else { // get param if(par.consistsOfSpace()) userError("%s: missing parameter for option '%s'!\n", context.c_str(), n.c_str()); } if(a.type == TAppConfigItem::STRING) { par.unquote(); par.compileCString(); } a.setValue(par, setin, verbose_conf, envname, rc_name, context); } else { userError("%s: syntax error, missing option name in '%s'!\n", context.c_str(), str.c_str()); } } void TAppConfig::doEnvironVar(const char *env) { envname = env; char *p = getenv(env); if(p) { if(tstring(p).consistsOfSpace()) { if(verbose_conf) printf("environment variable '%s' is empty\n", env); return; } if(verbose_conf) printf("parsing environment variable '%s'\n", env); tvector a = split(p, ",", true, true); for(size_t i=0; i a; // which file ? if(!clrcfile.empty()) { if(fexists(clrcfile.c_str())) { if(verbose_conf) printf("parsing rc-file '%s'\n", clrcfile.c_str()); rc_name = clrcfile; } else { userError("can't load rc-file '%s'!\n", clrcfile.c_str()); } } else { tstring globalname = "/etc/" + rcfile; tstring localname; char *home = getenv("HOME"); if(home) { localname = tstring(home) + "/." + rcfile; } if(fexists(localname.c_str())) { if(verbose_conf) printf("parsing local rc-file '%s'\n", localname.c_str()); rc_name = localname; } else if(fexists(globalname.c_str())) { if(verbose_conf) printf("parsing global rc-file '%s'\n", globalname.c_str()); rc_name = globalname; } else { if(verbose_conf) printf("neither '%s' nor '%s' exist: no rc-file found\n", localname.c_str(), globalname.c_str()); return; } } // load file a = loadTextFile(rc_name.c_str()); // parse rc-file for(size_t i = 0; i < a.size(); i++) { if(!strchr("#;!/", a[i][0])) { // ignore comment if(!a[i].consistsOfSpace()) setFromStr(a[i], "rc-file '" + rc_name + "' (line " + tstring(int(i + 1)) + ")", TAppConfigItem::RC_FILE); } } } void TAppConfig::doCommandLine(int ac, char *av[], const tstring& version) { // one of these chars follows a signgle dash '-' to make it a numeric arg // in the sense of 'ignore_negnum' const char *validnegnumchars="0123456789."; // set version and name opt[name["application-name"]].string_value = av[0]; opt[name["application-name"]].string_value.extractFilename(); opt[name["application-version"]].string_value = version; // parse command line bool nomoreoptions = false; // -- reached = false for(int i=1; i= 0) { // valid short option TAppConfigItem& a(opt[char2index[(unsigned char)*p]]); tstring par; if(a.type != TAppConfigItem::SWITCH) { // get param if(p[1]) { par = &p[1]; } else { i++; if(imax) max = len; } } return max; } void TAppConfig::printHelp(bool show_hidden) const { tstring ausage(usage); tstring atrailer(trailer); ausage.searchReplace("%n", getString("application-name")); ausage.searchReplace("%v", getString("application-version")); ausage.searchReplace("%e", TAPPCONFIG_EMAIL); ausage.searchReplace("%gpl", TAPPCONFIG_GPL); atrailer.searchReplace("%n", getString("application-name")); atrailer.searchReplace("%v", getString("application-version")); atrailer.searchReplace("%e", TAPPCONFIG_EMAIL); atrailer.searchReplace("%gpl", TAPPCONFIG_GPL); puts(ausage.c_str()); int max = getMaxOptLen(show_hidden); for(size_t i=0; i& a, const tstring& context) { if((a.size()==1) && (a[0].consistsOfSpace())) return; tstring comp = a[0]; tstring parameter; if(a.size()>1) { parameter=a[1]; parameter.unquote(); } if(a.size()>2) { tstring s = join(a, "="); s.expandUnprintable('\''); fatalError2("%s: syntax error near component '%s'! (too many '=')\n", context.c_str(), s.c_str()); } if(comp=="usage") { usage = parameter; } else if(comp=="commonheadline") { commonhead = parameter; } else if(comp=="trailer") { trailer = parameter; } else if(comp=="onlycl") { onlycl = true; } else if(comp=="stopat--") { stopatdd = true; } else if(comp=="remove--") { removedd = true; } else if(comp=="ignore_negnum") { ignore_negnum = true; } else fatalError2("%s: unknown component '%s'!\n", context.c_str(), comp.c_str()); } void TAppConfig::doMetaChar(const tstring& str, const tstring& context) { tvector a = split(str, ",", true); for(size_t i=0; i < a.size(); i++) { setComp(split(a[i], "=", true, true), context); } } void TAppConfig::addConfigItems(const char **list, const char *listname, bool privat) { for(int line=1; ; line++, list++) { // context string for errors tstring context("conflist " + tstring(listname) + " (line " + tstring(line) + ")"); // test for end of list (EOL) if(*list == 0) fatalError1("%s: list not terminated! (0 pointer, should be \"EOL\")\n", context.c_str()); if(strcmp(*list, "EOL")==0) break; if(strcmp(*list, "#EOL")==0) break; if(strcmp(*list, "")==0) break; if(strlen(*list)<7) fatalError2("%s: line corrupt (too short) perhaps listitem or list not terminated!\n (was '%s') check listitem (must have at least type and name)\nor terminate list with \"EOL\"\n", context.c_str(), *list); // meta char? if(*list[0] == '#') { doMetaChar((*list)+1, context); continue; } // get config item TAppConfigItem a(*list, context.c_str(), privat); // skip useless options if(onlycl && a.configopt) continue; // register name if(name.contains(a.name)) fatalError3("%s: duplicate name '%s' (previoulsy defined in %s)!\n", context.c_str(), a.name.c_str(), opt[name[a.name]].context.c_str()); name[a.name] = opt.size(); // register char name if(!a.char_name.empty()) { if(char2index[(unsigned char)a.char_name[0]]>=0) fatalError3("%s: duplicate char name '%c' (previoulsy defined in %s)!\n", context.c_str(), a.char_name[0], opt[char2index[(unsigned char)a.char_name[0]]].context.c_str()); char2index[(unsigned char)a.char_name[0]] = opt.size(); } // register aliases for(size_t i=0; i < a.alias.size(); i++) { if((a.alias[i].len() == 2) && (a.alias[i][0] == '-')) { // char alias if(char2index[(unsigned char)a.alias[i][1]]>=0) fatalError3("%s: duplicate char name for char alias '%c' (previoulsy defined in %s)!\n", context.c_str(), a.alias[i][1], opt[char2index[(unsigned char)a.alias[i][1]]].context.c_str()); char2index[(unsigned char)a.alias[i][1]] = opt.size(); } else { // name alias if(alias.contains(a.alias[i])) fatalError2("%s: duplicate alias '%s'!\n", context.c_str(), a.alias[i].c_str()); alias[a.alias[i]] = opt.size(); } } // set headline for help option if(a.name == "help") a.headline = commonhead; // add config item opt += a; } } mp3check-0.8.7/tappconfig.h000644 000766 000766 00000021274 11763233643 014672 0ustar00ovov000000 000000 /*GPL*START* * * tappconfig.h - console application framework header file * * Copyright (C) 1998 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #ifndef _ngw_tappconfig_h_ #define _ngw_tappconfig_h_ #include "tmap.h" #include "tvector.h" #include "tstring.h" // global application pointer class TAppConfig; extern TAppConfig *tApp; // should be a private subclass of TAppConfig class TAppConfigItem { public: // types enum TACO_TYPE {TACO_TYPE_NONE, STRING, INT, DOUBLE, BOOL, SWITCH}; enum TACO_STRING_MODE {OVERRIDE, APPEND, ONCE}; enum TACO_SET_IN {NEVER, DEFAULT, COMMAND_LINE, RC_FILE, ENVIRONMENT, APPLICATION}; // cons TAppConfigItem(); TAppConfigItem(const char *confitem, const char *context, bool privat); // interface void printItemToFile(FILE *f) const; // print default void printCurItemToFile(FILE *f, bool simple) const; // print current void printValue(const tstring& env, const tstring& rcfile) const; void printHelp(int maxoptlen, bool globalonlycl) const; int getOptLen() const; tstring getTypeStr() const; void setValue(const tstring& par, TACO_SET_IN setin, bool verbose=false, const tstring& env="", const tstring& rcfile="", const tstring& context="command line"); // set from application methods: // returns true if value is valid, else false // sets value from string according to any type (switch == bool here) bool setValueFromAppFromStr(const tstring& parameter); void setValueFromApp(const tstring& str); // returns true if value is valid, else false bool setValueFromApp(double d); // returns true if value is valid, else false bool setValueFromApp(int i); void setValueFromApp(bool b); private: // private methods void setComp(const tvector& a, bool privat); void validate(const char *context); void privateInit(); tstring getParamStr() const; tstring getWasSetStr(const tstring& env, const tstring& rcfile) const; tstring getWasSetStr(TACO_SET_IN setin, const tstring& env, const tstring& rcfile) const; tstring getFlagsStr(const tstring& optprefix, bool globalonlycl) const; public: // data bool must_have; bool should_have; bool only_cl; bool configopt; bool only_app; bool save; bool optional_param; bool hide; TACO_TYPE type; TACO_SET_IN set_in; TACO_STRING_MODE string_mode; tstring string_sep; double double_value, double_upper, double_lower, double_default; int int_value, int_upper, int_lower, int_default; bool bool_value, bool_default; // temp flag bool printed; tstring name; tstring context; tstring help; tstring headline; tstring char_name; tstring par; tvector alias; tstring type_str; tstring upper, lower, def; tstring string_value, string_default; }; // this is the main class of this h file class TAppConfig { public: // ctor & dtor TAppConfig(const char *conflist[], const char *listname, int argc, char *av[], const char *envstrname, const char *rcname, const tstring& version); ~TAppConfig(); // main interface void printHelp(bool show_hidden = false) const; void printValues() const; bool save(tstring *rc_name_out = 0); // save items with item.save==true // typed options: const tstring& getString(const tstring& par) const; double getDouble(const tstring& par) const; int getInt(const tstring& par) const; bool getBool(const tstring& par) const; // bool + switch bool getSwitch(const tstring& par) const; // bool + switch bool operator() (const tstring& par) const; // bool + switch // untyped parameters: tstring param(int i) const { return _params[i]; } size_t numParam() const { return _params.size(); } const tvector& params() const { return _params; } // set values: // returns true if value is valid, else false // sets value from string according to any type (switch == bool here) bool setValueFromStr(const tstring &n, const tstring& str); void setValue(const tstring &n, const tstring& str); bool setValue(const tstring &n, double d); bool setValue(const tstring &n, int i); void setValue(const tstring &n, bool b); // return the upper and lower bounds and defaults for the type int intUpper(const tstring &n) const; int intLower(const tstring &n) const; int intDefault(const tstring &n) const; double doubleUpper(const tstring &n) const; double doubleLower(const tstring &n) const; double doubleDefault(const tstring &n) const; tstring stringDefault(const tstring &n) const; bool boolDefault(const tstring &n) const; // return location where parameter was set TAppConfigItem::TACO_SET_IN wasSetIn(const tstring& n) const; bool wasSetByUser(const tstring& n) const; private: // readonly public data tvector _params; // private data tmap name; // get index of long name int char2index[256]; // get index of short name tvector opt; // all options in the order of conflist tmap alias; // aliases for options tstring envname; // name of env var tstring rc_name; // name of loaded rc file tstring rc_str; // namestr of rc file bool verbose_conf; // verbose configuration: warnings and values bool onlycl; // true== dont use rcfile and envvar, only command line bool stopatdd; // stop option scanning after -- bool removedd; // remove first -- param bool ignore_negnum;// something like -20 or -.57 is not trated as options tstring usage; // usage string: printed before help tstring trailer; // trailer string: printed after help tstring commonhead; // headline for common options int getMaxOptLen(bool show_hidden) const; void doMetaChar(const tstring& str, const tstring& context); void setComp(const tvector& a, const tstring& context); void addConfigItems(const char **list, const char *listname, bool privat); void doCommandLine(int ac, char *av[], const tstring& version); void doEnvironVar(const char *envvar); void doRCFile(const tstring& rcfile, const tstring& clrcfile); void setFromStr(const tstring& str, const tstring& context, TAppConfigItem::TACO_SET_IN setin); tstring getName(const tstring& str, const tstring& context, const tstring& optprefix="", bool errorIfNotFound = true) const; void createRCFile(const tstring& fname, const tstring& rcname) const; }; // global helper functions neede by tappconfig and other applications // file tools // return true if file is a directory bool fisdir(const char *fname); // return true if file is a regular file bool fisregular(const char *fname); // return true if file is a symbolic link bool fissymlink(const char *fname); // return false if nothing with this name exists bool fexists(const char *fname); // return length of file or -1 if file does not exist off_t flen(const char *fname); off_t flen(int fdes); off_t flen(FILE *file); // errors void userWarning(const char *message, ...) #ifdef __GNUC__ __attribute__ ((format(printf,1,2))) #endif ; void userError(const char *message, ...) #ifdef __GNUC__ __attribute__ ((noreturn,format(printf,1,2))) #endif ; int setUserErrorExitStatus(int status); // checkpoint macros //#define CHECKPOINTS #define KEYWORD_CHECKPOINTS #ifdef CHECKPOINTS # ifdef __GNUC__ # define CP() addCheckpoint(__FILE__,__LINE__,__PRETTY_FUNCTION__) # else # define CP() addCheckpoint(__FILE__,__LINE__) # endif int addCheckpoint(const char *file, int line, const char *func = 0); // keyword checkpoint # ifdef KEYWORD_CHECKPOINTS # define for if(CP())for // #define while if(0);else while # define do if(CP())do // #define if if(0);else if # define else else if(CP()) # define switch if(CP())switch # define break if(CP())break # define return if(CP())return # endif // internal checkpoint data extern const char *checkpoint_file[]; extern const char *checkpoint_func[]; extern int checkpoint_line[]; #endif #endif /* tappconfig.h */ mp3check-0.8.7/TARGET000644 000766 000766 00000000274 11763233643 013275 0ustar00ovov000000 000000 TARGET=mp3check SHARED_FILES=INSTALL COPYING Makefile configure tappconfig.cc tappconfig.h tstring.cc tstring.h texception.h tmap.h tvector.h tregex.cc tregex.h tfiletools.h tfiletools.cc mp3check-0.8.7/texception.h000644 000766 000766 00000011510 11763233643 014712 0ustar00ovov000000 000000 /*GPL*START* * * texception - basic exceptions * * Copyright (C) 1998 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #ifndef _ngw_texception_h_ #define _ngw_texception_h_ extern "C" { #include #include #include #include } // history: // 1999: // 17:02 04 Jun derived from terror.h // 2000: // 11:10 09 Jul tbaseexception and texception merged, message() and name() added // 00:50 09 Jul internal error added #define TExceptionN(n) public: virtual const char *name() const { return #n; } #define TExceptionM(m) public: virtual const char *message() const { return m; } #define TExceptionM1(m,a) public: virtual const char *message() const { char *buf; asprintf(&buf, m, a); return buf; } #define TExceptionM2(m,a,b) public: virtual const char *message() const { char *buf; asprintf(&buf, m, a,b); return buf; } #define TExceptionM3(m,a,b,c) public: virtual const char *message() const { char *buf; asprintf(&buf, m, a,b,c); return buf; } #define TExceptionM4(m,a,b,c,d) public: virtual const char *message() const { char *buf; asprintf(&buf, m, a,b,c,d); return buf; } // base class of all exceptions class TException { TExceptionN(TException); virtual ~TException() {} TExceptionM("(no message available)"); #if 0 #ifndef __USE_GNU static void asprintf(char **strp, const char *format, ...) { va_list ap; va_start(ap, format); *strp = new char[1024]; vsprintf(*strp, format, ap); va_end(ap); } #endif #if !(defined HAVE_STRDUP) static char *strdup(const char *str) { char *buf; vasprintf(&buf, "%s", str); return buf; } #endif #endif }; // general exceptions, also base classes class TIndexOutOfRangeException: public TException { TExceptionN(TIndexOutOfRangeException); TIndexOutOfRangeException(int lower_, int index_, int upper_): lower(lower_), index(index_), upper(upper_) {} TExceptionM3("index %d not in [%d..%d]", index, lower, upper); int lower, index, upper; }; class TZeroBasedIndexOutOfRangeException: public TIndexOutOfRangeException { TExceptionN(TZeroBasedIndexOutOfRangeException); TZeroBasedIndexOutOfRangeException(int index_, int total_num_): TIndexOutOfRangeException(0, index_, total_num_-1) {} }; class TErrnoException: public TException { TExceptionN(TErrnoException); TErrnoException(int error = -1): err(error) { if(err < 0) err = errno; } const char *str() const { if(err >= 0) return strerror(err); else return "(no error)"; } int err; TExceptionM2("%s (errno #%d)", str(), err); }; class TOperationErrnoException: public TErrnoException { TExceptionN(TOperationErrnoException); TOperationErrnoException(const char *operation_, int error = -1): TErrnoException(error), operation(operation_) {} const char *operation; TExceptionM3("%s: %s (errno #%d)", operation, str(), err); }; class TNotFoundException: public TException { TExceptionN(TNotFoundException); }; class TFileOperationErrnoException: public TErrnoException { TExceptionN(TFileOperationErrnoException); TFileOperationErrnoException(const char *filename_, const char *operation_, int err_ = -1): TErrnoException(err_), filename(strdup(filename_)), operation(strdup(operation_)) {} // virtual ~TFileOperationErrnoException() { free(filename); free(operation); } const char *filename; const char *operation; TExceptionM3("%s: %s (during %s)", filename, TErrnoException::message(), operation); // TExceptionM2("%s: %s (during )", filename, operation); // TExceptionM("toll"); }; class TInternalErrorException: public TException { TExceptionN(TInternalErrorException); TInternalErrorException(const char *error_ = "unspecific error"): error(strdup(error_)) {} // ~TInternalErrorException() { free(error); } char *error; TExceptionM1("internal error: %s", error); }; class TNotInitializedException: public TInternalErrorException { TExceptionN(TNotInitializedException); TNotInitializedException(const char *type_name_): type_name(type_name_) {} TExceptionM1("internal_error: object of type '%s' is not initialized", type_name ? type_name : ""); const char *type_name; }; #endif /* texception.h */ mp3check-0.8.7/tfiletools.cc000644 000766 000766 00000016210 11763233643 015054 0ustar00ovov000000 000000 /*GPL*START* * * tfilesync.h - file and directory handling tolls header file * * Copyright (C) 2000 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #include #include #include "tfiletools.h" #define COUNT_VERBOSE_STEP 1000 // TFile implementation // TFile::FileType TFile::filetype() const { if(isregular()) return FT_REGULAR; else if(isdir()) return FT_DIR; else if(issymlink()) return FT_SYMLINK; else if(ischardev()) return FT_CHARDEV; else if(isblockdev()) return FT_BLOCKDEV; else if(isfifo()) return FT_FIFO; else if(issocket()) return FT_SOCKET; else return FT_UNKNOWN; } tstring TFile::filetypeLongStr() const { switch(filetype()) { case FT_SOCKET: return "socket"; case FT_SYMLINK: return "symbolic link"; case FT_REGULAR: return "regular file"; case FT_BLOCKDEV: return "block device"; case FT_DIR: return "directory"; case FT_CHARDEV: return "character device"; case FT_FIFO: return "fifo"; default: { tstring t; t.sprintf("%#o", int(filetypebits())); return t; } } } char TFile::filetypeChar() const { switch(filetype()) { case FT_SOCKET: return 's'; case FT_SYMLINK: return 'l'; case FT_REGULAR: return '-'; case FT_BLOCKDEV: return 'b'; case FT_DIR: return 'd'; case FT_CHARDEV: return 'c'; case FT_FIFO: return 'p'; default: return '?'; } } tstring TFile::filetypeStr7() const { switch(filetype()) { case FT_SOCKET: return "socket "; case FT_SYMLINK: return "symlink"; case FT_REGULAR: return "regular"; case FT_BLOCKDEV: return "blockdv"; case FT_DIR: return "dir "; case FT_CHARDEV: return "chardev"; case FT_FIFO: return "fifo "; default: return "unknown"; } } void TFile::getstat() const { if(stated) return; if(name_.empty()) throw TNotInitializedException("TFile"); TFile *t = const_cast(this); if(follow_links) { if(stat(name_.c_str(), &(t->statbuf))) throw TFileOperationErrnoException(name_.c_str(), "stat"); } else { if(lstat(name_.c_str(), &(t->statbuf))) throw TFileOperationErrnoException(name_.c_str(), "lstat"); } t->stated = true; num_stated++; } // TDir implementation // size_t TDir::numRecursive(bool low_mem, const char *verbose, bool count_files, bool count_dirs) const { size_t r = 0; if(count_files) r = numFiles(); if(count_dirs) r += numDirs(); if(verbose) { verbose_num += r; if(verbose_num > (old_verbose_num + COUNT_VERBOSE_STEP)) { printf("counting %s in %s: %11u\r", count_files ? (count_dirs ? "files and dirs" : "files") : (count_dirs ? "dirs" : "nothing"), verbose, unsigned(verbose_num)); fflush(stdout); old_verbose_num = verbose_num; } } for(size_t i = 0; i < numDirs(); i++) r += dir(i).numRecursive(low_mem, verbose, count_files, count_dirs); if(low_mem) freeMem(); return r; } void TDir::scan() const { if(scanned) return; assert(subTreeContext); if(name().empty()) throw TNotInitializedException("TDir"); TDir *t = const_cast(this); // maximum depth already reached? if so treat directory as empty if(depth >= subTreeContext->max_depth) { t->scanned = true; return; } // prevent crossing filesystems? if(!subTreeContext->cross_filesystems) { if(subTreeContext->root->device() != device()) { // consider directory empty because it is on a different filesystem than the subtree root t->scanned = true; return; } } // scan directory struct dirent *dire = 0; DIR *tdir = opendir(name().c_str()); if(tdir == 0) throw TFileOperationErrnoException(name().c_str(), "opendir"); size_t dirs_left = hardlinks() - 2; while((dire = readdir(tdir)) != 0) { if((dire->d_name[0] != '.') || (strcmp(".", dire->d_name) && strcmp("..", dire->d_name))) { TFile f((name() + "/") + dire->d_name); if((dirs_left || no_leaf_optimize) && f.isdir()) { t->name2dirid[dire->d_name] = dirs.size(); t->dirs[dirs.size()] = TDir(f, *subTreeContext, depth + 1); dirs_left--; } else { t->name2fileid[dire->d_name] = files.size(); t->files[files.size()] = f; } } } closedir(tdir); t->scanned = true; // update access time invalidateStat(); } size_t TDir::verbose_num = 0; size_t TDir::old_verbose_num = 0; bool TFile::follow_links = false; size_t TFile::num_stated = 0; #ifdef WIN32 bool TDir::no_leaf_optimize = true; #else bool TDir::no_leaf_optimize = false; #endif // global functions tvector findFilesRecursive(const TDir& dir) { // return value tvector r; // add files for(size_t i = 0; i < dir.numFiles(); i++) r.push_back(dir.file(i).name()); // add dirs recursively for(size_t j = 0; j < dir.numDirs(); j++) r += findFilesRecursive(dir.dir(j)); // return list return r; } tvector filterExtensions(const tvector& list, const tvector& extensions, bool remove) { // create lookup map tmap ext; for(size_t i = 0; i < extensions.size(); i++) ext[extensions[i]] = 1; // filter list tvector r; for(size_t j = 0; j < list.size(); j++) { tstring e = list[j]; e.extractFilenameExtension(); if(ext.contains(e) != remove) r.push_back(list[j]); } // return list return r; } void makeDirectoriesIncludingParentsIfNecessary(const tstring& dirname, bool verbose, bool dummy) { // check whether dirname already exists try { TFile f(dirname); // if it exists and is a directory we are done if(f.isdir()) return; } // else we need to create the parent directory and then dirname catch(const TFileOperationErrnoException& e) { if(e.err == ENOENT) { // create parent dir tstring parent = dirname; parent.removeDirSlash(); parent.extractPath(); if(!parent.empty()) makeDirectoriesIncludingParentsIfNecessary(parent, verbose, dummy); // recurse // create dirname if(verbose) printf("creating directory '%s'\n", dirname.c_str()); if(!dummy) if(mkdir(dirname.c_str(), S_IRWXU) == -1) throw TFileOperationErrnoException(dirname.c_str(), "mkdir"); return; } else throw; } // if we get here dirname exists and is not a directory: error throw TFileOperationErrnoException(dirname.c_str(), "makeDirectoriesIncludingParentsIfNecessary", ENOTDIR); } mp3check-0.8.7/tfiletools.h000644 000766 000766 00000021162 11763233643 014720 0ustar00ovov000000 000000 /*GPL*START* * * tfilesync.h - file and directory handling tolls header file * * Copyright (C) 2000 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #ifndef _ngw_tfiletools_h_ # define _ngw_tfiletools_h_ # include # include # include # include # include "tstring.h" # include "texception.h" # include "tmap.h" # include "tvector.h" // history: // 2001: // 25 Jun 2001: created (Dir and File taken from filesync.cc) // // 2007 24 Oct: removed __STRICT_ANSI__ support, fixed dev_t and made it more robust, fixed operator < for TFileInstance // own device type which is a simple 64 bit unsigned integer typedef unsigned long long mydev_t; inline mydev_t dev_t2mydev_t(const dev_t& dev) { # ifdef __APPLE__ assert(sizeof(dev_t) == 4); return *(reinterpret_cast(&dev)); # else assert(sizeof(dev_t) == 8); return *(reinterpret_cast(&dev)); # endif } inline dev_t mydev_t2dev_t(mydev_t dev) { # ifdef __APPLE__ assert(sizeof(dev_t) == 4); unsigned int tmp = dev; return *(reinterpret_cast(&tmp)); # else assert(sizeof(dev_t) == 8); return *(reinterpret_cast(&dev)); # endif } // file instance information (needed for hardlink structure) struct TFileInstance { TFileInstance(mydev_t d = 0, ino_t i = 0): device(d), inode(i) {} mydev_t device; ino_t inode; }; inline bool operator < (const TFileInstance& d1, const TFileInstance& d2) { if(d1.device < d2.device) return true; if(d1.device > d2.device) return false; return d1.inode < d2.inode; } inline bool operator == (const TFileInstance& d1, const TFileInstance& d2) { return (d1.inode == d2.inode) && (d1.device == d2.device); } inline bool operator != (const TFileInstance& d1, const TFileInstance& d2) { return (d1.inode != d2.inode) || (d1.device != d2.device); } class TFile { public: // cons & des TFile(const tstring& fname = tstring()): name_(fname), stated(false) {} ~TFile() {} void invalidateStat() const { const_cast(this)->stated = false; } // read only interface // file types enum FileType { FT_UNKNOWN, FT_REGULAR, FT_DIR, FT_SYMLINK, FT_CHARDEV, FT_BLOCKDEV, FT_FIFO, FT_SOCKET }; // name const tstring& name() const {if(name_.empty()) throw TNotInitializedException("TFile"); return name_;} tstring filename() const {tstring r= name_; r.extractFilename(); return r;} tstring pathname() const {tstring r= name_; r.extractPath(); return r;} // stat fields mydev_t device() const {getstat(); return dev_t2mydev_t(statbuf.st_dev);} ino_t inode() const {getstat(); return statbuf.st_ino;} nlink_t hardlinks() const {getstat(); return statbuf.st_nlink;} uid_t userid() const {getstat(); return statbuf.st_uid;} gid_t groupid() const {getstat(); return statbuf.st_gid;} mydev_t devicetype() const {getstat(); return dev_t2mydev_t(statbuf.st_rdev);} off_t size() const {getstat(); return statbuf.st_size;} time_t atime() const {getstat(); return statbuf.st_atime;} time_t mtime() const {getstat(); return statbuf.st_mtime;} time_t ctime() const {getstat(); return statbuf.st_ctime;} // mode fields mode_t protection() const { getstat(); return statbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); } bool isdir() const { getstat(); return S_ISDIR(statbuf.st_mode); } bool isregular() const { getstat(); return S_ISREG(statbuf.st_mode); } bool issymlink() const { getstat(); return S_ISLNK(statbuf.st_mode); } bool ischardev() const { getstat(); return S_ISCHR(statbuf.st_mode); } bool isblockdev() const { getstat(); return S_ISBLK(statbuf.st_mode); } bool isfifo() const { getstat(); return S_ISFIFO(statbuf.st_mode); } bool issocket() const { getstat(); return S_ISSOCK(statbuf.st_mode); } bool devicetypeApplies() const { return ischardev() || isblockdev(); } mode_t filetypebits() const { return statbuf.st_mode & S_IFMT; } TFileInstance instance() const { return TFileInstance(device(), inode()); } FileType filetype() const; tstring filetypeLongStr() const; char filetypeChar() const; tstring filetypeStr7() const; static void followLinks(bool follow = true) { follow_links = follow; } static size_t numStated() { return num_stated; } private: // private helpers void getstat() const; // private data tstring name_; struct stat statbuf; bool stated; // private static data static bool follow_links; static size_t num_stated; // forbid implicit comparison bool operator==(const TFile&); }; class TDir; class TSubTreeContext { public: TSubTreeContext(bool cross_filesystems, size_t max_depth = 0xffffffff): root(0), cross_filesystems(cross_filesystems), max_depth(max_depth) {} TDir *root; bool cross_filesystems; size_t max_depth; }; class TDir: public TFile { public: TDir(): TFile(), scanned(false), subTreeContext(0), depth(0) {} TDir(const tstring& fname, TSubTreeContext& subTreeContext_, size_t depth = 0): TFile(fname), scanned(false), subTreeContext(&subTreeContext_), depth(depth) { init(); } TDir(const TFile& file_, TSubTreeContext& subTreeContext_, size_t depth = 0): TFile(file_), scanned(false), subTreeContext(&subTreeContext_), depth(depth) { init(); } ~TDir() {} // read only interface const TFile& file(const tstring& fname) const { scan(); if(name2fileid.contains(fname)) return files[name2fileid[fname]]; else throw TNotFoundException(); } const TDir & dir (const tstring& fname) const { scan(); if(name2dirid .contains(fname)) return dirs [name2dirid [fname]]; else throw TNotFoundException(); } const TFile& file(size_t index) const { scan(); if(index >= files.size()) throw TZeroBasedIndexOutOfRangeException(index, files.size()); return files[index];} const TDir & dir (size_t index) const { scan(); if(index >= dirs .size()) throw TZeroBasedIndexOutOfRangeException(index, dirs .size()); return dirs [index];} size_t numFiles() const { scan(); return files.size();} size_t numDirs () const { scan(); return dirs .size();} bool containsFile(const tstring& fname) const { scan(); return name2fileid.contains(fname); } bool containsDir (const tstring& fname) const { scan(); return name2dirid .contains(fname); } bool contains (const tstring& fname) const { return containsDir(fname) || containsFile(fname); } bool isEmpty() const { return dirs.empty() && files.empty(); } void invalidateContents() { freeMem(); } size_t numRecursive(bool low_mem = true, const char *verbose = 0, bool count_files = true, bool count_dirs = true) const; void freeMem() const { if(scanned) { TDir *t = const_cast(this); t->dirs.clear(); t->files.clear(); t->name2fileid.clear(); t->name2dirid.clear(); t->scanned = false; } } static void resetVerboseNum() { old_verbose_num = verbose_num = 0; }; static void noLeafOptimize(bool no_opt) { no_leaf_optimize = no_opt; # ifdef WIN32 no_leaf_optimize = true; # endif } private: // private helpers void scan() const; void init() { assert(subTreeContext); if(subTreeContext->root == 0) subTreeContext->root = this; } // private data bool scanned; tmap name2fileid; tmap files; tmap name2dirid; tmap dirs; TSubTreeContext *subTreeContext; size_t depth; // private static data static size_t verbose_num; static size_t old_verbose_num; static bool no_leaf_optimize; // forbid implicit comparison bool operator==(const TDir&); }; // global functions tvector findFilesRecursive(const TDir& dir); tvector filterExtensions(const tvector& list, const tvector& extensions, bool remove = false); void makeDirectoriesIncludingParentsIfNecessary(const tstring& dirname, bool verbose = false, bool dummy = false); #endif /* _ngw_tfiletools_h_ */ mp3check-0.8.7/tmap.h000644 000766 000766 00000004160 11763233643 013474 0ustar00ovov000000 000000 /*GPL*START* * * tmap<> class template -- improved stl map<> * * Copyright (C) 2000-2001 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #ifndef _ngw_tmap_h_ #define _ngw_tmap_h_ #ifdef DONT_USE_STL # include "ttmap.h" # define tmap_base ttmap #else # include # define tmap_base map using namespace std; #endif #include "texception.h" // history: start 08 Jul 2000 // 2000: // 08 Jul: removing tarray and tassocarray, this should provide tmap and tvector // 2001: // 15 Sep: DONT_USE_STL feature added // 16 Sep: splittet tstl.h into tvector.h and tmap.h template class tmap: public tmap_base { public: // 1:1 wrapper /// access element (read/write) (this is needed for some strange reason) T& operator[](const K& key) { return tmap_base::operator[](key); }; // new functionality /// return whether an element with key is contained or not bool contains(const K& key) const { return this->find(key) != tmap_base::end(); } /// access element read only (const) // g++ 2.95.2 does not allow this: // const T& operator[](const K& key) const { const_iterator i = this->find(key); if(i != end()) return i->second; else throw TNotFoundException(); } // throw(TNotFoundException) const T& operator[](const K& key) const { if(contains(key)) return this->find(key)->second; else throw TNotFoundException(); } // throw(TNotFoundException) }; #endif /* _ngw_tmap_h_ */ mp3check-0.8.7/tregex.cc000644 000766 000766 00000013670 11763233643 014175 0ustar00ovov000000 000000 /*GPL*START* * * encapsulated POSIX regular expression (regex) interface header * * Copyright (C) 1998 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #include "tregex.h" // history: // 1997: // 03:00 Jul 04 created from 'rtest.cc', tested, usable // 03:00 Jul 11 added substring match and substitution // 21:00 Sep 04 added preserve case // 1998: // 22:38 Aug 05 added progmode (number and stderr) // 1999: // 14:58 Jan 31 added pre/post_padstring for highlighting (h=106, cc=237) // 20:58 Oct 14 fixed parameterSubstitution(): in and out may now be the same string // 2000: // 11:50 Jul 09 fatalError removed, replaced with exceptions // 2001: // 21:20 02 Apr v0.9.19: empty RHS caused TZeroBasedIndexOutOfRange bug fixed (226 lines) // global data // 0-9a-z #define MAX_SUBSTRING 36 // ctor & dtor TRegEx::TRegEx(const char *regex, int cflags): preg(), nosub(false) { memset(&preg, 0, sizeof(regex_t)); int error = regcomp(&preg, regex, cflags); if(error) throw TErroneousRegexException(error, preg); if(cflags & REG_NOSUB) nosub = true; } TRegEx::~TRegEx() { regfree(&preg); } // matching bool TRegEx::match(const char *str, int flags) const { return regexec(&preg, str, 0, 0, flags) == 0; // match } bool TRegEx::firstMatch(const char *str, int& start, int& len, int flags) const { if(nosub) throw TNoSubRegexException(); regmatch_t m[1]; if(regexec(&preg, str, 1, m, flags)==0) { // match start = m->rm_so; len = m->rm_eo - m->rm_so; return true; } else { // no match start = len = -1; return false; } } bool TRegEx::allMatches(const char *str, tvector& all, int flags) const { if(nosub) throw TNoSubRegexException(); regmatch_t m[1]; const char *p = str; bool match_ = false; int start, len, off = 0; while(1) { if(regexec(&preg, p, 1, m, flags)) break; match_ = true; start = m->rm_so; len = m->rm_eo - m->rm_so; all += start + off; all += len; if(len==0) break; flags |= REG_NOTBOL; p += start + len; off += start + len; } return match_; } bool TRegEx::allMatchesSubstring(const char *str, tvector >& all, int flags, int progress, int progmode) const { if(nosub) throw TNoSubRegexException(); const char *p = str; bool match_ = false; int start, len, off = 0, j=0; tvector sub; regmatch_t m[MAX_SUBSTRING]; FILE *pout = stdout; if(progress < 0) { pout = stderr; progress = -progress; } if(progmode & P_STDERR) pout = stderr; bool prognum = (progmode & P_NUMBER) > 0; while(1) { memset(m, -1, sizeof(regmatch_t)*MAX_SUBSTRING); if(regexec(&preg, p, MAX_SUBSTRING, m, flags)) break; match_ = true; sub.clear(); for(int i=0; i < MAX_SUBSTRING; i++) { if(m[i].rm_so != -1) { sub += m[i].rm_so + off; sub += m[i].rm_eo - m[i].rm_so; } } start = m->rm_so; len = m->rm_eo - m->rm_so; all += sub; if(len==0) break; flags |= REG_NOTBOL; p += start + len; off += start + len; // show progress if(progress) { if((j%progress)==0) { if(prognum) fprintf(pout, "%6d \r", j); else putc('.', pout); fflush(pout); } j++; } } return match_; } // ascii specific code static int validatePos(char c) { if((c<'0') || (c>'z')) return -1; if((c>'9') && (c<'a')) return -1; if(c>='a') return c-'a'+10; else return c-'0'; } static tstring backslashParamSubstitute(const tstring& org, const tstring& sub, const tvector& occ) { if(sub.len() < 2) return sub; int num = occ.size() / 2; if(num == 0) return sub; size_t i = 0, pos = 0; tstring r; while(i < (sub.len() - 1)) { if(sub[i]=='\\') { char c = sub[i+1]; if(c=='\\') { // protection r += sub.substr(pos, i+1); i += 2; pos = i; } else { int p = validatePos(c); if((p>=0) && (p >& occ, bool preserve_case, int modify_case, int progress, const tstring& pre_padstring, const tstring& post_padstring, tvector *match_pos) { tstring out; int pos = 0; for(size_t i = 0; i0) if((i%progress)==0) {putchar('.');fflush(stdout);} } out += in.substr(pos); outstr = out; } mp3check-0.8.7/tregex.h000644 000766 000766 00000006257 11763233643 014042 0ustar00ovov000000 000000 /*GPL*START* * * encapsulated POSIX regular expression (regex) interface header * * Copyright (C) 1998 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #ifndef _ngw_tregex_h_ #define _ngw_tregex_h_ #include #include #include "tstring.h" class TRegEx { public: // ctor & dtor // cflags: REG_EXTENDED, REG_ICASE, REG_NOSUB, REG_NEWLINE TRegEx(const char *regex, int cflags); ~TRegEx(); // matching: // eflags: REG_NOTBOL, REG_NOTEOL // return true if str matches the regex bool match(const char *str, int eflags=0) const; // return true if str matches the regex and set position bool firstMatch(const char *str, int& start, int& len, int eflags=0) const; // return true if str matches the regex and return all positions // in all: all[0]=start1, all[1]=len1, all[2]=start2, all[3]=len2, ... bool allMatches(const char *str, tvector& all, int eflags=0) const; // same as above but also recognize substrings in all bool allMatchesSubstring(const char *str, tvector >& all, int eflags=0, int progress=0, int progmode=0) const; // constants for allMatchesSubstring(..., progmode) // default: print '.' to stdout // P_STDERR: print to stderr // P_NUMBER: print number instead of a '.' enum {P_STDERR=1, P_NUMBER=2}; // error exceptions class TErroneousRegexException: public TException { TExceptionN(TErroneousRegexException); TErroneousRegexException(int error_, const regex_t& preg_): error(error_), preg(preg_) {} const char *str() const { static char buf[256]; regerror(error, &preg, buf, sizeof(buf)); return buf; } int error; regex_t preg; TExceptionM2("%s (error #%d)", str(), error); }; class TNoSubRegexException: public TException {TExceptionN(TErroneousRegexException);}; private: // private data regex_t preg; bool nosub; // forbid default copy and assignment TRegEx(const TRegEx&); const TRegEx& operator=(const TRegEx&); }; // substitute occurences occ in string with sub and write to out // and use back references in sub from occ (\0 .. \9 \a .. \z) void parameterSubstitution(const tstring& in, tstring& out, const tstring& sub, const tvector >& occ, bool preserve_case=false, int modify_case=0, int progress=0, const tstring& pre_padstring=tstring(), const tstring& post_padstring=tstring(), tvector *match_pos=0); #endif /* tregex.h */ mp3check-0.8.7/tstring.cc000644 000766 000766 00000104106 11763233643 014364 0ustar00ovov000000 000000 /*GPL*START* * * NUL byte safe string implementation * * Copyright (C) 1997-2001 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #include #include #include #include #include "tstring.h" #include "texception.h" // todo: // - make Split,Unquote,ReadLine,extractFilename,extractPath 0 byte safe // - separat functions using tvector<> for better modularity // 1997: // 01:45 11 Jun split(): backslash behavior fixed (601 lines) // 23:50 11 Jun strings may contain 0 bytes // 12:00 19 Jun some filename extracting added // 17:00 19 Jun more sophisticated search: ignore_case and whole_words // 02:00 08 Jul substring extraction via operator() (start,end) // 02:00 31 Jul new ContainsNulChar, new ReadFile, fixed \ \\ in ExpUnPrint // 12:00 08 Aug new Upper Lower Capitalize // 23:30 19 Aug improved collapseSpace() // 00:00 27 Aug cropSpace() bug fixed (1 byte out of bound zero write) // 20:00 30 Aug now cons accept 0 pointer as empty string // 21:00 30 Aug addDirSlash() added (809 lines) // 13:00 02 Sep isLower ... added, preserve_case for SearchReplace added (867) // 23:45 16 Dec normalizePath() added // 15:00 24 Dec started conversion to Rep reference model // 18:00 27 Dec finished. debugging starts ... :) // 1998: // 00:30 09 Jan scanTools started (cc=817) (h=462) // 00:05 12 Jan compare operators fixed (0 byte ...) // 19:00 09 Oct zeroRep and fast string(int i) for i=0 // 14:30 10 Oct xc16emu emuwid.s problem solved: memset() // 14:36 10 Oct string(0) 80 times faster than string(1)! (zero_rep) // 01:53 17 Oct createNulRep and createZeroRep non inline // 1999: // 14:55 31 Jan +=string speedup for empty string (cc=919, h=532) // 15:08 31 Jan searchReplace: pre/post_padstring added // 00:36 03 Feb getFitWordsBlock added (954) // 23:02 04 Feb search/searchReplace match_pos added (954) // 23:49 15 Feb class string renamed to class tstring, tappframe simplified (1003) // 00:46 16 Feb toLong/toDouble/toInt/toBool added (from old str2value.cc) (1016) // 23:51 03 Mar cropSpaceEnd added, getFitWords space semantics change // 23:46 13 Apr trelops.h replaces != and > operator (1034) // 00:31 16 Apr started: replace fatalErrors by exceptions // 23:48 20 Aug remove html tags added // 22:17 09 Dec added operator != and > because trelops will not instantiate them for two different types // 2000: // 23:30 30 Jun loop changed from while(1) to for(;;) ;-) // 22:50 01 Jul toInt/Long pointer p initialized to 0, quotes feature added to expandUnprintable // 22:00 06 Jul progressBar() added // 2001: // 00:15 08 Feb extractPath now removed trailing slash (1090 lines) // 00:45 15 Mar searchReplace max_num parameter added // 22:00 18 Sep palmos fixes // 2002: // 22:25 08 Apr expandUnpritable: allow high ISO graphical characters (ASCII 161-255), better nul_mem and zero_mem sizes for 64 bit systems // 2003: // 22:20 27 Jan length of nul_mem and zero_mem fixed // 2006: // 27 Jul: palmos support removed // global static null and zero rep members tstring::Rep* tstring::Rep::nul = 0; char tstring::Rep::nul_mem[sizeof(Rep) + 1]; tstring::Rep* tstring::Rep::zero = 0; char tstring::Rep::zero_mem[sizeof(Rep) + 2]; // non inline Rep implementations // copy this representation tstring::Rep *tstring::Rep::clone(size_t minmem) { Rep *p = create(minmem >= len ? minmem : len); p->len = len; memcpy(p->data(), data(), len+1); return p; } // create a new representation tstring::Rep *tstring::Rep::create(size_t tmem) { size_t m = sizeof(Rep) << 1; while((m - 1 - sizeof(Rep)) < tmem) m <<= 1; Rep *p = new (m - 1 - sizeof(Rep)) Rep; p->mem = m - 1 - sizeof(Rep); p->ref = 1; p->vulnerable = false; return p; } // create null string representation void tstring::Rep::createNulRep() { nul = (Rep *)nul_mem; nul->len = 0; nul->mem = 0; nul->ref = 1; // never modify/delete static object nul->vulnerable = false; nul->terminate(); } // create zero string representation void tstring::Rep::createZeroRep() { zero = (Rep *)zero_mem; zero->len = 1; zero->mem = 1; zero->ref = 1; // never modify/delete static object zero->vulnerable = false; (*zero)[0] = '0'; zero->terminate(); } // non inline string implelentation tstring::tstring(const char *s):rep(0) { if(s){ int l = strlen(s); rep = Rep::create(l); rep->len = l; strcpy(rep->data(), s); } else rep = Rep::nulRep()->grab(); } tstring::tstring(const char *s, size_t l):rep(0) { if(s && (l > 0)) { rep = Rep::create(l); rep->len = l; memcpy(rep->data(), s, l); rep->terminate(); } else rep = Rep::nulRep()->grab(); } tstring::tstring(char c, size_t n):rep(0) { if(n) { rep = Rep::create(n); rep->len = n; if(n) memset(rep->data(), c, n); rep->terminate(); } else rep = Rep::nulRep()->grab(); } tstring::tstring(char c):rep(0) { rep = Rep::create(1); rep->len = 1; (*rep)[0] = c; rep->terminate(); } tstring::tstring(int i):rep((i==0)?(Rep::zeroRep()->grab()):(Rep::nulRep()->grab())) { if(i) sprintf("%d", i); } tstring::tstring(int i, const char *format):rep(Rep::nulRep()->grab()) { sprintf(format, i); } tstring::tstring(double d, const char *format):rep(Rep::nulRep()->grab()) { sprintf(format, d); } tstring operator + (const tstring& s1, const tstring& s2) { tstring r(s1); r += s2; return r; } tstring operator + (const char *s1, const tstring& s2) { tstring r(s1); r += s2; return r; } tstring operator + (const tstring& s1, const char *s2) { tstring r(s1); r += s2; return r; } tstring operator + (char s1, const tstring& s2) { tstring r(s1); r += s2; return r; } tstring operator + (const tstring& s1, char s2) { tstring r(s1); r += tstring(s2); return r; } bool operator == (const tstring& s1, const tstring& s2) {return tstring::_string_equ(s1, s2);} bool operator == (const tstring& s1, const char *s2) {return (strcmp(s1.c_str(), s2)==0);} bool operator == (const char *s1, const tstring& s2) {return (strcmp(s1, s2.c_str())==0);} bool operator != (const tstring& s1, const tstring& s2) {return !tstring::_string_equ(s1, s2);} bool operator != (const tstring& s1, const char *s2) {return (strcmp(s1.c_str(), s2)!=0);} bool operator != (const char *s1, const tstring& s2) {return (strcmp(s1, s2.c_str())!=0);} bool operator < (const tstring& s1, const tstring& s2) {return (tstring::_string_cmp(s1, s2) < 0);} bool operator < (const tstring& s1, const char *s2) {return (strcmp(s1.c_str(), s2) < 0);} bool operator < (const char *s1, const tstring& s2) {return (strcmp(s1, s2.c_str()) < 0);} bool operator > (const tstring& s1, const char *s2) {return (strcmp(s1.c_str(), s2) > 0);} bool operator > (const char *s1, const tstring& s2) {return (strcmp(s1, s2.c_str()) > 0);} bool operator > (const tstring& s1, const tstring& s2) {return (tstring::_string_cmp(s1, s2) > 0);} /// append string tstring& tstring::operator += (const tstring& a) {if(!a.empty()) {append(a.rep->data(), a.rep->len);} return *this;} /// append cstring tstring& tstring::operator += (const char *a) {if(a) append(a, strlen(a)); return *this;} /// append cstring tstring& tstring::operator += (char c) {detachResize(rep->len + 1); (*rep)[rep->len++]=c; (*rep)[rep->len]=0; return *this;} /// append byte array a of length len tstring& tstring::append(const char *a, int alen) { if(a) { detachResize(rep->len + alen); memcpy(rep->data() + rep->len, a, alen); rep->len += alen; rep->terminate(); } return *this; } /// assign string a to this tstring& tstring::operator = (const tstring& a) {if(&a != this) {rep->release(); rep = a.rep->grab();} return *this;} /// direct character access: const/readonly char tstring::operator [] (size_t i) const /* throw(IndexOutOfRange) */ { if(i <= rep->len) return (*rep)[i]; else return 0; } /// direct character access: read/write char& tstring::operator[](size_t i) { if(i < rep->len) {detach(); return (*rep)[i];} detachResize(i + 1); for(; rep->len <= i; rep->len++) (*rep)[rep->len] = 0; return (*rep)[i]; } /// substring extraction (len=end-start) tstring tstring::substr(size_t start, size_t end) const /* throw(InvalidRange) */ { if((end == npos) || (end > rep->len)) end = rep->len; if(start > rep->len) start = rep->len; if(start > end) start = end; return tstring(rep->data()+start, end-start); } // compare helpers int tstring::_string_cmp(const tstring& s1, const tstring& s2) { int r = memcmp(s1.rep->data(), s2.rep->data(), s1.rep->len <= s2.rep->len ? s1.rep->len : s2.rep->len); if(r) return r; if(s1.rep->len > s2.rep->len) return +1; if(s1.rep->len < s2.rep->len) return -1; return 0; } bool tstring::_string_equ(const tstring& s1, const tstring& s2) { if(s1.rep->len != s2.rep->len) return false; return memcmp(s1.rep->data(), s2.rep->data(), s1.rep->len)==0; } /// detach from string pool, you should never need to call this void tstring::detach() { if(rep->ref > 1) { replaceRep(rep->clone()); } } // no, there is *not* a dangling pointer here (ref > 1) /** detach from string pool and make sure at least minsize bytes of mem are available (use this before the dirty version sprintf to make it clean) (use this before the clean version sprintf to make it fast) */ void tstring::detachResize(size_t minsize) { if((rep->ref==1) && (minsize <= rep->mem)) return; replaceRep(rep->clone(minsize)); } /// detach from string pool and declare that string might be externally modified (the string has become vulnerable) void tstring::invulnerableDetach() { detach(); rep->vulnerable = true; } /// check for 0 in string (then its not a real cstring anymore) bool tstring::containsNulChar() const { rep->terminate(); if(strlen(rep->data()) != rep->len) return true; else return false; } /// get a pointer to the at most max last chars (useful for printf) const char *tstring::pSuf(size_t max) const { return rep->data()+((max>=rep->len)?0:(rep->len-max)); } /// sprintf into this string void tstring::sprintf(const char *format, ...) { va_list ap; int ret = -1; va_start(ap, format); #if defined(__STRICT_ANSI__) // this is the unsecure and dirty but ansi compatible version detachResize(256); ret = vsprintf(rep->data(), format, ap); // not secure! may write out of bounds! #else // this is the clean version (never overflows) int s = 16/4; do { if(ret <= s) s <<= 2; // fast increase, printf may be slow else s = ret + 8; // C99 standard, after first iteration this should be large enough detachResize(s); ret = vsnprintf(rep->data(), s, format, ap); } while((ret == -1) || (ret >= s)); #endif va_end(ap); rep->len = ret; } // returns true on success! returns value in bool_out! bool tstring::toBool(bool& bool_out) const { char buf[7]; int i; for(i=0; i<6; i++) { buf[i] = tolower((*rep)[i]); if((buf[i]==0) || isspace(buf[i])) break; } buf[i]=0; switch(i) { case 1: if((buf[0]=='1')||(buf[0]=='t')) { bool_out = true; return true; } if((buf[0]=='0')||(buf[0]=='f')) { bool_out = false; return true; } break; case 2: if(strcmp(buf,"on")==0) { bool_out = true; return true; } if(strcmp(buf,"no")==0) { bool_out = false; return true; } break; case 3: if(strcmp(buf,"yes")==0) { bool_out = true; return true; } if(strcmp(buf,"off")==0) { bool_out = false; return true; } break; case 4: if(strcmp(buf,"true")==0) { bool_out = true; return true; } break; case 5: if(strcmp(buf,"false")==0) { bool_out = false; return true; } break; } return false; } // returns true on success bool tstring::toLong(long& long_out, int base) const { char *p = 0; long r = strtoul(rep->data(), &p, base); if(p == rep->data()) return false; if(*p) if(!isspace(*p)) return false; long_out = r; return true; } // returns true on success bool tstring::toInt(int& int_out, int base) const { char *p = 0; int r = strtoul(rep->data(), &p, base); if(p == rep->data()) return false; if(*p) if(!isspace(*p)) return false; int_out = r; return true; } // returns true on success bool tstring::toDouble(double& double_out) const { char *p = 0; double r = strtod(rep->data(), &p); if(p == rep->data()) return false; if(*p) if(!isspace(*p)) return false; double_out = r; return true; } tstring tstring::scanToken(size_t& scanner, int flags, const char *allow, const char *forbid, bool allow_quoted) const { if(allow_quoted && (scanner < rep->len)) { char q = (*rep)[scanner]; if((q=='\'')||(q=='\"')) { int st(++scanner); while((scanner < rep->len) && ((*rep)[scanner]!=q)) ++scanner; tstring out = substr(st, scanner); if(scanner < rep->len) ++scanner; return out; } } size_t start(scanner); for(; (scanner < rep->len); ++scanner) { char c = (*rep)[scanner]; if(forbid && strchr(forbid, c)) break; if((flags&ALL )) continue; if(allow && strchr(allow , c)) continue; if((flags&ALPHA) && isalpha(c)) continue; if((flags&DIGIT) && isdigit(c)) continue; if((flags&LOWER) && islower(c)) continue; if((flags&UPPER) && isupper(c)) continue; if((flags&PRINT) && isprint(c)) continue; if((flags&GRAPH) && isgraph(c)) continue; if((flags&CNTRL) && iscntrl(c)) continue; if((flags&SPACE) && isspace(c)) continue; if((flags&XDIGIT)&&isxdigit(c)) continue; if((flags&PUNCT) && ispunct(c)) continue; break; } return substr(start, scanner); } tstring tstring::shortFilename(size_t maxchar) const { if(rep->len <= maxchar) return *this; if(maxchar < 3) return ""; return "..." + substr(rep->len - maxchar + 3); } void tstring::normalizePath() { // split path tvector a = split(*this, "/", false, false); // delete nul dirs for(tvector::iterator i = a.begin(); i != a.end();) { if(i->empty() || (*i == ".")) i = a.erase(i); else i++; } // check for absolute if((*rep)[0]=='/') clear(); else operator=("."); // delete '..' for(tvector::iterator i = a.begin(); i != a.end();) { if((*i == "..") && (i != a.begin())) { i--; if(*i != "..") { i = a.erase(i); i = a.erase(i); } else { i++; i++; } } else i++; } // assemble string if((a.size() > 0) || (len() == 0)) operator+=("/" + join(a, "/")); } void tstring::extractFilename() { const char *p = strrchr(rep->data(), '/'); if(p) operator=(p+1); } void tstring::extractPath() { const char *p = strrchr(rep->data(), '/'); if(p) { truncate((p - rep->data() + 1)); removeDirSlash(); } else clear(); } void tstring::removeDirSlash() { if(*this == "/") return; while(lastChar() == '/') truncate(rep->len-1); } void tstring::addDirSlash() { if(lastChar() != '/') operator += ("/"); } void tstring::extractFilenameExtension() { extractFilename(); // get file name const char *p = strrchr(rep->data(), '.'); if(p) { // contains dot if(p > rep->data()) { // last dot not first char operator=(p+1); // get extension return; } } clear(); // no extension } double tstring::binaryPercentage() const { double bin = 0; for(size_t i = 0; i < rep->len; i++) if((!isprint((*rep)[i])) && (!isspace((*rep)[i]))) bin+=1.0; return (bin * 100.0) / double(rep->len); } bool tstring::isLower() const { if(rep->len == 0) return false; for(size_t i = 0; i < rep->len; i++) if(isalpha((*rep)[i])) if(isupper((*rep)[i])) return false; return true; } bool tstring::isUpper() const { if(rep->len == 0) return false; for(size_t i = 0; i < rep->len; i++) if(isalpha((*rep)[i])) if(islower((*rep)[i])) return false; return true; } bool tstring::isCapitalized() const { if(rep->len == 0) return false; if(isalpha((*rep)[0])) if(islower((*rep)[0])) return false; for(size_t i = 1; i < rep->len; i++) if(isalpha((*rep)[i])) if(isupper((*rep)[i])) return false; return true; } void tstring::lower() { detach(); for(size_t i = 0; i < rep->len; i++) (*rep)[i] = tolower((*rep)[i]); } void tstring::upper() { detach(); for(size_t i = 0; i < rep->len; i++) (*rep)[i] = toupper((*rep)[i]); } void tstring::capitalize() { lower(); if(rep->len) (*rep)[0] = toupper((*rep)[0]); } static const char *bytesearch(const char *mem, int mlen, const char *pat, int plen, bool ignore_case, bool whole_words) { int i,j; for(i=0; i <= mlen-plen; i++) { if(ignore_case) { for(j=0; j 0) if(isalnum(mem[i-1]) || (mem[i-1]=='_')) left_ok = false; if(i < mlen-plen) if(isalnum(mem[i+plen]) || (mem[i+plen]=='_')) right_ok = false; if(left_ok && right_ok) return mem + i; } } } return 0; // not found } int tstring::searchReplace(const tstring& tsearch, const tstring& replace_, bool ignore_case, bool whole_words, bool preserve_case, int progress, const tstring& pre_padstring, const tstring& post_padstring, tvector *match_pos, int max_num) { // get new length and positions if(progress) { putc('S', stderr);fflush(stderr); } int num = search(tsearch, ignore_case, whole_words, progress); if(progress) { putc('R', stderr);fflush(stderr); } if(num==0) { return 0; } if(num >= max_num) num = max_num; int newlen = rep->len + num*(replace_.rep->len-tsearch.rep->len + pre_padstring.len()+post_padstring.len()); // create new string Rep *newrep = Rep::create(newlen); const char *p = rep->data(); // read char *q = newrep->data(); // write const char *r; // found substring int mlen = rep->len; // rest of read mem for(int i=0; i < num; i++) { if(progress>0) if((i%progress)==0) {putc('.', stderr);fflush(stderr);} r = bytesearch(p, mlen, tsearch.rep->data(), tsearch.rep->len, ignore_case, whole_words); memcpy(q, p, r-p); // add skipped part q += r-p; if(match_pos) (*match_pos) += int(q-newrep->data()); // enter start memcpy(q, pre_padstring.rep->data(), pre_padstring.rep->len); // add pre pad q += pre_padstring.len(); if(!preserve_case) { // add replaced part memcpy(q, replace_.rep->data(), replace_.rep->len); } else { tstring rr(preserveCase(tstring(r, tsearch.rep->len), replace_.rep->data())); memcpy(q, rr.rep->data(), rr.rep->len); } q += replace_.rep->len; memcpy(q, post_padstring.rep->data(), post_padstring.rep->len); // add post pad q += post_padstring.len(); if(match_pos) (*match_pos) += int(q-newrep->data()); // enter end mlen -= r-p; mlen -= tsearch.rep->len; p = r + tsearch.rep->len; } memcpy(q, p, mlen); // add rest replaceRep(newrep); rep->len = newlen; rep->terminate(); return num; } int tstring::search(const tstring& pat, bool ignore_case, bool whole_words, int progress, tvector *match_pos) const { if(pat.empty()) return -1; int num=0; int mlen=rep->len; const char *q; for(const char *p = rep->data(); (q=bytesearch(p, mlen, pat.rep->data(), pat.rep->len, ignore_case, whole_words)); num++) { if(match_pos) (*match_pos) += int(q-rep->data()); mlen -= q-p; mlen -= pat.rep->len; p = q + pat.rep->len; if(match_pos) (*match_pos) += int(p-rep->data()); if(progress>0) if((num%progress)==0) {putc('.', stderr);fflush(stderr);} } return num; } /// replace substring void tstring::replace(size_t start, size_t len_, const tstring &str) { if(start > length()) return; if(start + len_ > length()) return; if(str.length() > len_) detachResize(length() + str.length() - len_); else detach(); if(str.length() != len_) memmove(rep->data() + start + str.length(), rep->data() + start + len_, length() - start - len_); // insert string memcpy(rep->data() + start, str.data(), str.length()); // fix length rep->len += str.length() - len_; rep->terminate(); } bool tstring::hasPrefix(const tstring& pref) const { if(pref.rep->len > rep->len) return false; return memcmp(rep->data(), pref.rep->data(), pref.rep->len)==0; } bool tstring::hasSuffix(const tstring& suf) const { if(suf.rep->len > rep->len) return false; return memcmp(rep->data() + (rep->len - suf.rep->len), suf.rep->data(), suf.rep->len)==0; } bool tstring::consistsOfSpace() const { for(size_t i = 0; i < rep->len; i++) { if(!isspace((*rep)[i])) return false; } return true; } void tstring::truncate(size_t max) { if(max < rep->len) { detach(); rep->len = max; rep->terminate(); } } void tstring::replaceUnprintable(bool only_ascii) { for(size_t i = 0; i < rep->len; i++) { unsigned char& c = (unsigned char &)(*rep)[i]; if(!isprint(c)) { if(c < ' ') { c = '!'; } else if(only_ascii || (c < 0xa0)) { c = '?'; } } } } void tstring::unquote(bool allow_bslash, bool crop_space) { detach(); char *p=rep->data(); char *q=rep->data(); char quote=0; char *nonspace=rep->data(); if(crop_space) while(isspace(*p)) p++; for(; *p; p++) { if(allow_bslash && *p=='\\') { if(p[1] == quote) { p++; if(*p == 0) break; } } else { if(quote) { if(*p == quote) { quote = 0; continue; } } else { if((*p == '\'') || (*p == '\"')) { quote = *p; continue; } } } if(quote || (!isspace(*p))) nonspace = q; *(q++) = *p; } *q = 0; if(crop_space) if(*nonspace) nonspace[1] = 0; rep->len = strlen(rep->data()); } tstring tstring::getFitWordsBlock(size_t max) { tstring r = getFitWords(max); size_t spaces; size_t fill = max - r.len(); if(fill > 8) return r; size_t i,j; for(i = 0; i < r.len(); i++) if(r[i] != ' ') break; for(spaces = 0; i < r.len(); i++) if(r[i] == ' ') spaces++; if(fill > spaces) return r; tstring t; t.detachResize(max); for(i = 0, j = 0; i < r.len(); i++) { if(r[i] != ' ') break; (*(t.rep))[j++] = r[i]; } for(; i < r.len(); i++) { if((fill > 0)&&(r[i] == ' ')) { (*(t.rep))[j++] = ' '; (*(t.rep))[j++] = ' '; fill--; } else (*(t.rep))[j++] = r[i]; } t.rep->len = j; t.rep->terminate(); return t; } void tstring::cropSpaceEnd() { int e = rep->len; if(e == 0) return; else e--; while((e >= 0) && isspace((*rep)[e])) e--; truncate(e+1); } tstring tstring::getFitWords(size_t max) { if(max < 1) return tstring(); tstring r(*this); // return value // check for lf size_t lf = firstOccurence('\n'); if((lf != npos) && (lf <= max)) { operator=(substr(lf + 1)); r.truncate(lf); r.cropSpaceEnd(); return r; } // string fits if(rep->len <= max) { clear(); r.cropSpaceEnd(); return r; } // find space size_t last_space = npos; for(size_t i = 0; i <= max; i++) { if((*rep)[i] == ' ') last_space = i; } if(last_space == npos) last_space = max; // return r.truncate(last_space); while(isspace((*rep)[last_space])) last_space++; operator=(substr(last_space)); r.cropSpaceEnd(); return r; } void tstring::expandUnprintable(char quotes) { Rep *newrep = Rep::create(rep->len*4); char *q = newrep->data(); // write char *p = rep->data(); // read size_t l = 0; // expand each char for(size_t j = 0; j < rep->len; ++j, ++p) { if(isprint(*p) || (((unsigned char)*p) > 160)) { // printable --> print if((*p=='\\') || (quotes && (*p==quotes))) { // backslashify backslash and quotes *(q++) = '\\'; l++; } *(q++) = *p; l++; } else { // unprintable --> expand *(q++) = '\\'; // leading backslash l++; switch(*p) { case '\a': *(q++) = 'a'; l++; break; case '\b': *(q++) = 'b'; l++; break; case '\f': *(q++) = 'f'; l++; break; case '\n': *(q++) = 'n'; l++; break; case '\r': *(q++) = 'r'; l++; break; case '\t': *(q++) = 't'; l++; break; case '\v': *(q++) = 'v'; l++; break; default: // no single char control unsigned int i = (unsigned char)*p; l += 3; if(i < 32) { // print lower control octal if(isdigit(p[1])) { q += ::sprintf(q, "%03o", i); } else { q += ::sprintf(q, "%o", i); if(i>=8) --l; else l-=2; } } else { // print octal or hex if(isxdigit(p[1])) { q += ::sprintf(q, "%03o", i); } else { q += ::sprintf(q, "x%02x", i); } } } } } // end replaceRep(newrep); rep->len = l; rep->terminate(); } void tstring::backslashify() { Rep *newrep = Rep::create(rep->len*2); char *p = rep->data(); char *q = newrep->data(); int l = 0; // backslashify each char for(size_t i = 0; i < rep->len; i++, p++) { switch(*p) { case '\\': *(q++) = '\\'; *(q++) = '\\'; l+=2; break; case '\'': *(q++) = '\\'; *(q++) = '\''; l+=2; break; case '\"': *(q++) = '\\'; *(q++) = '\"'; l+=2; break; default: *(q++) = *p; l++; break; } } // end replaceRep(newrep); rep->len = l; rep->terminate(); } void tstring::compileCString() { detach(); char *p = rep->data(); // read char *q = rep->data(); // write char c; // tmp char size_t l = 0; // write size_t i = 0; // read while(i < rep->len) { c = *(p++); // read char i++; if(c == '\\') { // compile char if(i>=rep->len) break; c = *(p++); i++; switch(c) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'x': // hex char *qq; c = strtol(p, &qq, 16); i += qq-p; p = qq; break; case '0': // octal case '1': case '2': case '3': case '4': case '5': case '6': case '7': char buf[4]; buf[0] = c; buf[1] = *p; buf[2] = (i < rep->len) ? p[1] : 0; buf[3] = 0; char *t; c = strtol(buf, &t, 8); i += (t-buf)-1; p += (t-buf)-1; break; } } *(q++) = c; // write char l++; } rep->len = l; rep->terminate(); } void tstring::removeHTMLTags(int& level) { detach(); char *p = rep->data(); // read char *q = rep->data(); // write size_t l = 0; // write size_t i = 0; // read while(i < rep->len) { switch(*p) { case '<': level++; break; case '>': if(level > 0) level--; break; default: if(level == 0) { *(q++) = *p; l++; } } p++; i++; } rep->len = l; rep->terminate(); } void tstring::cropSpace(void) { size_t first = rep->len; size_t last = 0; size_t i; // get first nonspace for(i = 0; i < rep->len; ++i) if(!isspace((*rep)[i])) { first = i; break; } // full of spaces if(first == rep->len) { clear(); return; } // get last nonspace for(i = rep->len - 1; i >= first; --i) if(!isspace((*rep)[i])) { last = i; break; } ++last; // truncate if(first == 0) { truncate(last); return; } // extract substring operator=(substr(first, last)); } void tstring::collapseSpace(void) { detach(); char *p = rep->data(); // read char *q = rep->data(); // write char last_char = ' '; size_t l = 0; // length char c; for(size_t i = 0; i < rep->len; ++i, ++p) { if((!isspace(*p)) || (!isspace(last_char))) { c = *p; if(isspace(c)) c=' '; *(q++) = c; last_char = c; l++; } } if(isspace(last_char)&&(l>0)) --l; rep->len = l; rep->terminate(); } void tstring::translateChar(char from, char to) { detach(); char *p = rep->data(); for(size_t i = 0; i < rep->len; ++i, ++p) if(*p == from) *p = to; } size_t tstring::firstOccurence(char c) const { size_t i; for(i = 0; (i < rep->len) && ((*rep)[i] != c); ++i) /* empty body */; if(i < rep->len) return i; else return npos; } // non member implementation tvector split(const tstring &s, const char *sep, bool allow_quoting, bool crop_space) { tvector r; tstring buf; const char *p = s.c_str(); p--; // bias do { // next chunk p++; // collect chars to buf while(*p) { if(strchr(sep, *p)) { break; } else if(!allow_quoting) { buf += *(p++); } else if(*p=='\\') { p++; if(strchr(sep, *p)==0) buf += '\\'; if(*p) buf += *(p++); } else if(*p=='\'') { buf += '\''; for(p++; *p && *p!='\''; p++) { if(*p=='\\') { p++; buf += '\\'; if(*p) buf += *p; } else buf += *p; } buf += '\''; if(*p=='\'') p++; } else if(*p=='\"') { buf += '\"'; for(p++; *p && *p!='\"'; p++) { if(*p=='\\') { p++; buf += '\\'; if(*p) buf += *p; } else buf += *p; } buf += '\"'; if(*p=='\"') p++; } else { buf += *(p++); } } // put buf to r if(crop_space) buf.cropSpace(); r.push_back(buf); // cleanup buf.clear(); } while(*p); return r; } tstring join(const tvector& a, const tstring& sep) { tstring r; if(a.empty()) return r; else r = a[0]; for(size_t i = 1; i < a.size(); i++) { r += sep; r += a[i]; } return r; } tstring preserveCase(const tstring& from, const tstring& to) { tstring r(to); if(from.len() == to.len()) { // same len for(size_t i = 0; i < r.len(); i++) { if(islower(from[i])) r[i] = tolower(r[i]); else if(isupper(from[i])) r[i] = toupper(r[i]); } } else { // some heuristics if(from.isLower()) r.lower(); if(from.isUpper()) r.upper(); if(from.isCapitalized()) r.capitalize(); } return r; } const char *progressBar(const char *message, unsigned int n, unsigned int max, int width) { // max size of a buffer #define size 1024 // number of static buffers (must be power of two) #define numbuf 4 static char tbuf[size * numbuf]; static int tphase = 0; static int phase = 0; static char phasechar[] = "/-~-_-\\|"; tphase++; tphase &= numbuf - 1; char *buf = tbuf + size * tphase; // limit width if(width >= size) width = size - 1; if(message == 0) { // clear line sprintf(buf, "%*s", width, ""); return buf; } if(max == 0) { // open end progress if(phasechar[phase] == 0) phase = 0; sprintf(buf, "%.*s %11d %c", width - (11 - 3), message, n, phasechar[phase++]); return buf; } // proportional progress // get num chars for number and max int nlen = 0, i; for(i = max; i; i /= 10, nlen++) /* empty body */; int l = sprintf(buf, "%.*s %*d/%*d (%5.1f%%) ", width - (12 + 2 * nlen), message, nlen, n, nlen, max, double(n)/double(max)*100.0); int rest = width - l; if(rest <= 0) return buf; int done = int(double(n)/double(max)*double(rest)); if(done > rest) done = rest; char *p = buf + l; for(i = 0; i < done; i++) *(p++) = '*'; for(; i < rest; i++) *(p++) = '.'; *p = 0; return buf; #undef size } bool tstring::readLine(FILE *file) { char buf[1024]; clear(); for(;;) { buf[sizeof(buf)-2] = '\n'; if(!fgets(buf, sizeof(buf), file)) break; operator+=(buf); if(buf[sizeof(buf)-2] == '\n') break; } if(rep->len) return true; else return false; } size_t tstring::write(FILE *file) const { return fwrite(rep->data(), 1, rep->len, file); } size_t tstring::read(FILE *file, size_t l) { rep->release(); rep = Rep::create(l); int r = fread(rep->data(), 1, l, file); rep->len = r; rep->terminate(); return r; } int tstring::readFile(const char *filename) { struct stat buf; if(stat(filename, &buf)) return -1; // does not exist FILE *f=fopen(filename, "rb"); if(f == 0) return -2; // no permission? int r = read(f, buf.st_size); fclose(f); if(r != buf.st_size) return -3; // read error return 0; } int tstring::writeFile(const char *filename) { FILE *f = fopen(filename, "wb"); if(f == 0) return -2; // no permission? int r = write(f); fclose(f); if(r != int(length())) return -3; // write error return 0; } tvector loadTextFile(const char *fname) { FILE *f = fopen(fname, "r"); if(f==0) throw TFileOperationErrnoException(fname, "fopen(mode='r')", errno); tvector r; for(size_t i = 0; r[i].readLine(f); i++) /* empty body */; fclose(f); r.pop_back(); return r; } tvector loadTextFile(FILE *file) { tvector r; for(size_t i = 0; r[i].readLine(file); i++) /* empty body */; r.pop_back(); return r; } mp3check-0.8.7/tstring.h000644 000766 000766 00000037465 11763233643 014243 0ustar00ovov000000 000000 /*GPL*START* * * tstring - NUL byte tolerant sophisticated string class * * Copyright (C) 1997-2001 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #ifndef _ngw_tstring_h_ #define _ngw_tstring_h_ #include #include #include #include #include #include "tvector.h" #include "texception.h" using namespace std; /**@name null tolerant string class */ /*@{*/ /// null tolerant string class class tstring { public: // invalid iterator static const size_t npos = static_cast(-1); // flags for scanToken() enum {ALPHA=1, NUM=2, DIGIT=2, LOWER=4, UPPER=8, PRINT=16, XDIGIT=32, SPACE=64, ALNUM=1|2, PUNCT=128, CNTRL=256, GRAPH=1024, ALL=2048, NONE=0}; /// case flags for modify case enum {NOT=0, CAPITALIZE=-1}; private: // internal string representation class Rep { public: size_t len; // length without term 0 byte size_t mem; // allocated mem without term 0 byte int ref; // reference count (>=1) bool vulnerable; // true == always grab by clone, never by reference // (the string has become vulnerable to the outside) // char data[mem+1]; string data follows (+1 for term 0 byte) // return pointer to string data char *data() {return (char *)(this + 1);} // 'this + 1' means 'the byte following this object' // character access char& operator[] (size_t i) {return data()[i];} // reference Rep* grab() {if(vulnerable) return clone(); ++ref; return this;} // dereference void release() {if(--ref == 0) delete this;} // copy this representation Rep *clone(size_t minmem = 0); // terminate string with 0 byte void terminate() {*(data()+len) = 0;} // set term 0 byte // static methods // operator new for this class static void * operator new (size_t size, size_t tmem) { return ::operator new (size + tmem + 1);} static void operator delete (void *p, size_t) { ::operator delete (p); } // create a new representation static Rep *create(size_t tmem); // return pointer to the null string representation static Rep * nulRep() {if(nul == 0) createNulRep(); return nul;} // return pointer to the zero string representation (string conatining a literal 0: "0" (and not "\0")) static Rep * zeroRep() {if(zero == 0) createZeroRep(); return zero;} // create null string representation static void createNulRep(); // create zero string representation static void createZeroRep(); private: // static null string ("") representation static Rep* nul; static char nul_mem[]; // static zero string ("0") representation static Rep* zero; static char zero_mem[]; // forbid assignement Rep& operator=(const Rep&); }; public: /**@name constructor & destructor */ /*@{*/ /// default construction tstring(): rep(Rep::nulRep()->grab()) {} /// copy construction tstring(const tstring& a):rep(a.rep->grab()) {} /// init from cstring tstring(const char *s); /// extract bytearray s of length len tstring(const char *s, size_t len); /// create string of chars c with length n explicit tstring(char c, size_t n); /// char to string conversion explicit tstring(char c); /// int to string conversion explicit tstring(int i); /// int to string conversion with format explicit tstring(int i, const char *format); /// double to string conversion explicit tstring(double d, const char *format = "%g"); /// destructor ~tstring() {rep->release();} /*@}*/ /**@name main interface */ /*@{*/ /// return length in bytes size_t len() const {return rep->len;} /// return length in bytes size_t length() const {return rep->len;} /// return length in bytes size_t size() const {return rep->len;} /// clear string void clear() {replaceRep(Rep::nulRep()->grab());} /// explicit conversion to c string // const char *operator*() const {return rep->data();} /// explicit conversion to c string const char *c_str() const {return rep->data();} /// explicit conversion to c string const char *data() const { return rep->data();} /// direct raw data access: user with caution char *rawdata() { invulnerableDetach(); return rep->data(); } /// return true if string is empty, else false bool empty() const {return rep->len == 0;} /// append string tstring& operator += (const tstring& a); /// append cstring tstring& operator += (const char *a); /// append cstring tstring& operator += (char c); /// append byte array a of length len tstring& append(const char *a, int alen); /// assign string a to this tstring& operator = (const tstring& a); /// direct character access: const/readonly char operator [] (size_t i) const; /// direct character access: read/write char& operator [] (size_t i); /// substring extraction (len=end-start) tstring substr(size_t start, size_t end = npos) const; /// ASCII to number conversion bool toLong(long& long_out, int base = 0) const; bool toInt(int& int_out, int base = 0) const; int getInt(int base = 0) const { int i = 0; toInt(i, base); return i; } bool toDouble(double& double_out) const; bool toBool(bool& bool_out) const; /*@}*/ /**@name scanning */ /*@{*/ /// return a scanned token with scanner tstring scanToken(size_t& scanner, int flags, const char *allow=0, const char *forbid=0, bool allow_quoted=false) const; /// scan a token or quoted string to out with scanner tstring scanString(size_t& scanner, int flags, const char *allow=0, const char *forbid=0) const { return scanToken(scanner, flags, allow, forbid, true);} /// scan a token up to char upto tstring scanUpTo(size_t& scanner, char upto) const { int start(scanner); while((scanner < rep->len)&&((*rep)[scanner]!=upto)) ++scanner; return substr(start, scanner);} /// scan a token to out up to chars upto tstring scanUpTo(size_t& scanner, const char *upto) const { int start(scanner); while((scanner < rep->len)&&(strchr(upto, (*rep)[scanner])==0)) ++scanner; return substr(start, scanner);} /// return the rest of the scanned string tstring scanRest(size_t& scanner) const {if(scanner < rep->len) { int start(scanner);scanner=rep->len;return substr(start, scanner); } return tstring();} /// skip spaces void skipSpace(size_t& scanner) const {while((scanner < rep->len)&&isspace((*rep)[scanner]))++scanner;} /// perhaps skip one char c void perhapsSkipOneChar(size_t& scanner, char c) const {if((scanner < rep->len)&&((*rep)[scanner]==c)) ++scanner;} /// return true if the end of string (eos) is reached bool scanEOS(size_t scanner) const {if(scanner >= rep->len) return true; else return false;} /// return the last character in the string or 0 if empty char lastChar() const {return rep->len?(*rep)[rep->len-1]:0;} /// return the first character in the string or 0 if empty char firstChar() const {return (*rep)[0];} /// return true if entire string consists of whitespace bool consistsOfSpace() const; /// return true if string has prefix bool hasPrefix(const tstring& prefix) const; /// return true if string has suffix bool hasSuffix(const tstring& suffix) const; /// return index of first occurence of char c or npos if not found size_t firstOccurence(char c) const; /// check whether char is contained or not bool contains(char c) const { return firstOccurence(c) != npos; } /// remove whitespace at beginning and end void cropSpace(); /// remove whitespace at end void cropSpaceEnd(); /// collapse whitespace void collapseSpace(); /// replace char from with char to void translateChar(char from, char to); /// expand unprintable chars to C-style backslash sequences void expandUnprintable(char quotes = 0); /// backslashify backslash and quotes void backslashify(); /// compile C-style backslash sequences back to unprintable chars void compileCString(); /// truncate to maximal length max void truncate(size_t max); /// replace unprintable characters for safe printing void replaceUnprintable(bool only_ascii = true); /** remove quotes @param allow_bslash true == backslashing allowed to protect quotes @param crop_space true == remove leading/trailing spaces not protected by quotes */ void unquote(bool allow_bslash = true, bool crop_space = true); /// return and remove the first words that fit into a string of length max tstring getFitWords(size_t max); // throw(InvalidWidth); /// remove the first words that fit into a string of length max and return in block format tstring getFitWordsBlock(size_t max); // throw(InvalidWidth); /// remove html tags (level == number of open brakets before call, init:0) void removeHTMLTags(int& level); /*@}*/ /**@name search/replace */ /*@{*/ /// replace substring search with replace, return number of replacements (not regexp, use TRegEx to match regular expressions) int searchReplace(const tstring& search, const tstring& replace, bool ignore_case=false, bool whole_words=false, bool preserve_case=false, int progress=0, const tstring& pre_padstring=tstring(), const tstring& post_padstring=tstring(), tvector *match_pos=0, int max_num = INT_MAX); /// return number of occurences of pat (not regexp) returns -1 on empty pat int search(const tstring& pat, bool ignore_case=false, bool whole_words=false, int progress=0, tvector *match_pos=0) const; // throw(StringIsEmpty); /// replace substring void replace(size_t start, size_t len, const tstring &str); /*@}*/ /**@name file I/O */ /*@{*/ /// read line from file like fgets, no line length limit bool readLine(FILE *file); /// write string to file, return number of bytes written size_t write(FILE *file) const; /// read len bytes from file to string, return bytes read size_t read(FILE *file, size_t len); // throw(InvalidWidth); /// read whole file into one string, return 0 on success -x on error int readFile(const char *filename); /// write string into file, return 0 on success -x on error int writeFile(const char *filename); /*@}*/ /**@name filename manipulation */ /*@{*/ /// remove leading path from filename void extractFilename(); /// remove part after last slash void extractPath(); /// add a slash at the end if it is missing void addDirSlash(); /// remove last char if last char is a slash void removeDirSlash(); /// extract part after the last dot (empty string if no extension, leading dot is ignored) void extractFilenameExtension(); /// make paths comparable (kill multislash, dots and resolve '..') void normalizePath(); /// check for absolute path bool isAbsolutePath() const {if((*rep)[0]=='/') return true; return false;} /// get truncated filename (for printing puroses) tstring shortFilename(size_t maxchar) const; /*@}*/ /**@name misc */ /*@{*/ /// get percentage of nonprintable and nonspace chars (0.0 .. 100.0) double binaryPercentage() const; /// check for 0 in string (then its not a real cstring anymore) bool containsNulChar() const; /// get a pointer to the at most max last chars (useful for printf) const char *pSuf(size_t max) const; /// sprintf into this string void sprintf(const char *format, ...); /*@}*/ /**@name case */ /*@{*/ /// convert to lower case void lower(); /// convert to upper case void upper(); /// convert to lower case, first char upper case void capitalize(); /// check for lower case, empty string returns false bool isLower() const; /// check for upper case, empty string returns false bool isUpper() const; /// check for capitalized case, empty string returns false bool isCapitalized() const; /*@}*/ public: /**@name detach methods */ /*@{*/ /// detach from string pool, you should never need to call this void detach(); // no, there is *not* a dangling pointer here (ref > 1) /** detach from string pool and make sure at least minsize bytes of mem are available (use this before the dirty version sprintf to make it clean) (use this before the clean version sprintf to make it fast) */ void detachResize(size_t minsize); /// detach from string pool and declare that string might be externally modified (the string has become vulnerable) void invulnerableDetach(); /*@}*/ private: // hidden string representation Rep *rep; // private methods void replaceRep(Rep *p) {rep->release(); rep = p;} public: // compare helpers static int _string_cmp(const tstring& s1, const tstring& s2); static bool _string_equ(const tstring& s1, const tstring& s2); }; /**@name concat operators */ /*@{*/ /// tstring operator + (const tstring& s1, const tstring& s2); /// tstring operator + (const char *s1, const tstring& s2); /// tstring operator + (const tstring& s1, const char *s2); /// tstring operator + (char s1, const tstring& s2); /// tstring operator + (const tstring& s1, char s2); /*@}*/ /**@name compare operators */ /*@{*/ /// bool operator == (const tstring& s1, const tstring& s2); /// bool operator == (const tstring& s1, const char *s2); /// bool operator == (const char *s1, const tstring& s2); /// bool operator != (const tstring& s1, const tstring& s2); /// bool operator != (const tstring& s1, const char *s2); /// bool operator != (const char *s1, const tstring& s2); /// bool operator < (const tstring& s1, const tstring& s2); /// bool operator < (const tstring& s1, const char *s2); /// bool operator < (const char *s1, const tstring& s2); /// bool operator > (const tstring& s1, const char *s2); /// bool operator > (const char *s1, const tstring& s2); /// bool operator > (const tstring& s1, const tstring& s2); /*@}*/ /**@name misc friends and nonmembers */ /*@{*/ /// split string into pieces by characters in c-str separator tvector split(const tstring& s, const char *separator, bool allow_quoting=false, bool crop_space=false); /// join, reverse the effect of split tstring join(const tvector& a, const tstring& separator); /// try to preserve case from 'from' to 'to' and return altered 'to' with case from 'from' tstring preserveCase(const tstring& from, const tstring& to); /// modify case inline tstring modifyCase(const tstring& s, int _case) { tstring r(s); switch(_case) { case tstring::UPPER: r.upper(); break; case tstring::LOWER: r.lower(); break; case tstring::CAPITALIZE: r.capitalize(); break; default: break; } return r; } /// Create progress bar const char *progressBar(const char *message = 0, unsigned int n = 0, unsigned int max = 0, int width = 79); /// load text file to array of strings tvector loadTextFile(const char *fname); /// load text file to array of strings tvector loadTextFile(FILE *file); /*@}*/ /*@}*/ #endif /* _ngw_tstring_h_ */ mp3check-0.8.7/tvector.h000644 000766 000766 00000005150 11763233643 014221 0ustar00ovov000000 000000 /*GPL*START* * * tvector<> class template -- improved stl vector<> * * Copyright (C) 2000-2001 by Johannes Overmann * * 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *GPL*END*/ #ifndef _ngw_tvector_h_ #define _ngw_tvector_h_ #ifdef DONT_USE_STL #include "ttvector.h" #define tvector_base ttvector #else # include # define tvector_base vector using namespace std; #endif #include "texception.h" // history: start 08 Jul 2000 // 2000: // 08 Jul: removing tarray and tassocarray, this should provide tmap and tvector // 2001: // 15 Sep: DONT_USE_STL feature added // 16 Sep: splittet tstl.h into tvector.h and tmap.h // 20 Sep: clear() added template class tvector: public tvector_base { public: // 1:1 wrapper /// create an empty vector tvector():tvector_base() {} /// create a vector with n elements tvector(size_t n):tvector_base(n) {} /// create a vector with n copies of t tvector(size_t n, const T& t):tvector_base(n, t) {} // new functionality /// append an element to the end const tvector& operator += (const T& a) { this->push_back(a); return *this; } /// append another tvector to the end const tvector& operator += (const tvector& a) { this->insert(tvector_base::end(), a.tvector_base::begin(), a.tvector_base::end()); return *this; } /// direct read only access, safe const T& operator[](size_t i) const { if(i < tvector_base::size()) return tvector_base::operator[](i); else throw TZeroBasedIndexOutOfRangeException(i, tvector_base::size()); } // throw(TZeroBasedIndexOutOfRangeException); /// direct read/write access, automatically create new elements T& operator[](size_t i) { if(i >= tvector_base::size()) operator+=(tvector(i - tvector_base::size() + 1)); return tvector_base::operator[](i); } /// clear vector void clear() { this->erase(tvector_base::begin(), tvector_base::end()); } }; #endif /* _ngw_tvector_h_ */