mirror of https://github.com/dnomd343/mobi-meta
Kin-Wai Koo
13 years ago
commit
3641e2586a
24 changed files with 3478 additions and 0 deletions
@ -0,0 +1,22 @@ |
|||
Copyright (c) 2012 Kin-Wai Koo |
|||
|
|||
Permission is hereby granted, free of charge, to any person |
|||
obtaining a copy of this software and associated documentation |
|||
files (the ÒSoftwareÓ), to deal in the Software without |
|||
restriction, including without limitation the rights to use, |
|||
copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following |
|||
conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be |
|||
included in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED ÒAS ISÓ, WITHOUT WARRANTY OF ANY KIND, |
|||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
|||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
|||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
|||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
|||
OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,41 @@ |
|||
You will need to have Java (JDK or JRE) 1.5 or above to execute this program. |
|||
|
|||
To start the program, just double-click on the MobiMetaEditorV0.15.jar file. |
|||
|
|||
If that doesn't work, type this from the command-line: |
|||
java -jar MobiMetaEditorV0.15.jar |
|||
|
|||
This was written based on the MOBI file format describe in: |
|||
http://wiki.mobileread.com/wiki/MOBI |
|||
|
|||
This application is licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php). |
|||
|
|||
|
|||
ChangeLog |
|||
|
|||
v0.15 |
|||
- added the ability to add/edit the TTS EXTH record |
|||
|
|||
v0.14 |
|||
- does not pack the header if the full name field and EXTH records remain unchanged |
|||
- added Open and Save menu items |
|||
- lets the user specify the target filename |
|||
|
|||
v0.13 |
|||
- added a Header Info dialog, which details various fields in the PDB header, PalmDOC header, and MOBI header |
|||
- added in provisions to invoke debug and safe modes from the command line |
|||
- added in WhisperPrep, which lets users set ASINs and sets the CDEType to EBOK |
|||
(to invoke, type "java -cp MobiMetaEditorV0.13.jar cli.WhisperPrep |
|||
<input directory> <output directory>") |
|||
|
|||
v0.12 |
|||
- changed the GUI to use FileDialog instead of JFileChooser for a more native look and feel |
|||
- added support for window modified indicator on OS X |
|||
- lets the user specify an input filename on the command line |
|||
|
|||
v0.11 |
|||
- fixed some MobiHeader size calculation bugs |
|||
- added facilities to edit the language fields |
|||
|
|||
v0.10 |
|||
- initial release |
@ -0,0 +1,175 @@ |
|||
package cli; |
|||
|
|||
import java.util.List; |
|||
import java.io.BufferedReader; |
|||
import java.io.File; |
|||
import java.io.IOException; |
|||
import java.io.InputStreamReader; |
|||
import java.util.LinkedList; |
|||
|
|||
import mobimeta.*; |
|||
|
|||
public class WhisperPrep |
|||
{ |
|||
private File inDir; |
|||
private File outDir; |
|||
private BufferedReader in; |
|||
|
|||
public final static void main(String[] args) |
|||
{ |
|||
if (args.length != 2) printUsage(); |
|||
|
|||
File inDir = new File(args[0]); |
|||
File outDir = new File(args[1]); |
|||
|
|||
if (!inDir.exists() || !inDir.isDirectory()) |
|||
printUsage("Input directory " + args[0] + " does not exist."); |
|||
|
|||
if (!outDir.exists() || !outDir.isDirectory()) |
|||
printUsage("Output directory " + args[1] + " does not exist."); |
|||
|
|||
if (inDir.getAbsolutePath().equals(outDir.getAbsolutePath())) |
|||
printUsage("Input and output directories cannot be the same."); |
|||
|
|||
WhisperPrep wp = new WhisperPrep(inDir, outDir); |
|||
wp.start(); |
|||
} |
|||
|
|||
private static void printUsage() |
|||
{ |
|||
printUsage(null); |
|||
} |
|||
|
|||
private static void printUsage(String message) |
|||
{ |
|||
if (message != null) System.err.println(message); |
|||
System.err.println("Usage: WhisperPrep <input directory> <output directory>"); |
|||
System.exit(0); |
|||
} |
|||
|
|||
public WhisperPrep(File inDir, File outDir) |
|||
{ |
|||
this.inDir = inDir; |
|||
this.outDir = outDir; |
|||
} |
|||
|
|||
public void start() |
|||
{ |
|||
LinkedList<File> fileList = new LinkedList<File>(); |
|||
for (File f : inDir.listFiles()) |
|||
{ |
|||
if (f.isFile() && f.getName().toLowerCase().endsWith(".mobi")) |
|||
fileList.add(f); |
|||
} |
|||
|
|||
in = new BufferedReader(new InputStreamReader(System.in)); |
|||
|
|||
int fileCount = fileList.size(); |
|||
int current = 1; |
|||
for (File f : fileList) |
|||
{ |
|||
log("********************"); |
|||
log("File " + (current++) + "/" + fileCount); |
|||
log("Filename: " + f.getName()); |
|||
MobiMeta meta = null; |
|||
try |
|||
{ |
|||
meta = new MobiMeta(f); |
|||
} |
|||
catch (MobiMetaException e) |
|||
{ |
|||
log("Error: " + e.getMessage()); |
|||
continue; |
|||
} |
|||
|
|||
log("Fullname: " + meta.getFullName()); |
|||
List<EXTHRecord> exthList = meta.getEXTHRecords(); |
|||
String encoding = meta.getCharacterEncoding(); |
|||
String author = null; |
|||
String isbn = null; |
|||
String oldasin = null; |
|||
for (EXTHRecord rec : exthList) |
|||
{ |
|||
switch (rec.getRecordType()) |
|||
{ |
|||
case 100: |
|||
author = StreamUtils.byteArrayToString(rec.getData(), encoding); |
|||
break; |
|||
|
|||
case 104: |
|||
isbn = StreamUtils.byteArrayToString(rec.getData(), encoding); |
|||
break; |
|||
|
|||
case 113: |
|||
oldasin = StreamUtils.byteArrayToString(rec.getData(), encoding); |
|||
break; |
|||
|
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (author != null) log("Author: " + author); |
|||
if (isbn != null) log("ISBN: " + isbn); |
|||
String asin = null; |
|||
if (oldasin == null) |
|||
asin = getInput("ASIN: "); |
|||
else |
|||
asin = getInput("ASIN [" + oldasin + "]: "); |
|||
|
|||
if (asin.length() == 0) |
|||
{ |
|||
if (oldasin != null) |
|||
asin = oldasin; |
|||
else |
|||
asin = null; |
|||
} |
|||
|
|||
for (EXTHRecord rec : exthList) |
|||
{ |
|||
int recType = rec.getRecordType(); |
|||
if (recType == 113) |
|||
{ |
|||
if (asin != null) exthList.remove(rec); |
|||
} |
|||
else if (recType == 501) |
|||
exthList.remove(rec); |
|||
} |
|||
|
|||
if (asin != null) exthList.add(new EXTHRecord(113, asin, encoding)); |
|||
exthList.add(new EXTHRecord(501, "EBOK", encoding)); |
|||
meta.setEXTHRecords(); |
|||
|
|||
try |
|||
{ |
|||
meta.saveToNewFile(new File(outDir, f.getName())); |
|||
} |
|||
catch (MobiMetaException e) |
|||
{ |
|||
log("Error saving file: " + e.getMessage()); |
|||
} |
|||
|
|||
log(""); |
|||
} |
|||
} |
|||
|
|||
private void log(String message) |
|||
{ |
|||
System.out.println(message); |
|||
} |
|||
|
|||
private String getInput(String message) |
|||
{ |
|||
System.out.print(message); |
|||
String value = null; |
|||
try |
|||
{ |
|||
value = in.readLine(); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
} |
|||
|
|||
return (value == null)?"":value; |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
package gui; |
|||
|
|||
import javax.swing.JTable; |
|||
import javax.swing.table.TableCellEditor; |
|||
import javax.swing.table.TableCellRenderer; |
|||
import javax.swing.table.TableColumn; |
|||
import javax.swing.table.TableModel; |
|||
|
|||
public class CustomJTable extends JTable |
|||
{ |
|||
/** |
|||
* This code was adapted from |
|||
* http://www.javalobby.org/java/forums/t84905.html
|
|||
* |
|||
* A traditional JTable lets you specify custom renderers and editors on a |
|||
* column by column basis. This class lets you have different renderers and |
|||
* editors in the same column (but different rows). |
|||
*/ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public CustomJTable(TableModel model) |
|||
{ |
|||
super(model); |
|||
} |
|||
|
|||
public TableCellRenderer getCellRenderer(int row, int column) |
|||
{ |
|||
TableColumn tableColumn = getColumnModel().getColumn(column); |
|||
TableCellRenderer renderer = tableColumn.getCellRenderer(); |
|||
if (renderer != null) return renderer; |
|||
if (getValueAt(row, column) != null) |
|||
{ |
|||
return getDefaultRenderer(getValueAt(row, column).getClass()); |
|||
} |
|||
return getDefaultRenderer(getColumnClass(column)); |
|||
} |
|||
|
|||
public TableCellEditor getCellEditor(int row, int column) |
|||
{ |
|||
TableColumn tableColumn = getColumnModel().getColumn(column); |
|||
TableCellEditor editor = tableColumn.getCellEditor(); |
|||
if (editor != null) return editor; |
|||
if (getValueAt(row, column) != null) |
|||
{ |
|||
return getDefaultEditor(getValueAt(row, column).getClass()); |
|||
} |
|||
return getDefaultEditor(getColumnClass(column)); |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
package gui; |
|||
|
|||
import mobimeta.EXTHRecord; |
|||
|
|||
public interface EXTHAddRecordListener |
|||
{ |
|||
public void addEXTHRecord(EXTHRecord rec); |
|||
} |
@ -0,0 +1,14 @@ |
|||
package gui; |
|||
|
|||
public class GuiException extends Exception |
|||
{ |
|||
/** |
|||
* |
|||
*/ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public GuiException(String message) |
|||
{ |
|||
super(message); |
|||
} |
|||
} |
@ -0,0 +1,201 @@ |
|||
package gui; |
|||
|
|||
import mobimeta.*; |
|||
|
|||
import java.io.File; |
|||
import java.util.List; |
|||
|
|||
import javax.swing.table.AbstractTableModel; |
|||
|
|||
class GuiModel extends AbstractTableModel implements EXTHAddRecordListener, LanguageModel, MetaInfoProvider |
|||
{ |
|||
/** |
|||
* This is essentially a proxy to the MobiMeta object. In addition, it |
|||
* serves as a TableModel for the JTable |
|||
*/ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private MobiMeta model = null; |
|||
|
|||
// static globals for convenience
|
|||
//
|
|||
private static String fullName = null; |
|||
private static List<EXTHRecord> exthRecords = null; |
|||
private static String characterEncoding = null; |
|||
|
|||
|
|||
public GuiModel() |
|||
{ |
|||
} |
|||
|
|||
public void setModel(File f) throws GuiException |
|||
{ |
|||
try |
|||
{ |
|||
model = new MobiMeta(f); |
|||
} |
|||
catch (MobiMetaException e) |
|||
{ |
|||
throw new GuiException(e.getMessage()); |
|||
} |
|||
|
|||
fullName = model.getFullName(); |
|||
exthRecords = model.getEXTHRecords(); |
|||
characterEncoding = model.getCharacterEncoding(); |
|||
} |
|||
|
|||
public String getColumnName(int col) |
|||
{ |
|||
if (col == 0) |
|||
return "Record Type"; |
|||
else |
|||
return "Value"; |
|||
} |
|||
|
|||
public int getColumnCount() |
|||
{ |
|||
return 2; |
|||
} |
|||
|
|||
public int getRowCount() |
|||
{ |
|||
return (exthRecords == null)?0:exthRecords.size(); |
|||
} |
|||
|
|||
public Object getValueAt(int row, int col) |
|||
{ |
|||
EXTHRecord rec = exthRecords.get(row); |
|||
int type = rec.getRecordType(); |
|||
String typeDesc = rec.getTypeDescription(); |
|||
|
|||
if (col == 0) |
|||
{ |
|||
if (typeDesc == null) |
|||
return Integer.toString(type); |
|||
else |
|||
return type + " (" + typeDesc + ")"; |
|||
} |
|||
else |
|||
{ |
|||
byte[] data = rec.getData(); |
|||
|
|||
if (typeDesc == null) |
|||
{ |
|||
StringBuffer sb = new StringBuffer(); |
|||
sb.append("{ "); |
|||
int len = data.length; |
|||
for (int i=0; i<len; i++) |
|||
{ |
|||
if (i > 0) sb.append(", "); |
|||
sb.append(data[i] & 0xff); |
|||
} |
|||
sb.append(" }"); |
|||
return sb.toString(); |
|||
} |
|||
else if (EXTHRecord.isBooleanType(type)) |
|||
{ |
|||
int value = StreamUtils.byteArrayToInt(data); |
|||
if (value == 0) |
|||
return Boolean.FALSE; |
|||
else |
|||
return Boolean.TRUE; |
|||
} |
|||
else |
|||
return StreamUtils.byteArrayToString(data, characterEncoding); |
|||
} |
|||
} |
|||
|
|||
public boolean isCellEditable(int row, int col) |
|||
{ |
|||
if ((col == 0) || MobiCommon.safeMode) return false; |
|||
|
|||
return exthRecords.get(row).isKnownType(); |
|||
} |
|||
|
|||
public void setValueAt(Object value, int row, int column) |
|||
{ |
|||
if (column != 1) return; |
|||
|
|||
if (value instanceof String) |
|||
exthRecords.get(row).setData((String)value, characterEncoding); |
|||
else if (value instanceof Boolean) |
|||
exthRecords.get(row).setData(((Boolean)value).booleanValue()); |
|||
else |
|||
return; |
|||
|
|||
fireTableCellUpdated(row, column); |
|||
} |
|||
|
|||
public String getFullName() |
|||
{ |
|||
return fullName; |
|||
} |
|||
|
|||
public void setFullName(String s) |
|||
{ |
|||
fullName = s; |
|||
} |
|||
|
|||
public void removeRecordAtRow(int row) |
|||
{ |
|||
exthRecords.remove(row); |
|||
fireTableRowsDeleted(row, row); |
|||
} |
|||
|
|||
|
|||
public void addEXTHRecord(EXTHRecord rec) |
|||
{ |
|||
exthRecords.add(rec); |
|||
int lastIndex = exthRecords.size() - 1; |
|||
fireTableRowsInserted(lastIndex, lastIndex); |
|||
} |
|||
|
|||
public static String getCharacterEncoding() |
|||
{ |
|||
return characterEncoding; |
|||
} |
|||
|
|||
public void save(File outputFile, boolean packHeader) throws GuiException |
|||
{ |
|||
if (packHeader) |
|||
{ |
|||
model.setFullName(fullName); |
|||
model.setEXTHRecords(); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
model.saveToNewFile(outputFile, packHeader); |
|||
} |
|||
catch (MobiMetaException e) |
|||
{ |
|||
throw new GuiException("Problems encountered while saving file: " |
|||
+ e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
public int getLocale() |
|||
{ |
|||
return model.getLocale(); |
|||
} |
|||
|
|||
public int getDictInput() |
|||
{ |
|||
return model.getDictInput(); |
|||
} |
|||
|
|||
public int getDictOutput() |
|||
{ |
|||
return model.getDictOutput(); |
|||
} |
|||
|
|||
public void setLanguages(int locale, int dictInput, int dictOutput) |
|||
{ |
|||
model.setLanguages(locale, dictInput, dictOutput); |
|||
} |
|||
|
|||
public String getMetaInfo() |
|||
{ |
|||
return model.getMetaInfo(); |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
package gui; |
|||
|
|||
import java.awt.BorderLayout; |
|||
import java.awt.FlowLayout; |
|||
|
|||
import javax.swing.JButton; |
|||
import javax.swing.JDialog; |
|||
import javax.swing.JFrame; |
|||
import javax.swing.JPanel; |
|||
import javax.swing.border.EmptyBorder; |
|||
import java.awt.event.ActionListener; |
|||
import java.awt.event.ActionEvent; |
|||
import javax.swing.JTextArea; |
|||
import javax.swing.JScrollPane; |
|||
|
|||
public class HeaderInfoDialog extends JDialog implements ActionListener |
|||
{ |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
private static final long serialVersionUID = 1L; |
|||
private final JPanel contentPanel = new JPanel(); |
|||
private JButton closeButton; |
|||
private JScrollPane scrollPane; |
|||
private JTextArea textArea; |
|||
|
|||
/** |
|||
* Create the dialog. |
|||
*/ |
|||
public HeaderInfoDialog(JFrame parent, MetaInfoProvider infoProvider) |
|||
{ |
|||
super(parent, "Header Info", true); |
|||
setBounds(100, 100, 450, 300); |
|||
getContentPane().setLayout(new BorderLayout()); |
|||
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); |
|||
getContentPane().add(contentPanel, BorderLayout.CENTER); |
|||
contentPanel.setLayout(new BorderLayout(0, 0)); |
|||
{ |
|||
scrollPane = new JScrollPane(); |
|||
contentPanel.add(scrollPane, BorderLayout.CENTER); |
|||
{ |
|||
textArea = new JTextArea(infoProvider.getMetaInfo()); |
|||
textArea.setEditable(false); |
|||
scrollPane.setViewportView(textArea); |
|||
} |
|||
} |
|||
{ |
|||
JPanel buttonPane = new JPanel(); |
|||
buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); |
|||
getContentPane().add(buttonPane, BorderLayout.SOUTH); |
|||
{ |
|||
closeButton = new JButton("Close"); |
|||
closeButton.addActionListener(this); |
|||
closeButton.setActionCommand("Cancel"); |
|||
buttonPane.add(closeButton); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected JButton getCloseButton() { |
|||
return closeButton; |
|||
} |
|||
|
|||
public void actionPerformed(ActionEvent e) |
|||
{ |
|||
if (e.getSource() == closeButton) |
|||
{ |
|||
setVisible(false); |
|||
dispose(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,74 @@ |
|||
package gui; |
|||
|
|||
public class LanguageCodes |
|||
{ |
|||
// these codes are taken from:
|
|||
// http://www.autoitscript.com/autoit3/docs/appendix/OSLangCodes.htm
|
|||
//
|
|||
public final static int[] code = |
|||
{ 0, 1078, 1052, 1025, 2049, 3073, 4097, 5121, 6145, 7169, 8193, 9217, |
|||
10241, 11265, 12289, 13313, 14337, 15361, 16385, 1067, 1068, 2092, |
|||
1069, 1059, 1026, 1027, 4, 1028, 2052, 3076, 4100, 5124, 1050, 1029, |
|||
1030, 1043, 2067, 9, 1033, 2057, 3081, 4105, 5129, 6153, 7177, |
|||
8201, 9225, 10249, 11273, 12297, 13321, 1061, 1080, 1073, 1035, |
|||
1036, 2060, 3084, 4108, 5132, 6156, 1079, 1031, 2055, 3079, 4103, |
|||
5127, 1032, 1037, 1081, 1038, 1039, 1057, 1040, 2064, 1041, 1087, |
|||
1111, 1042, 1062, 1063, 1071, 1086, 2110, 1102, 1044, 2068, 1045, |
|||
1046, 2070, 1048, 1049, 1103, 2074, 3098, 1051, 1060, 1034, 2058, |
|||
3082, 4106, 5130, 6154, 7178, 8202, 9226, 10250, 11274, 12298, |
|||
13322, 14346, 15370, 16394, 17418, 18442, 19466, 20490, 1089, 1053, |
|||
2077, 1097, 1092, 1054, 1055, 1058, 1056, 1091, 2115, 1066 }; |
|||
|
|||
public final static String[] description = |
|||
{ "Unspecified", "Afrikaans", "Albanian", "Arabic - Saudi Arabia", |
|||
"Arabic - Iraq", "Arabic - Egypt", "Arabic - Libya", |
|||
"Arabic - Algeria", "Arabic - Morocco", "Arabic - Tunisia", |
|||
"Arabic - Oman", "Arabic - Yemen", "Arabic - Syria", |
|||
"Arabic - Jordan", "Arabic - Lebanon", "Arabic - Kuwait", |
|||
"Arabic - UAE", "Arabic - Bahrain", "Arabic - Qatar", "Armenian", |
|||
"Azeri - Latin", "Azeri - Cyrillic", "Basque", "Belarusian", |
|||
"Bulgarian", "Catalan", "Chinese", "Chinese - Taiwan", "Chinese - PRC", |
|||
"Chinese - Hong Kong", "Chinese - Singapore", "Chinese - Macau", |
|||
"Croatian", "Czech", "Danish", "Dutch - Standard", |
|||
"Dutch - Belgian", "English", "English - United States", |
|||
"English Ð United Kingdom", "English - Australian", |
|||
"English - Canadian", "English - New Zealand", "English - Irish", |
|||
"English - South Africa", "English - Jamaica", |
|||
"English - Caribbean", "English - Belize", "English - Trinidad", |
|||
"English - Zimbabwe", "English - Philippines", "Estonian", |
|||
"Faeroese", "Farsi", "Finnish", "French - Standard", |
|||
"French - Belgian", "French - Canadian", "French - Swiss", |
|||
"French - Luxembourg", "French - Monaco", "Georgian", |
|||
"German - Standard", "German - Swiss", "German - Austrian", |
|||
"German - Luxembourg", "German - Liechtenstei", "Greek", "Hebrew", |
|||
"Hindi", "Hungarian", "Icelandic", "Indonesian", |
|||
"Italian - Standard", "Italian - Swiss", "Japanese", "Kazakh", |
|||
"Konkani", "Korean", "Latvian", "Lithuanian", "Macedonian", |
|||
"Malay - Malaysia", "Malay - Brunei Darussalam", "Marathi", |
|||
"Norwegian - Bokmal", "Norwegian - Nynorsk", "Polish", |
|||
"Portuguese - Brazilian", "Portuguese - Standard", "Romanian", |
|||
"Russian", "Sanskrit", "Serbian - Latin", "Serbian - Cyrillic", |
|||
"Slovak", "Slovenian", "Spanish - Traditional Sort", |
|||
"Spanish - Mexican", "Spanish - Modern Sort", |
|||
"Spanish - Guatemala", "Spanish - Costa Rica", "Spanish - Panama", |
|||
"Spanish - Dominican Republic", "Spanish - Venezuela", |
|||
"Spanish - Colombia", "Spanish - Peru", "Spanish - Argentina", |
|||
"Spanish - Ecuador", "Spanish - Chile", "Spanish - Uruguay", |
|||
"Spanish - Paraguay", "Spanish - Bolivia", "Spanish - El Salvador", |
|||
"Spanish - Honduras", "Spanish - Nicaragua", |
|||
"Spanish - Puerto Rico", "Swahili", "Swedish", "Swedish - Finland", |
|||
"Tamil", "Tatar", "Thai", "Turkish", "Ukrainian", "Urdu", |
|||
"Uzbek - Latin", "Uzbek - Cyrillic", "Vietnamese" }; |
|||
|
|||
|
|||
public static int codeToIndex(int key) |
|||
{ |
|||
int len = code.length; |
|||
for (int i = 0; i < len; i++) |
|||
{ |
|||
if (code[i] == key) return i; |
|||
} |
|||
|
|||
return -1; |
|||
} |
|||
} |
@ -0,0 +1,258 @@ |
|||
package gui; |
|||
|
|||
import java.awt.BorderLayout; |
|||
import java.awt.FlowLayout; |
|||
|
|||
import javax.swing.JButton; |
|||
import javax.swing.JDialog; |
|||
import javax.swing.JFrame; |
|||
import javax.swing.JPanel; |
|||
import javax.swing.border.EmptyBorder; |
|||
import java.awt.GridBagLayout; |
|||
import java.awt.Component; |
|||
import javax.swing.Box; |
|||
import javax.swing.JLabel; |
|||
import java.awt.GridBagConstraints; |
|||
import java.awt.Font; |
|||
import javax.swing.SwingConstants; |
|||
import java.awt.Insets; |
|||
import java.awt.event.ActionEvent; |
|||
import java.awt.event.ActionListener; |
|||
|
|||
import javax.swing.JComboBox; |
|||
|
|||
public class LanguageDialog extends JDialog implements ActionListener |
|||
{ |
|||
/** |
|||
* |
|||
*/ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
private final JPanel contentPanel = new JPanel(); |
|||
|
|||
private JButton okButton; |
|||
private JButton cancelButton; |
|||
private JComboBox cbLanguage; |
|||
private JComboBox cbDictInput; |
|||
private JComboBox cbDictOutput; |
|||
|
|||
// store original values here
|
|||
private int locale; |
|||
private int dictInput; |
|||
private int dictOutput; |
|||
|
|||
// these values indicate if they are known values
|
|||
private boolean localeExists; |
|||
private boolean dictInputExists; |
|||
private boolean dictOutputExists; |
|||
|
|||
private LanguageModel model; |
|||
|
|||
/** |
|||
* Create the dialog. |
|||
*/ |
|||
public LanguageDialog(JFrame parent, LanguageModel model) |
|||
{ |
|||
super(parent, true); |
|||
this.model = model; |
|||
locale = model.getLocale(); |
|||
dictInput = model.getDictInput(); |
|||
dictOutput = model.getDictOutput(); |
|||
localeExists = (LanguageCodes.codeToIndex(locale) != -1); |
|||
dictInputExists = (LanguageCodes.codeToIndex(dictInput) != -1); |
|||
dictOutputExists = (LanguageCodes.codeToIndex(dictOutput) != -1); |
|||
|
|||
String[] localeChoices; |
|||
String[] dictInputChoices; |
|||
String[] dictOutputChoices; |
|||
|
|||
// assemble choices for combo boxes
|
|||
//
|
|||
if (localeExists) |
|||
localeChoices = LanguageCodes.description; |
|||
else |
|||
{ |
|||
localeChoices = new String[LanguageCodes.description.length + 1]; |
|||
System.arraycopy(LanguageCodes.description, 0, localeChoices, 0, |
|||
LanguageCodes.description.length); |
|||
localeChoices[localeChoices.length - 1] = "Unknown (" + locale |
|||
+ ")"; |
|||
} |
|||
|
|||
if (dictInputExists) |
|||
dictInputChoices = LanguageCodes.description; |
|||
else |
|||
{ |
|||
dictInputChoices = new String[LanguageCodes.description.length + 1]; |
|||
System.arraycopy(LanguageCodes.description, 0, dictInputChoices, 0, |
|||
LanguageCodes.description.length); |
|||
dictInputChoices[dictInputChoices.length - 1] = "Unknown (" |
|||
+ dictInput + ")"; |
|||
} |
|||
|
|||
if (dictOutputExists) |
|||
dictOutputChoices = LanguageCodes.description; |
|||
else |
|||
{ |
|||
dictOutputChoices = new String[LanguageCodes.description.length + 1]; |
|||
System.arraycopy(LanguageCodes.description, 0, dictOutputChoices, |
|||
0, LanguageCodes.description.length); |
|||
dictOutputChoices[dictOutputChoices.length - 1] = "Unknown (" |
|||
+ dictOutput + ")"; |
|||
} |
|||
|
|||
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); |
|||
setBounds(100, 100, 450, 300); |
|||
getContentPane().setLayout(new BorderLayout()); |
|||
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); |
|||
getContentPane().add(contentPanel, BorderLayout.CENTER); |
|||
contentPanel.setLayout(new BorderLayout(0, 0)); |
|||
{ |
|||
Component verticalStrut = Box.createVerticalStrut(20); |
|||
contentPanel.add(verticalStrut, BorderLayout.NORTH); |
|||
} |
|||
{ |
|||
Component verticalStrut = Box.createVerticalStrut(20); |
|||
contentPanel.add(verticalStrut, BorderLayout.SOUTH); |
|||
} |
|||
{ |
|||
Component horizontalStrut = Box.createHorizontalStrut(20); |
|||
contentPanel.add(horizontalStrut, BorderLayout.WEST); |
|||
} |
|||
{ |
|||
Component horizontalStrut = Box.createHorizontalStrut(20); |
|||
contentPanel.add(horizontalStrut, BorderLayout.EAST); |
|||
} |
|||
{ |
|||
JPanel panel = new JPanel(); |
|||
contentPanel.add(panel, BorderLayout.CENTER); |
|||
GridBagLayout gbl_panel = new GridBagLayout(); |
|||
gbl_panel.columnWidths = new int[] |
|||
{ 0, 0, 0 }; |
|||
gbl_panel.rowHeights = new int[] |
|||
{ 0, 0, 0, 0 }; |
|||
gbl_panel.columnWeights = new double[] |
|||
{ 0.0, 1.0, Double.MIN_VALUE }; |
|||
gbl_panel.rowWeights = new double[] |
|||
{ 0.0, 0.0, 0.0, Double.MIN_VALUE }; |
|||
panel.setLayout(gbl_panel); |
|||
{ |
|||
JLabel lblLanguage = new JLabel("Language"); |
|||
lblLanguage.setHorizontalAlignment(SwingConstants.RIGHT); |
|||
lblLanguage.setFont(new Font("Lucida Grande", Font.BOLD, 13)); |
|||
GridBagConstraints gbc_lblLanguage = new GridBagConstraints(); |
|||
gbc_lblLanguage.anchor = GridBagConstraints.EAST; |
|||
gbc_lblLanguage.insets = new Insets(0, 0, 5, 5); |
|||
gbc_lblLanguage.gridx = 0; |
|||
gbc_lblLanguage.gridy = 0; |
|||
panel.add(lblLanguage, gbc_lblLanguage); |
|||
} |
|||
{ |
|||
cbLanguage = new JComboBox(localeChoices); |
|||
cbLanguage.setSelectedIndex(localeExists ? LanguageCodes |
|||
.codeToIndex(locale) : (localeChoices.length - 1)); |
|||
GridBagConstraints gbc_cbLanguage = new GridBagConstraints(); |
|||
gbc_cbLanguage.insets = new Insets(0, 0, 5, 0); |
|||
gbc_cbLanguage.fill = GridBagConstraints.HORIZONTAL; |
|||
gbc_cbLanguage.gridx = 1; |
|||
gbc_cbLanguage.gridy = 0; |
|||
panel.add(cbLanguage, gbc_cbLanguage); |
|||
} |
|||
{ |
|||
JLabel lblDictionaryInput = new JLabel("Dictionary Input"); |
|||
lblDictionaryInput.setHorizontalAlignment(SwingConstants.RIGHT); |
|||
lblDictionaryInput.setFont(new Font("Lucida Grande", Font.BOLD, |
|||
13)); |
|||
GridBagConstraints gbc_lblDictionaryInput = new GridBagConstraints(); |
|||
gbc_lblDictionaryInput.anchor = GridBagConstraints.EAST; |
|||
gbc_lblDictionaryInput.insets = new Insets(0, 0, 5, 5); |
|||
gbc_lblDictionaryInput.gridx = 0; |
|||
gbc_lblDictionaryInput.gridy = 1; |
|||
panel.add(lblDictionaryInput, gbc_lblDictionaryInput); |
|||
} |
|||
{ |
|||
cbDictInput = new JComboBox(dictInputChoices); |
|||
cbDictInput |
|||
.setSelectedIndex(dictInputExists ? LanguageCodes |
|||
.codeToIndex(dictInput) |
|||
: (dictInputChoices.length - 1)); |
|||
GridBagConstraints gbc_cbDictInput = new GridBagConstraints(); |
|||
gbc_cbDictInput.insets = new Insets(0, 0, 5, 0); |
|||
gbc_cbDictInput.fill = GridBagConstraints.HORIZONTAL; |
|||
gbc_cbDictInput.gridx = 1; |
|||
gbc_cbDictInput.gridy = 1; |
|||
panel.add(cbDictInput, gbc_cbDictInput); |
|||
} |
|||
{ |
|||
JLabel lblDictionaryOutput = new JLabel("Dictionary Output"); |
|||
lblDictionaryOutput |
|||
.setHorizontalAlignment(SwingConstants.RIGHT); |
|||
lblDictionaryOutput.setFont(new Font("Lucida Grande", |
|||
Font.BOLD, 13)); |
|||
GridBagConstraints gbc_lblDictionaryOutput = new GridBagConstraints(); |
|||
gbc_lblDictionaryOutput.anchor = GridBagConstraints.EAST; |
|||
gbc_lblDictionaryOutput.insets = new Insets(0, 0, 0, 5); |
|||
gbc_lblDictionaryOutput.gridx = 0; |
|||
gbc_lblDictionaryOutput.gridy = 2; |
|||
panel.add(lblDictionaryOutput, gbc_lblDictionaryOutput); |
|||
} |
|||
{ |
|||
cbDictOutput = new JComboBox(dictOutputChoices); |
|||
cbDictOutput.setSelectedIndex(dictOutputExists ? LanguageCodes |
|||
.codeToIndex(dictOutput) |
|||
: (dictOutputChoices.length - 1)); |
|||
GridBagConstraints gbc_cbDictOutput = new GridBagConstraints(); |
|||
gbc_cbDictOutput.fill = GridBagConstraints.HORIZONTAL; |
|||
gbc_cbDictOutput.gridx = 1; |
|||
gbc_cbDictOutput.gridy = 2; |
|||
panel.add(cbDictOutput, gbc_cbDictOutput); |
|||
} |
|||
} |
|||
{ |
|||
JPanel buttonPane = new JPanel(); |
|||
buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); |
|||
getContentPane().add(buttonPane, BorderLayout.SOUTH); |
|||
{ |
|||
okButton = new JButton("OK"); |
|||
okButton.addActionListener(this); |
|||
buttonPane.add(okButton); |
|||
getRootPane().setDefaultButton(okButton); |
|||
} |
|||
{ |
|||
cancelButton = new JButton("Cancel"); |
|||
cancelButton.addActionListener(this); |
|||
buttonPane.add(cancelButton); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void actionPerformed(ActionEvent event) |
|||
{ |
|||
Object source = event.getSource(); |
|||
|
|||
if (source == okButton) |
|||
{ |
|||
int localeIndex = cbLanguage.getSelectedIndex(); |
|||
if ((localeIndex >= 0) && (localeIndex < LanguageCodes.code.length)) |
|||
locale = LanguageCodes.code[localeIndex]; |
|||
|
|||
int dictInputIndex = cbDictInput.getSelectedIndex(); |
|||
if ((dictInputIndex >= 0) && (dictInputIndex < LanguageCodes.code.length)) |
|||
dictInput = LanguageCodes.code[dictInputIndex]; |
|||
|
|||
int dictOutputIndex = cbDictOutput.getSelectedIndex(); |
|||
if ((dictOutputIndex >= 0) && (dictOutputIndex < LanguageCodes.code.length)) |
|||
dictOutput = LanguageCodes.code[dictOutputIndex]; |
|||
|
|||
model.setLanguages(locale, dictInput, dictOutput); |
|||
|
|||
setVisible(false); |
|||
dispose(); |
|||
} |
|||
else if (source == cancelButton) |
|||
{ |
|||
setVisible(false); |
|||
dispose(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
package gui; |
|||
|
|||
public interface LanguageModel |
|||
{ |
|||
public int getLocale(); |
|||
|
|||
public int getDictInput(); |
|||
|
|||
public int getDictOutput(); |
|||
|
|||
public void setLanguages(int locale, int dictInput, int dictOutput); |
|||
} |
@ -0,0 +1,667 @@ |
|||
package gui; |
|||
|
|||
import java.io.*; |
|||
import java.util.HashSet; |
|||
import java.awt.EventQueue; |
|||
|
|||
import javax.swing.JFrame; |
|||
import javax.swing.JOptionPane; |
|||
|
|||
import java.awt.GridBagLayout; |
|||
import javax.swing.JLabel; |
|||
|
|||
import java.awt.FileDialog; |
|||
import java.awt.GridBagConstraints; |
|||
import java.awt.Insets; |
|||
import java.awt.Rectangle; |
|||
|
|||
import javax.swing.JSeparator; |
|||
import javax.swing.JPanel; |
|||
import java.awt.BorderLayout; |
|||
import java.awt.Component; |
|||
import javax.swing.Box; |
|||
import javax.swing.SwingConstants; |
|||
import java.awt.Font; |
|||
import javax.swing.JTextField; |
|||
import java.awt.Color; |
|||
import javax.swing.JScrollPane; |
|||
import java.awt.FlowLayout; |
|||
import javax.swing.JButton; |
|||
import javax.swing.JTable; |
|||
import javax.swing.event.DocumentEvent; |
|||
import javax.swing.event.DocumentListener; |
|||
import javax.swing.event.ListSelectionEvent; |
|||
import javax.swing.event.ListSelectionListener; |
|||
import javax.swing.event.TableModelEvent; |
|||
import javax.swing.event.TableModelListener; |
|||
import java.awt.event.ActionListener; |
|||
import java.awt.event.ActionEvent; |
|||
import java.awt.event.MouseAdapter; |
|||
import java.awt.event.MouseEvent; |
|||
|
|||
import javax.swing.JTextArea; |
|||
|
|||
import mobimeta.MobiCommon; |
|||
import javax.swing.JMenuBar; |
|||
import javax.swing.JMenu; |
|||
import javax.swing.JMenuItem; |
|||
|
|||
public class Main implements ListSelectionListener, ActionListener, |
|||
TableModelListener, LanguageModel |
|||
{ |
|||
private FileDialog openFileChooser = null; |
|||
private FileDialog saveFileChooser = null; |
|||
private JFrame frame; |
|||
private JTextArea lblInputFilename; |
|||
private JTextArea lblOutputFilename; |
|||
private JTextField tfFullName; |
|||
private JTable table; |
|||
private JButton buttonRemove; |
|||
private JButton buttonAdd; |
|||
private JButton buttonSave; |
|||
private JButton btnLanguage; |
|||
private JButton btnHeaderInfo; |
|||
private GuiModel model; |
|||
private File outputFile; |
|||
private boolean packHeader = false; |
|||
private JMenuItem mntmOpen; |
|||
private JMenuItem mntmSave; |
|||
|
|||
/** |
|||
* Launch the application. |
|||
*/ |
|||
public static void main(String[] args) |
|||
{ |
|||
System.setProperty("apple.laf.useScreenMenuBar", "true"); |
|||
System.setProperty("com.apple.mrj.application.apple.menu.about.name", |
|||
"Mobi Meta Editor"); |
|||
|
|||
HashSet<String> optionsSet = new HashSet<String>(); |
|||
File inputFile = null; |
|||
for (int i=0; i<args.length; i++) |
|||
{ |
|||
String arg = args[i]; |
|||
if (arg.startsWith("-")) |
|||
optionsSet.add(arg); |
|||
else if (inputFile == null) |
|||
inputFile = new File(arg); |
|||
else |
|||
printUsage(); |
|||
} |
|||
|
|||
if ((inputFile != null) && (!inputFile.exists() || !inputFile.isFile())) |
|||
{ |
|||
System.err.println("Input file " + inputFile.getAbsolutePath() |
|||
+ " does not exist or is not a file."); |
|||
System.exit(1); |
|||
} |
|||
|
|||
if (optionsSet.contains("-h") || optionsSet.contains("--help")) |
|||
printUsage(); |
|||
|
|||
if (optionsSet.contains("-s")) |
|||
MobiCommon.safeMode = true; |
|||
|
|||
if (optionsSet.contains("-d")) |
|||
MobiCommon.debug = true; |
|||
|
|||
// get around inner class mumbo-jumbo
|
|||
//
|
|||
final File f = inputFile; |
|||
|
|||
EventQueue.invokeLater(new Runnable() |
|||
{ |
|||
public void run() |
|||
{ |
|||
try |
|||
{ |
|||
Main window = new Main(f); |
|||
window.frame.setVisible(true); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private static void printUsage() |
|||
{ |
|||
System.err.println("Usage: mobimeta [switches] [input file]"); |
|||
System.err.println(" -h\tthis message"); |
|||
System.err |
|||
.println(" -s\tsafe mode - does not change the size of the mobi header record"); |
|||
System.err.println(" -d\tdebug mode"); |
|||
System.exit(0); |
|||
} |
|||
|
|||
/** |
|||
* Create the application. |
|||
*/ |
|||
public Main(File inputFile) |
|||
{ |
|||
model = new GuiModel(); |
|||
model.addTableModelListener(this); |
|||
initialize(); |
|||
if (inputFile == null) |
|||
inputFile = getSourceFile(); |
|||
|
|||
if (inputFile == null) |
|||
{ |
|||
showAlert("No mobi file selected."); |
|||
System.exit(0); |
|||
} |
|||
} |
|||
|
|||
|
|||
public void valueChanged(ListSelectionEvent e) |
|||
{ |
|||
if (!MobiCommon.safeMode) |
|||
buttonRemove.setEnabled(table.getSelectedRow() != -1); |
|||
} |
|||
|
|||
public void pickSaveTarget() |
|||
{ |
|||
if (saveFileChooser == null) |
|||
{ |
|||
saveFileChooser = new FileDialog(frame, "Select mobi file", FileDialog.SAVE); |
|||
saveFileChooser.setFilenameFilter(new MobiFileFilter()); |
|||
} |
|||
|
|||
if (outputFile != null) |
|||
{ |
|||
saveFileChooser.setDirectory(outputFile.getParent()); |
|||
saveFileChooser.setFile(outputFile.getName()); |
|||
} |
|||
|
|||
saveFileChooser.setVisible(true); |
|||
|
|||
if (saveFileChooser.getFile() != null) |
|||
{ |
|||
outputFile = new File(saveFileChooser.getDirectory(), saveFileChooser.getFile()); |
|||
lblOutputFilename.setText(outputFile.getAbsolutePath()); |
|||
} |
|||
} |
|||
|
|||
public void actionPerformed(ActionEvent event) |
|||
{ |
|||
Object source = event.getSource(); |
|||
|
|||
if (source == buttonAdd) |
|||
{ |
|||
NewRecordDialog dialog = new NewRecordDialog(frame, model); |
|||
dialog.setVisible(true); |
|||
} |
|||
else if (source == buttonRemove) |
|||
{ |
|||
int row = table.getSelectedRow(); |
|||
if (row == -1) return; |
|||
model.removeRecordAtRow(row); |
|||
} |
|||
else if ((source == buttonSave) || (source == mntmSave)) |
|||
{ |
|||
if (packHeader) model.setFullName(tfFullName.getText()); |
|||
try |
|||
{ |
|||
File tmpOutput = null; |
|||
if (lblInputFilename.getText().equals(lblOutputFilename.getText())) |
|||
tmpOutput = File.createTempFile("mobimeta", ".mobi"); |
|||
else |
|||
tmpOutput = outputFile; |
|||
|
|||
model.save(tmpOutput, packHeader); |
|||
if (!tmpOutput.equals(outputFile)) |
|||
{ |
|||
if (!tmpOutput.renameTo(outputFile)) |
|||
{ |
|||
showAlert("Error renaming temp file to " + outputFile.getAbsolutePath()); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
setWindowChangedStatus(false); |
|||
showAlert("File saved."); |
|||
} |
|||
catch (GuiException e) |
|||
{ |
|||
showAlert(e.getMessage()); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
showAlert("Could not create temp file for writing: " + e.getMessage()); |
|||
} |
|||
} |
|||
else if (source == btnLanguage) |
|||
{ |
|||
LanguageDialog dialog = new LanguageDialog(frame, this); |
|||
dialog.setVisible(true); |
|||
} |
|||
else if (source == btnHeaderInfo) |
|||
{ |
|||
HeaderInfoDialog dialog = new HeaderInfoDialog(frame, model); |
|||
dialog.setVisible(true); |
|||
} |
|||
else if (source == mntmOpen) |
|||
{ |
|||
getSourceFile(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Initialize the contents of the frame. |
|||
*/ |
|||
private void initialize() |
|||
{ |
|||
frame = new JFrame(); |
|||
frame.setTitle("Mobi Meta Editor"); |
|||
frame.setBounds(50, 50, 800, 700); |
|||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); |
|||
frame.getContentPane().setLayout(new BorderLayout(0, 0)); |
|||
|
|||
Component horizontalStrut = Box.createHorizontalStrut(20); |
|||
frame.getContentPane().add(horizontalStrut, BorderLayout.WEST); |
|||
|
|||
Component horizontalStrut_1 = Box.createHorizontalStrut(20); |
|||
frame.getContentPane().add(horizontalStrut_1, BorderLayout.EAST); |
|||
|
|||
Component verticalStrut = Box.createVerticalStrut(20); |
|||
frame.getContentPane().add(verticalStrut, BorderLayout.NORTH); |
|||
|
|||
Component verticalStrut_1 = Box.createVerticalStrut(20); |
|||
frame.getContentPane().add(verticalStrut_1, BorderLayout.SOUTH); |
|||
|
|||
JPanel panel = new JPanel(); |
|||
frame.getContentPane().add(panel, BorderLayout.CENTER); |
|||
GridBagLayout gbl_panel = new GridBagLayout(); |
|||
gbl_panel.columnWidths = new int[] |
|||
{ 0, 0, 0, 0, 0 }; |
|||
gbl_panel.rowHeights = new int[] |
|||
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|||
gbl_panel.columnWeights = new double[] |
|||
{ 1.0, 0.0, 1.0, 0.0, Double.MIN_VALUE }; |
|||
gbl_panel.rowWeights = new double[] |
|||
{ 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, Double.MIN_VALUE }; |
|||
panel.setLayout(gbl_panel); |
|||
|
|||
JLabel lblInput = new JLabel("Input Filename"); |
|||
lblInput.setFont(new Font("Lucida Grande", Font.BOLD, 13)); |
|||
lblInput.setVerticalAlignment(SwingConstants.TOP); |
|||
GridBagConstraints gbc_lblInput = new GridBagConstraints(); |
|||
gbc_lblInput.anchor = GridBagConstraints.NORTHEAST; |
|||
gbc_lblInput.insets = new Insets(0, 0, 5, 5); |
|||
gbc_lblInput.gridx = 0; |
|||
gbc_lblInput.gridy = 0; |
|||
panel.add(lblInput, gbc_lblInput); |
|||
|
|||
Component horizontalStrut_2 = Box.createHorizontalStrut(20); |
|||
GridBagConstraints gbc_horizontalStrut_2 = new GridBagConstraints(); |
|||
gbc_horizontalStrut_2.insets = new Insets(0, 0, 5, 5); |
|||
gbc_horizontalStrut_2.gridx = 1; |
|||
gbc_horizontalStrut_2.gridy = 0; |
|||
panel.add(horizontalStrut_2, gbc_horizontalStrut_2); |
|||
|
|||
lblInputFilename = new JTextArea(0, 40); |
|||
lblInputFilename.setEditable(false); |
|||
lblInputFilename.setLineWrap(true); |
|||
GridBagConstraints gbc_lblInputFilename = new GridBagConstraints(); |
|||
gbc_lblInputFilename.fill = GridBagConstraints.BOTH; |
|||
gbc_lblInputFilename.anchor = GridBagConstraints.NORTHWEST; |
|||
gbc_lblInputFilename.insets = new Insets(0, 0, 5, 5); |
|||
gbc_lblInputFilename.gridx = 2; |
|||
gbc_lblInputFilename.gridy = 0; |
|||
panel.add(lblInputFilename, gbc_lblInputFilename); |
|||
|
|||
JLabel lblOutput = new JLabel("Output Filename"); |
|||
lblOutput.setFont(new Font("Lucida Grande", Font.BOLD, 13)); |
|||
lblOutput.setVerticalAlignment(SwingConstants.TOP); |
|||
lblOutput.setHorizontalAlignment(SwingConstants.RIGHT); |
|||
GridBagConstraints gbc_lblOutput = new GridBagConstraints(); |
|||
gbc_lblOutput.anchor = GridBagConstraints.NORTHEAST; |
|||
gbc_lblOutput.insets = new Insets(0, 0, 5, 5); |
|||
gbc_lblOutput.gridx = 0; |
|||
gbc_lblOutput.gridy = 1; |
|||
panel.add(lblOutput, gbc_lblOutput); |
|||
|
|||
lblOutputFilename = new JTextArea(0, 40); |
|||
lblOutputFilename.setEditable(false); |
|||
lblOutputFilename.setLineWrap(true); |
|||
lblOutputFilename.addMouseListener(new MouseAdapter() |
|||
{ |
|||
public void mouseClicked(MouseEvent e) |
|||
{ |
|||
pickSaveTarget(); |
|||
} |
|||
}); |
|||
GridBagConstraints gbc_lblOutputFilename = new GridBagConstraints(); |
|||
gbc_lblOutputFilename.fill = GridBagConstraints.BOTH; |
|||
gbc_lblOutputFilename.anchor = GridBagConstraints.NORTHWEST; |
|||
gbc_lblOutputFilename.insets = new Insets(0, 0, 5, 5); |
|||
gbc_lblOutputFilename.gridx = 2; |
|||
gbc_lblOutputFilename.gridy = 1; |
|||
panel.add(lblOutputFilename, gbc_lblOutputFilename); |
|||
|
|||
JSeparator separator = new JSeparator(); |
|||
separator.setForeground(Color.DARK_GRAY); |
|||
GridBagConstraints gbc_separator = new GridBagConstraints(); |
|||
gbc_separator.fill = GridBagConstraints.BOTH; |
|||
gbc_separator.gridwidth = 3; |
|||
gbc_separator.insets = new Insets(0, 0, 5, 5); |
|||
gbc_separator.gridx = 0; |
|||
gbc_separator.gridy = 2; |
|||
panel.add(separator, gbc_separator); |
|||
|
|||
Component separatorStrut1 = Box.createVerticalStrut(10); |
|||
GridBagConstraints gbc_strut1 = new GridBagConstraints(); |
|||
gbc_strut1.fill = GridBagConstraints.VERTICAL; |
|||
gbc_strut1.gridwidth = 1; |
|||
gbc_strut1.insets = new Insets(0, 0, 5, 0); |
|||
gbc_strut1.gridx = 3; |
|||
gbc_strut1.gridy = 2; |
|||
panel.add(separatorStrut1, gbc_strut1); |
|||
|
|||
JLabel lblTitle = new JLabel("Full Name"); |
|||
lblTitle.setFont(new Font("Lucida Grande", Font.BOLD, 13)); |
|||
GridBagConstraints gbc_lblTitle = new GridBagConstraints(); |
|||
gbc_lblTitle.anchor = GridBagConstraints.NORTHEAST; |
|||
gbc_lblTitle.insets = new Insets(0, 0, 5, 5); |
|||
gbc_lblTitle.gridx = 0; |
|||
gbc_lblTitle.gridy = 3; |
|||
panel.add(lblTitle, gbc_lblTitle); |
|||
|
|||
tfFullName = new JTextField(15); |
|||
if (MobiCommon.safeMode) |
|||
tfFullName.setEditable(false); |
|||
else |
|||
{ |
|||
tfFullName.getDocument().addDocumentListener(new DocumentListener() |
|||
{ |
|||
public void changedUpdate(DocumentEvent arg0) |
|||
{ |
|||
packHeader = true; |
|||
setWindowChangedStatus(true); |
|||
} |
|||
|
|||
public void insertUpdate(DocumentEvent arg0) |
|||
{ |
|||
packHeader = true; |
|||
setWindowChangedStatus(true); |
|||
} |
|||
|
|||
public void removeUpdate(DocumentEvent arg0) |
|||
{ |
|||
packHeader = true; |
|||
setWindowChangedStatus(true); |
|||
} |
|||
}); |
|||
} |
|||
GridBagConstraints gbc_tfFullName = new GridBagConstraints(); |
|||
gbc_tfFullName.fill = GridBagConstraints.HORIZONTAL; |
|||
gbc_tfFullName.anchor = GridBagConstraints.NORTHWEST; |
|||
gbc_tfFullName.insets = new Insets(0, 0, 5, 5); |
|||
gbc_tfFullName.gridx = 2; |
|||
gbc_tfFullName.gridy = 3; |
|||
panel.add(tfFullName, gbc_tfFullName); |
|||
|
|||
JPanel panel_3 = new JPanel(); |
|||
GridBagConstraints gbc_panel_3 = new GridBagConstraints(); |
|||
gbc_panel_3.insets = new Insets(0, 0, 5, 5); |
|||
gbc_panel_3.fill = GridBagConstraints.BOTH; |
|||
gbc_panel_3.gridx = 2; |
|||
gbc_panel_3.gridy = 4; |
|||
panel.add(panel_3, gbc_panel_3); |
|||
|
|||
btnLanguage = new JButton("Language..."); |
|||
btnLanguage.addActionListener(this); |
|||
btnHeaderInfo = new JButton("Header Info..."); |
|||
btnHeaderInfo.addActionListener(this); |
|||
panel_3.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5)); |
|||
panel_3.add(btnLanguage); |
|||
panel_3.add(btnHeaderInfo); |
|||
|
|||
JLabel lblExthRecords = new JLabel("EXTH Records"); |
|||
lblExthRecords.setFont(new Font("Lucida Grande", Font.BOLD, 13)); |
|||
GridBagConstraints gbc_lblExthRecords = new GridBagConstraints(); |
|||
gbc_lblExthRecords.anchor = GridBagConstraints.NORTHEAST; |
|||
gbc_lblExthRecords.insets = new Insets(0, 0, 5, 5); |
|||
gbc_lblExthRecords.gridx = 0; |
|||
gbc_lblExthRecords.gridy = 5; |
|||
panel.add(lblExthRecords, gbc_lblExthRecords); |
|||
|
|||
JScrollPane scrollPane = new JScrollPane(); |
|||
GridBagConstraints gbc_scrollPane = new GridBagConstraints(); |
|||
gbc_scrollPane.fill = GridBagConstraints.BOTH; |
|||
gbc_scrollPane.insets = new Insets(0, 0, 5, 5); |
|||
gbc_scrollPane.gridx = 2; |
|||
gbc_scrollPane.gridy = 5; |
|||
panel.add(scrollPane, gbc_scrollPane); |
|||
|
|||
table = new CustomJTable(model); |
|||
table.getColumnModel().getColumn(0).setPreferredWidth(100); |
|||
table.getSelectionModel().addListSelectionListener(this); |
|||
scrollPane.setViewportView(table); |
|||
|
|||
JPanel panel_1 = new JPanel(); |
|||
GridBagConstraints gbc_panel_1 = new GridBagConstraints(); |
|||
gbc_panel_1.insets = new Insets(0, 0, 5, 5); |
|||
gbc_panel_1.anchor = GridBagConstraints.EAST; |
|||
gbc_panel_1.gridx = 2; |
|||
gbc_panel_1.gridy = 6; |
|||
panel.add(panel_1, gbc_panel_1); |
|||
panel_1.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5)); |
|||
|
|||
buttonAdd = new JButton("+"); |
|||
buttonAdd.addActionListener(this); |
|||
panel_1.add(buttonAdd); |
|||
|
|||
buttonRemove = new JButton("-"); |
|||
buttonRemove.addActionListener(this); |
|||
buttonRemove.setEnabled(false); |
|||
panel_1.add(buttonRemove); |
|||
|
|||
if (MobiCommon.safeMode) |
|||
{ |
|||
buttonAdd.setEnabled(false); |
|||
buttonRemove.setEnabled(false); |
|||
} |
|||
|
|||
JSeparator separator_1 = new JSeparator(); |
|||
separator_1.setForeground(Color.DARK_GRAY); |
|||
GridBagConstraints gbc_separator_1 = new GridBagConstraints(); |
|||
gbc_separator_1.insets = new Insets(0, 0, 5, 5); |
|||
gbc_separator_1.fill = GridBagConstraints.HORIZONTAL; |
|||
gbc_separator_1.gridwidth = 3; |
|||
gbc_separator_1.gridx = 0; |
|||
gbc_separator_1.gridy = 7; |
|||
panel.add(separator_1, gbc_separator_1); |
|||
|
|||
Component separatorStrut2 = Box.createVerticalStrut(10); |
|||
GridBagConstraints gbc_strut2 = new GridBagConstraints(); |
|||
gbc_strut2.fill = GridBagConstraints.VERTICAL; |
|||
gbc_strut2.gridwidth = 1; |
|||
gbc_strut2.insets = new Insets(0, 0, 5, 0); |
|||
gbc_strut2.gridx = 3; |
|||
gbc_strut2.gridy = 7; |
|||
panel.add(separatorStrut2, gbc_strut2); |
|||
|
|||
JPanel panel_2 = new JPanel(); |
|||
GridBagConstraints gbc_panel_2 = new GridBagConstraints(); |
|||
gbc_panel_2.insets = new Insets(0, 0, 0, 5); |
|||
gbc_panel_2.anchor = GridBagConstraints.WEST; |
|||
gbc_panel_2.gridwidth = 3; |
|||
gbc_panel_2.fill = GridBagConstraints.VERTICAL; |
|||
gbc_panel_2.gridx = 0; |
|||
gbc_panel_2.gridy = 8; |
|||
panel.add(panel_2, gbc_panel_2); |
|||
|
|||
buttonSave = new JButton("Save"); |
|||
buttonSave.addActionListener(this); |
|||
panel_2.add(buttonSave); |
|||
|
|||
JMenuBar menuBar = new JMenuBar(); |
|||
frame.setJMenuBar(menuBar); |
|||
|
|||
JMenu mnFile = new JMenu("File"); |
|||
menuBar.add(mnFile); |
|||
|
|||
mntmOpen = new JMenuItem("Open..."); |
|||
mntmOpen.addActionListener(this); |
|||
mnFile.add(mntmOpen); |
|||
|
|||
mntmSave = new JMenuItem("Save"); |
|||
mntmSave.addActionListener(this); |
|||
mnFile.add(mntmSave); |
|||
} |
|||
|
|||
private File getSourceFile() |
|||
{ |
|||
if (openFileChooser == null) |
|||
{ |
|||
openFileChooser = new FileDialog(frame, "Select mobi file", FileDialog.LOAD); |
|||
openFileChooser.setFilenameFilter(new MobiFileFilter()); |
|||
} |
|||
|
|||
openFileChooser.setVisible(true); |
|||
|
|||
File source = null; |
|||
String dir = openFileChooser.getDirectory(); |
|||
String file = openFileChooser.getFile(); |
|||
if ((dir != null) && (file != null)) |
|||
source = new File(dir, file); |
|||
|
|||
if (source != null) |
|||
{ |
|||
try |
|||
{ |
|||
model.setModel(source); |
|||
} |
|||
catch (GuiException e) |
|||
{ |
|||
showAlert("Could not parse mobi file: " + e.getMessage()); |
|||
System.exit(0); |
|||
} |
|||
|
|||
lblInputFilename.setText(source.getAbsolutePath()); |
|||
tfFullName.setText(model.getFullName()); |
|||
|
|||
outputFile = getOutputFile(source); |
|||
lblOutputFilename.setText(outputFile.getAbsolutePath()); |
|||
|
|||
// we trigger the window modified indicator when we set tfFullName
|
|||
//
|
|||
setWindowChangedStatus(false); |
|||
packHeader = false; |
|||
} |
|||
|
|||
return source; |
|||
} |
|||
|
|||
private File getOutputFile(File inputFile) |
|||
{ |
|||
File parent = inputFile.getParentFile(); |
|||
String inputName = inputFile.getName(); |
|||
int dot = inputName.lastIndexOf('.'); |
|||
String outputName; |
|||
|
|||
if (dot == -1) |
|||
outputName = inputName + "_new"; |
|||
else |
|||
outputName = inputName.substring(0, dot) + "_new" |
|||
+ inputName.substring(dot); |
|||
|
|||
return new File(parent, outputName); |
|||
} |
|||
|
|||
private void showAlert(String message) |
|||
{ |
|||
JOptionPane.showMessageDialog(frame, message); |
|||
} |
|||
|
|||
// scroll to the newly added/deleted row
|
|||
//
|
|||
public void tableChanged(TableModelEvent event) |
|||
{ |
|||
int eventType = event.getType(); |
|||
int row = event.getLastRow(); |
|||
if (eventType == TableModelEvent.INSERT) |
|||
{ |
|||
table.getSelectionModel().setSelectionInterval(row, row); |
|||
table.scrollRectToVisible(new Rectangle(table.getCellRect(row, 0, |
|||
true))); |
|||
packHeader = true; |
|||
setWindowChangedStatus(true); |
|||
} |
|||
else if (eventType == TableModelEvent.DELETE) |
|||
{ |
|||
boolean select = false; |
|||
int numRows = model.getRowCount(); |
|||
if (numRows > row) |
|||
{ |
|||
select = true; |
|||
} |
|||
else if (numRows > 0) |
|||
{ |
|||
select = true; |
|||
row = numRows - 1; |
|||
} |
|||
if (select) |
|||
{ |
|||
table.getSelectionModel().setSelectionInterval(row, row); |
|||
table.scrollRectToVisible(new Rectangle(table.getCellRect(row, |
|||
0, true))); |
|||
} |
|||
packHeader = true; |
|||
setWindowChangedStatus(true); |
|||
} |
|||
else if (eventType == TableModelEvent.UPDATE) |
|||
{ |
|||
packHeader = true; |
|||
setWindowChangedStatus(true); |
|||
} |
|||
} |
|||
|
|||
protected void setWindowChangedStatus(boolean status) |
|||
{ |
|||
frame.getRootPane().putClientProperty("Window.documentModified", |
|||
Boolean.valueOf(status)); |
|||
} |
|||
|
|||
// we implement the LanguageModel interface because we want to intercept the
|
|||
// setLanguages() call so that we can set the window status changed flag
|
|||
//
|
|||
public int getLocale() |
|||
{ |
|||
return model.getLocale(); |
|||
} |
|||
|
|||
// we implement the LanguageModel interface because we want to intercept the
|
|||
// setLanguages() call so that we can set the window status changed flag
|
|||
//
|
|||
public int getDictInput() |
|||
{ |
|||
return model.getDictInput(); |
|||
} |
|||
|
|||
// we implement the LanguageModel interface because we want to intercept the
|
|||
// setLanguages() call so that we can set the window status changed flag
|
|||
//
|
|||
public int getDictOutput() |
|||
{ |
|||
return model.getDictOutput(); |
|||
} |
|||
|
|||
// we implement the LanguageModel interface because we want to intercept the
|
|||
// setLanguages() call so that we can set the window status changed flag
|
|||
//
|
|||
public void setLanguages(int locale, int dictInput, int dictOutput) |
|||
{ |
|||
model.setLanguages(locale, dictInput, dictOutput); |
|||
setWindowChangedStatus(true); |
|||
} |
|||
protected JMenuItem getMntmOpen() { |
|||
return mntmOpen; |
|||
} |
|||
protected JMenuItem getMntmSave() { |
|||
return mntmSave; |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
package gui; |
|||
|
|||
public interface MetaInfoProvider |
|||
{ |
|||
public String getMetaInfo(); |
|||
} |
@ -0,0 +1,32 @@ |
|||
package gui; |
|||
|
|||
import java.io.File; |
|||
import java.io.FilenameFilter; |
|||
|
|||
import javax.swing.filechooser.FileFilter; |
|||
|
|||
class MobiFileFilter extends FileFilter implements FilenameFilter |
|||
{ |
|||
|
|||
// to make it work with JFileChooser
|
|||
//
|
|||
public boolean accept(File f) |
|||
{ |
|||
if (f.isDirectory()) return true; |
|||
|
|||
return (f.getName().toLowerCase().endsWith(".azw") || f.getName().toLowerCase().endsWith(".mobi")); |
|||
} |
|||
|
|||
public String getDescription() |
|||
{ |
|||
return "*.azw,*.mobi"; |
|||
} |
|||
|
|||
// to make it work with java.awt.FileDialog
|
|||
//
|
|||
public boolean accept(File f, String name) |
|||
{ |
|||
return (name.toLowerCase().endsWith(".azw") || name.toLowerCase().endsWith(".mobi")); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,175 @@ |
|||
package gui; |
|||
|
|||
import java.awt.BorderLayout; |
|||
import java.awt.FlowLayout; |
|||
|
|||
import javax.swing.JButton; |
|||
import javax.swing.JDialog; |
|||
import javax.swing.JFrame; |
|||
import javax.swing.JPanel; |
|||
import javax.swing.border.EmptyBorder; |
|||
import javax.swing.JLabel; |
|||
import java.awt.GridBagLayout; |
|||
import java.awt.GridBagConstraints; |
|||
import javax.swing.JComboBox; |
|||
import java.awt.Insets; |
|||
import java.awt.Font; |
|||
import java.awt.event.ActionEvent; |
|||
import java.awt.event.ActionListener; |
|||
|
|||
import javax.swing.JTextField; |
|||
|
|||
import mobimeta.EXTHRecord; |
|||
|
|||
public class NewRecordDialog extends JDialog implements ActionListener |
|||
{ |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
private static final long serialVersionUID = 1L; |
|||
private final JPanel contentPanel = new JPanel(); |
|||
private JTextField tfValue; |
|||
private JComboBox typeCombo; |
|||
private EXTHAddRecordListener listener; |
|||
private JButton addButton; |
|||
private JButton cancelButton; |
|||
|
|||
|
|||
/** |
|||
* Create the dialog. |
|||
*/ |
|||
public NewRecordDialog(JFrame parent, EXTHAddRecordListener listener) |
|||
{ |
|||
super(parent, true); |
|||
|
|||
this.listener = listener; |
|||
String[] comboValues = new String[EXTHRecord.knownTypes.length]; |
|||
for (int i=0; i<comboValues.length; i++) |
|||
{ |
|||
comboValues[i] = EXTHRecord.knownTypes[i] + " (" + EXTHRecord.knownDesc[i] + ")"; |
|||
} |
|||
|
|||
setBounds(100, 100, 450, 300); |
|||
getContentPane().setLayout(new BorderLayout()); |
|||
contentPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); |
|||
getContentPane().add(contentPanel, BorderLayout.CENTER); |
|||
GridBagLayout gbl_contentPanel = new GridBagLayout(); |
|||
gbl_contentPanel.columnWidths = new int[]{0, 0}; |
|||
gbl_contentPanel.rowHeights = new int[]{0, 0, 0}; |
|||
gbl_contentPanel.columnWeights = new double[]{0.0, 1.0}; |
|||
gbl_contentPanel.rowWeights = new double[]{0.0, 0.0, Double.MIN_VALUE}; |
|||
contentPanel.setLayout(gbl_contentPanel); |
|||
{ |
|||
JLabel lblType = new JLabel("Type"); |
|||
lblType.setFont(new Font("Lucida Grande", Font.BOLD, 13)); |
|||
GridBagConstraints gbc_lblType = new GridBagConstraints(); |
|||
gbc_lblType.anchor = GridBagConstraints.EAST; |
|||
gbc_lblType.insets = new Insets(0, 0, 5, 5); |
|||
gbc_lblType.gridx = 0; |
|||
gbc_lblType.gridy = 0; |
|||
contentPanel.add(lblType, gbc_lblType); |
|||
} |
|||
{ |
|||
typeCombo = new JComboBox(comboValues); |
|||
GridBagConstraints gbc_typeCombo = new GridBagConstraints(); |
|||
gbc_typeCombo.insets = new Insets(0, 0, 5, 0); |
|||
gbc_typeCombo.fill = GridBagConstraints.HORIZONTAL; |
|||
gbc_typeCombo.gridx = 1; |
|||
gbc_typeCombo.gridy = 0; |
|||
contentPanel.add(typeCombo, gbc_typeCombo); |
|||
} |
|||
{ |
|||
JLabel lblValue = new JLabel("Value"); |
|||
lblValue.setFont(new Font("Lucida Grande", Font.BOLD, 13)); |
|||
GridBagConstraints gbc_lblValue = new GridBagConstraints(); |
|||
gbc_lblValue.anchor = GridBagConstraints.EAST; |
|||
gbc_lblValue.insets = new Insets(0, 0, 0, 5); |
|||
gbc_lblValue.gridx = 0; |
|||
gbc_lblValue.gridy = 1; |
|||
contentPanel.add(lblValue, gbc_lblValue); |
|||
} |
|||
{ |
|||
tfValue = new JTextField(); |
|||
GridBagConstraints gbc_tfValue = new GridBagConstraints(); |
|||
gbc_tfValue.fill = GridBagConstraints.HORIZONTAL; |
|||
gbc_tfValue.gridx = 1; |
|||
gbc_tfValue.gridy = 1; |
|||
contentPanel.add(tfValue, gbc_tfValue); |
|||
tfValue.setColumns(20); |
|||
} |
|||
{ |
|||
JPanel buttonPane = new JPanel(); |
|||
buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); |
|||
getContentPane().add(buttonPane, BorderLayout.SOUTH); |
|||
{ |
|||
addButton = new JButton("Add"); |
|||
addButton.addActionListener(this); |
|||
buttonPane.add(addButton); |
|||
getRootPane().setDefaultButton(addButton); |
|||
} |
|||
{ |
|||
cancelButton = new JButton("Cancel"); |
|||
cancelButton.addActionListener(this); |
|||
buttonPane.add(cancelButton); |
|||
} |
|||
} |
|||
|
|||
setDefaultCloseOperation(DISPOSE_ON_CLOSE); |
|||
} |
|||
|
|||
protected JComboBox getTypeCombo() { |
|||
return typeCombo; |
|||
} |
|||
|
|||
public void actionPerformed(ActionEvent event) |
|||
{ |
|||
Object source = event.getSource(); |
|||
|
|||
if (source == addButton) |
|||
{ |
|||
String value = tfValue.getText(); |
|||
if (value.length() == 0) return; |
|||
|
|||
int typeIndex = typeCombo.getSelectedIndex(); |
|||
if (typeIndex == -1) return; |
|||
|
|||
int type = EXTHRecord.knownTypes[typeIndex]; |
|||
EXTHRecord rec = null; |
|||
if (EXTHRecord.isBooleanType(type)) // this is an ugly hack - we really should present a checkbox to the user
|
|||
{ |
|||
boolean boolValue = false; |
|||
if (value.equals("1") |
|||
|| |
|||
value.toLowerCase().equals("true") |
|||
|| |
|||
value.toLowerCase().equals("on") |
|||
|| |
|||
value.toLowerCase().equals("yes")) |
|||
{ |
|||
boolValue = true; |
|||
} |
|||
rec = new EXTHRecord(type, boolValue); |
|||
} |
|||
else |
|||
rec = new EXTHRecord(type, value, GuiModel.getCharacterEncoding()); |
|||
listener.addEXTHRecord(rec); |
|||
setVisible(false); |
|||
dispose(); |
|||
} |
|||
else if (source == cancelButton) |
|||
{ |
|||
setVisible(false); |
|||
dispose(); |
|||
} |
|||
} |
|||
protected JButton getAddButton() { |
|||
return addButton; |
|||
} |
|||
protected JButton getCancelButton() { |
|||
return cancelButton; |
|||
} |
|||
protected JTextField getTfValue() { |
|||
return tfValue; |
|||
} |
|||
} |
@ -0,0 +1,185 @@ |
|||
package mobimeta; |
|||
|
|||
import java.io.*; |
|||
import java.util.*; |
|||
|
|||
public class EXTHHeader |
|||
{ |
|||
private byte[] identifier = { 69, 88, 84, 72 }; |
|||
private byte[] headerLength = { 0, 0, 0, 0 }; |
|||
private byte[] recordCount = { 0, 0, 0, 0 }; |
|||
private List<EXTHRecord> recordList = null; |
|||
|
|||
public EXTHHeader() |
|||
{ |
|||
recordList = new LinkedList<EXTHRecord>(); |
|||
} |
|||
|
|||
public EXTHHeader(List<EXTHRecord> list) |
|||
{ |
|||
setRecordList(list); |
|||
} |
|||
|
|||
public EXTHHeader(InputStream in) throws IOException |
|||
{ |
|||
MobiCommon.logMessage("*** EXTHHeader ***"); |
|||
|
|||
StreamUtils.readByteArray(in, identifier); |
|||
if ((identifier[0] != 69) |
|||
|| |
|||
(identifier[1] != 88) |
|||
|| |
|||
(identifier[2] != 84) |
|||
|| |
|||
(identifier[3] != 72)) |
|||
{ |
|||
throw new IOException("Expected to find EXTH header identifier" |
|||
+ " EXTH but got something else instead"); |
|||
} |
|||
|
|||
StreamUtils.readByteArray(in, headerLength); |
|||
|
|||
if (MobiCommon.debug) |
|||
{ |
|||
MobiCommon.logMessage("EXTH header length: " |
|||
+ StreamUtils.byteArrayToLong(headerLength)); |
|||
} |
|||
|
|||
StreamUtils.readByteArray(in, recordCount); |
|||
int count = StreamUtils.byteArrayToInt(recordCount); |
|||
MobiCommon.logMessage("EXTH record count: " + count); |
|||
|
|||
recordList = new LinkedList<EXTHRecord>(); |
|||
for (int i=0; i<count; i++) |
|||
{ |
|||
recordList.add(new EXTHRecord(in)); |
|||
} |
|||
|
|||
int padding = paddingSize(dataSize()); |
|||
MobiCommon.logMessage("padding size: " + padding); |
|||
for (int i=0; i<padding; i++) StreamUtils.readByte(in); |
|||
} |
|||
|
|||
public int size() |
|||
{ |
|||
int dataSize = dataSize(); |
|||
return 12 + dataSize + paddingSize(dataSize); |
|||
} |
|||
|
|||
public void recomputeFields() |
|||
{ |
|||
StreamUtils.intToByteArray(size(), headerLength); |
|||
StreamUtils.intToByteArray(recordList.size(), recordCount); |
|||
} |
|||
|
|||
public List<EXTHRecord> getRecordList() |
|||
{ |
|||
LinkedList<EXTHRecord> list = new LinkedList<EXTHRecord>(); |
|||
for (EXTHRecord rec : recordList) |
|||
{ |
|||
list.add(rec.copy()); |
|||
} |
|||
|
|||
return list; |
|||
} |
|||
|
|||
public void setRecordList(List<EXTHRecord> list) |
|||
{ |
|||
recordList = new LinkedList<EXTHRecord>(); |
|||
if (list != null) |
|||
{ |
|||
for (EXTHRecord rec : list) |
|||
{ |
|||
recordList.add(rec.copy()); |
|||
} |
|||
} |
|||
recomputeFields(); |
|||
} |
|||
|
|||
public void removeRecordsWithType(int type) |
|||
{ |
|||
boolean changed = false; |
|||
for (EXTHRecord rec : recordList) |
|||
{ |
|||
if (rec.getRecordType() == type) |
|||
{ |
|||
recordList.remove(rec); |
|||
changed = true; |
|||
} |
|||
} |
|||
|
|||
if (changed) recomputeFields(); |
|||
} |
|||
|
|||
public boolean recordsWithTypeExist(int type) |
|||
{ |
|||
for (EXTHRecord rec : recordList) |
|||
{ |
|||
if (rec.getRecordType() == type) return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
public void setAllRecordsWithTypeToString(int type, |
|||
String s, |
|||
String encoding) |
|||
{ |
|||
boolean changed = false; |
|||
for (EXTHRecord rec : recordList) |
|||
{ |
|||
if (rec.getRecordType() == type) |
|||
{ |
|||
rec.setData(s, encoding); |
|||
changed = true; |
|||
} |
|||
} |
|||
|
|||
if (changed) recomputeFields(); |
|||
} |
|||
|
|||
public void addRecord(int recType, String s, String encoding) |
|||
{ |
|||
EXTHRecord rec = new EXTHRecord |
|||
(recType, StreamUtils.stringToByteArray(s, encoding)); |
|||
recordList.add(rec); |
|||
recomputeFields(); |
|||
} |
|||
|
|||
public void addRecord(int recType, byte[] buffer) |
|||
{ |
|||
recordList.add(new EXTHRecord(recType, buffer)); |
|||
recomputeFields(); |
|||
} |
|||
|
|||
protected int dataSize() |
|||
{ |
|||
int size = 0; |
|||
for (EXTHRecord rec : recordList) |
|||
{ |
|||
size += rec.size(); |
|||
} |
|||
|
|||
return size; |
|||
} |
|||
|
|||
protected int paddingSize(int dataSize) |
|||
{ |
|||
int paddingSize = dataSize % 4; |
|||
if (paddingSize != 0) paddingSize = 4 - paddingSize; |
|||
|
|||
return paddingSize; |
|||
} |
|||
|
|||
public void write(OutputStream out) throws IOException |
|||
{ |
|||
out.write(identifier); |
|||
out.write(headerLength); |
|||
out.write(recordCount); |
|||
for (EXTHRecord rec : recordList) |
|||
{ |
|||
rec.write(out); |
|||
} |
|||
int padding = paddingSize(dataSize()); |
|||
for (int i=0; i<padding; i++) out.write(0); |
|||
} |
|||
} |
@ -0,0 +1,251 @@ |
|||
package mobimeta; |
|||
|
|||
import java.io.*; |
|||
import java.util.HashMap; |
|||
import java.util.HashSet; |
|||
|
|||
public class EXTHRecord |
|||
{ |
|||
// if a type exists in booleanTypes, then it is assumed to have boolean
|
|||
// values
|
|||
// if a type exists in knownTypes but not in booleanTypes, then it is
|
|||
// assumed to have string values
|
|||
public final static int[] booleanTypes = |
|||
{ 404 }; |
|||
public final static int[] knownTypes = |
|||
{ 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, |
|||
114, 118, 119, 200, 404, 501, 503, 504 }; |
|||
public final static String[] knownDesc = |
|||
{ "author", "publisher", "imprint", "description", "ISBN", "subject", |
|||
"publishing date", "review", "contributor", "rights", |
|||
"subject code", "type", "source", "ASIN", "version number", |
|||
"retail price", "retail price currency", "dictionary short name", |
|||
"TTS off", "CDE type", "updated title", "ASIN" }; |
|||
private static HashMap<Integer, String> typeHash; |
|||
private static HashSet<Integer> booleanTypesSet; |
|||
|
|||
private byte[] recordType = { 0, 0, 0, 0 }; |
|||
private byte[] recordLength = { 0, 0, 0, 0 }; |
|||
private byte[] recordData = null; |
|||
|
|||
static |
|||
{ |
|||
typeHash = new HashMap<Integer,String>(knownTypes.length); |
|||
for (int i=0; i<knownTypes.length; i++) |
|||
typeHash.put(Integer.valueOf(knownTypes[i]), knownDesc[i]); |
|||
|
|||
booleanTypesSet = new HashSet<Integer>(booleanTypes.length); |
|||
for (int i=0; i<booleanTypes.length; i++) |
|||
booleanTypesSet.add(Integer.valueOf(booleanTypes[i])); |
|||
} |
|||
|
|||
public static boolean isBooleanType(int type) |
|||
{ |
|||
return booleanTypesSet.contains(Integer.valueOf(type)); |
|||
} |
|||
|
|||
public static boolean isKnownType(int type) |
|||
{ |
|||
return typeHash.containsKey(Integer.valueOf(type)); |
|||
} |
|||
|
|||
public static String getDescriptionForType(int type) |
|||
{ |
|||
return typeHash.get(Integer.valueOf(type)); |
|||
} |
|||
|
|||
public EXTHRecord(int recType, String data, String characterEncoding) |
|||
{ |
|||
this(recType, StreamUtils.stringToByteArray(data, characterEncoding)); |
|||
} |
|||
|
|||
public EXTHRecord(int recType, boolean data) |
|||
{ |
|||
StreamUtils.intToByteArray(recType, recordType); |
|||
recordData = new byte[1]; |
|||
recordData[0] = data?(byte)1:0; |
|||
StreamUtils.intToByteArray(size(), recordLength); |
|||
} |
|||
|
|||
public EXTHRecord(int recType, byte[] data) |
|||
{ |
|||
StreamUtils.intToByteArray(recType, recordType); |
|||
int len = (data==null)?0:data.length; |
|||
StreamUtils.intToByteArray(len + 8, recordLength); |
|||
recordData = new byte[len]; |
|||
if (len > 0) |
|||
{ |
|||
System.arraycopy(data, 0, recordData, 0, len); |
|||
} |
|||
} |
|||
|
|||
public EXTHRecord(InputStream in) throws IOException |
|||
{ |
|||
MobiCommon.logMessage("*** EXTHRecord ***"); |
|||
|
|||
StreamUtils.readByteArray(in, recordType); |
|||
StreamUtils.readByteArray(in, recordLength); |
|||
|
|||
int len = StreamUtils.byteArrayToInt(recordLength); |
|||
if (len < 8) throw new IOException("Invalid EXTH record length"); |
|||
|
|||
recordData = new byte[len - 8]; |
|||
StreamUtils.readByteArray(in, recordData); |
|||
|
|||
if (MobiCommon.debug) |
|||
{ |
|||
int recType = StreamUtils.byteArrayToInt(recordType); |
|||
System.out.print("EXTH record type: "); |
|||
switch (recType) |
|||
{ |
|||
case 100: |
|||
MobiCommon.logMessage("author"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 101: |
|||
MobiCommon.logMessage("publisher"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 103: |
|||
MobiCommon.logMessage("description"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 104: |
|||
MobiCommon.logMessage("isbn"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 105: |
|||
MobiCommon.logMessage("subject"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 106: |
|||
MobiCommon.logMessage("publishingdate"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 109: |
|||
MobiCommon.logMessage("rights"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 113: |
|||
case 504: |
|||
MobiCommon.logMessage("asin"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 118: |
|||
MobiCommon.logMessage("retail price"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 119: |
|||
MobiCommon.logMessage("retail price currency"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 200: |
|||
MobiCommon.logMessage("dictionary short name"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
case 404: |
|||
MobiCommon.logMessage("text to speech"); |
|||
int ttsflag = StreamUtils.byteArrayToInt(recordData); |
|||
MobiCommon.logMessage((ttsflag == 0)?"enabled":"disabled"); |
|||
break; |
|||
case 501: |
|||
MobiCommon.logMessage("cdetype"); |
|||
MobiCommon.logMessage |
|||
(StreamUtils.byteArrayToString(recordData)); |
|||
break; |
|||
default: |
|||
MobiCommon.logMessage(Integer.toString(recType)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public int getRecordType() |
|||
{ |
|||
return StreamUtils.byteArrayToInt(recordType); |
|||
} |
|||
|
|||
public byte[] getData() |
|||
{ |
|||
return recordData; |
|||
} |
|||
|
|||
public int getDataLength() |
|||
{ |
|||
return recordData.length; |
|||
} |
|||
|
|||
public int size() |
|||
{ |
|||
return getDataLength() + 8; |
|||
} |
|||
|
|||
public void setData(String s, String encoding) |
|||
{ |
|||
recordData = StreamUtils.stringToByteArray(s, encoding); |
|||
StreamUtils.intToByteArray(size(), recordLength); |
|||
} |
|||
|
|||
public void setData(int value) |
|||
{ |
|||
if (recordData == null) |
|||
{ |
|||
recordData = new byte[4]; |
|||
StreamUtils.intToByteArray(size(), recordLength); |
|||
} |
|||
|
|||
StreamUtils.intToByteArray(value, recordData); |
|||
} |
|||
|
|||
public void setData(boolean value) |
|||
{ |
|||
if (recordData == null) |
|||
{ |
|||
recordData = new byte[1]; |
|||
StreamUtils.intToByteArray(size(), recordLength); |
|||
} |
|||
|
|||
StreamUtils.intToByteArray(value?1:0, recordData); |
|||
} |
|||
|
|||
public EXTHRecord copy() |
|||
{ |
|||
return new EXTHRecord(StreamUtils.byteArrayToInt(recordType), |
|||
recordData); |
|||
} |
|||
|
|||
public boolean isKnownType() |
|||
{ |
|||
return isKnownType(StreamUtils.byteArrayToInt(recordType)); |
|||
} |
|||
|
|||
public String getTypeDescription() |
|||
{ |
|||
return getDescriptionForType(StreamUtils.byteArrayToInt(recordType)); |
|||
} |
|||
|
|||
public void write(OutputStream out) throws IOException |
|||
{ |
|||
if (MobiCommon.debug) |
|||
{ |
|||
MobiCommon.logMessage("*** Write EXTHRecord ***"); |
|||
MobiCommon.logMessage(StreamUtils.dumpByteArray(recordType)); |
|||
MobiCommon.logMessage(StreamUtils.dumpByteArray(recordLength)); |
|||
MobiCommon.logMessage(StreamUtils.dumpByteArray(recordData)); |
|||
MobiCommon.logMessage("************************"); |
|||
} |
|||
out.write(recordType); |
|||
out.write(recordLength); |
|||
out.write(recordData); |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
package mobimeta; |
|||
|
|||
public class MobiCommon |
|||
{ |
|||
public static boolean debug = false; |
|||
|
|||
// safe mode avoids changing the size of the mobi header
|
|||
//
|
|||
public static boolean safeMode = false; |
|||
|
|||
public static void logMessage(String message) |
|||
{ |
|||
if (debug) System.out.println(message); |
|||
} |
|||
} |
@ -0,0 +1,526 @@ |
|||
package mobimeta; |
|||
|
|||
import java.io.*; |
|||
import java.util.*; |
|||
|
|||
public class MobiHeader |
|||
{ |
|||
private byte[] compression = { 0, 0 }; |
|||
private byte[] unused0 = { 0, 0 }; |
|||
private byte[] textLength = { 0, 0, 0, 0 }; |
|||
private byte[] recordCount = { 0, 0 }; |
|||
private byte[] recordSize = { 0, 0 }; |
|||
private byte[] encryptionType = { 0, 0 }; |
|||
private byte[] unused1 = { 0, 0 }; |
|||
private byte[] identifier = { 0, 0, 0, 0 }; |
|||
private byte[] headerLength = { 0, 0, 0, 0 }; // from offset 0x10
|
|||
private byte[] mobiType = { 0, 0, 0, 0 }; |
|||
private byte[] textEncoding = { 0, 0, 0, 0 }; |
|||
private byte[] uniqueID = { 0, 0, 0, 0 }; |
|||
private byte[] fileVersion = { 0, 0, 0, 0 }; |
|||
private byte[] orthographicIndex = { 0, 0, 0, 0 }; |
|||
private byte[] inflectionIndex = { 0, 0, 0, 0 }; |
|||
private byte[] indexNames = { 0, 0, 0, 0 }; |
|||
private byte[] indexKeys = { 0, 0, 0, 0 }; |
|||
private byte[] extraIndex0 = { 0, 0, 0, 0 }; |
|||
private byte[] extraIndex1 = { 0, 0, 0, 0 }; |
|||
private byte[] extraIndex2 = { 0, 0, 0, 0 }; |
|||
private byte[] extraIndex3 = { 0, 0, 0, 0 }; |
|||
private byte[] extraIndex4 = { 0, 0, 0, 0 }; |
|||
private byte[] extraIndex5 = { 0, 0, 0, 0 }; |
|||
private byte[] firstNonBookIndex = { 0, 0, 0, 0 }; |
|||
private byte[] fullNameOffset = { 0, 0, 0, 0 }; |
|||
private byte[] fullNameLength = { 0, 0, 0, 0 }; |
|||
private byte[] locale = { 0, 0, 0, 0 }; |
|||
private byte[] inputLanguage = { 0, 0, 0, 0 }; |
|||
private byte[] outputLanguage = { 0, 0, 0, 0 }; |
|||
private byte[] minVersion = { 0, 0, 0, 0 }; |
|||
private byte[] firstImageIndex = { 0, 0, 0, 0 }; |
|||
private byte[] huffmanRecordOffset = { 0, 0, 0, 0 }; |
|||
private byte[] huffmanRecordCount = { 0, 0, 0, 0 }; |
|||
private byte[] huffmanTableOffset = { 0, 0, 0, 0 }; |
|||
private byte[] huffmanTableLength = { 0, 0, 0, 0 }; |
|||
private byte[] exthFlags = { 0, 0, 0, 0 }; |
|||
private byte[] restOfMobiHeader = null; |
|||
private EXTHHeader exthHeader = null; |
|||
private byte[] remainder = null; |
|||
// end of useful data
|
|||
|
|||
|
|||
private byte[] fullName = null; |
|||
private String characterEncoding = null; |
|||
|
|||
public MobiHeader(InputStream in, long mobiHeaderSize) throws IOException |
|||
{ |
|||
MobiCommon.logMessage("*** MobiHeader ***"); |
|||
MobiCommon.logMessage("compression"); |
|||
StreamUtils.readByteArray(in, compression); |
|||
StreamUtils.readByteArray(in, unused0); |
|||
StreamUtils.readByteArray(in, textLength); |
|||
StreamUtils.readByteArray(in, recordCount); |
|||
StreamUtils.readByteArray(in, recordSize); |
|||
MobiCommon.logMessage("encryptionType"); |
|||
StreamUtils.readByteArray(in, encryptionType); |
|||
StreamUtils.readByteArray(in, unused1); |
|||
|
|||
StreamUtils.readByteArray(in, identifier); |
|||
if (MobiCommon.debug) |
|||
{ |
|||
MobiCommon.logMessage("identifier: " |
|||
+ StreamUtils.byteArrayToString(identifier)); |
|||
} |
|||
if ((identifier[0] != 77) |
|||
|| |
|||
(identifier[1] != 79) |
|||
|| |
|||
(identifier[2] != 66) |
|||
|| |
|||
(identifier[3] != 73)) |
|||
{ |
|||
throw new IOException("Did not get expected MOBI identifier"); |
|||
} |
|||
|
|||
// this value will determine the size of restOfMobiHeader[]
|
|||
//
|
|||
StreamUtils.readByteArray(in, headerLength); |
|||
int headLen = StreamUtils.byteArrayToInt(headerLength); |
|||
restOfMobiHeader = new byte[headLen + 16 - 132]; |
|||
if (MobiCommon.debug) |
|||
{ |
|||
MobiCommon.logMessage("headerLength: " + headLen); |
|||
} |
|||
|
|||
StreamUtils.readByteArray(in, mobiType); |
|||
if (MobiCommon.debug) |
|||
{ |
|||
MobiCommon.logMessage("mobiType: " |
|||
+ StreamUtils.byteArrayToInt(mobiType)); |
|||
} |
|||
|
|||
StreamUtils.readByteArray(in, textEncoding); |
|||
switch (StreamUtils.byteArrayToInt(textEncoding)) |
|||
{ |
|||
case 1252: |
|||
characterEncoding = "Cp1252"; |
|||
break; |
|||
case 65001: |
|||
characterEncoding = "UTF-8"; |
|||
break; |
|||
default: |
|||
characterEncoding = null; |
|||
break; |
|||
} |
|||
MobiCommon.logMessage("text encoding: " + characterEncoding); |
|||
|
|||
StreamUtils.readByteArray(in, uniqueID); |
|||
StreamUtils.readByteArray(in, fileVersion); |
|||
StreamUtils.readByteArray(in, orthographicIndex); |
|||
StreamUtils.readByteArray(in, inflectionIndex); |
|||
StreamUtils.readByteArray(in, indexNames); |
|||
StreamUtils.readByteArray(in, indexKeys); |
|||
StreamUtils.readByteArray(in, extraIndex0); |
|||
StreamUtils.readByteArray(in, extraIndex1); |
|||
StreamUtils.readByteArray(in, extraIndex2); |
|||
StreamUtils.readByteArray(in, extraIndex3); |
|||
StreamUtils.readByteArray(in, extraIndex4); |
|||
StreamUtils.readByteArray(in, extraIndex5); |
|||
StreamUtils.readByteArray(in, firstNonBookIndex); |
|||
StreamUtils.readByteArray(in, fullNameOffset); |
|||
if (MobiCommon.debug) |
|||
{ |
|||
MobiCommon.logMessage("full name offset: " |
|||
+ StreamUtils.byteArrayToInt(fullNameOffset)); |
|||
} |
|||
|
|||
StreamUtils.readByteArray(in, fullNameLength); |
|||
int fullNameLen = StreamUtils.byteArrayToInt(fullNameLength); |
|||
MobiCommon.logMessage("full name length: " + fullNameLen); |
|||
StreamUtils.readByteArray(in, locale); |
|||
StreamUtils.readByteArray(in, inputLanguage); |
|||
StreamUtils.readByteArray(in, outputLanguage); |
|||
StreamUtils.readByteArray(in, minVersion); |
|||
StreamUtils.readByteArray(in, firstImageIndex); |
|||
StreamUtils.readByteArray(in, huffmanRecordOffset); |
|||
StreamUtils.readByteArray(in, huffmanRecordCount); |
|||
StreamUtils.readByteArray(in, huffmanTableOffset); |
|||
StreamUtils.readByteArray(in, huffmanTableLength); |
|||
StreamUtils.readByteArray(in, exthFlags); |
|||
if (MobiCommon.debug) |
|||
{ |
|||
MobiCommon.logMessage("exthFlags: " |
|||
+ StreamUtils.byteArrayToInt(exthFlags)); |
|||
} |
|||
boolean exthExists = ((StreamUtils.byteArrayToInt(exthFlags) & 0x40) |
|||
!= 0); |
|||
MobiCommon.logMessage("exthExists: " + exthExists); |
|||
StreamUtils.readByteArray(in, restOfMobiHeader); |
|||
|
|||
if (exthExists) |
|||
{ |
|||
exthHeader = new EXTHHeader(in); |
|||
} |
|||
|
|||
int currentOffset = 132 + restOfMobiHeader.length + exthHeaderSize(); |
|||
|
|||
remainder = new byte[(int)(mobiHeaderSize - currentOffset)]; |
|||
StreamUtils.readByteArray(in, remainder); |
|||
|
|||
int fullNameIndexInRemainder |
|||
= StreamUtils.byteArrayToInt(fullNameOffset) - currentOffset; |
|||
fullName = new byte[fullNameLen]; |
|||
MobiCommon.logMessage("fullNameIndexInRemainder: " |
|||
+ fullNameIndexInRemainder); |
|||
MobiCommon.logMessage("fullNameLen: " + fullNameLen); |
|||
|
|||
if ((fullNameIndexInRemainder >= 0) |
|||
&& |
|||
(fullNameIndexInRemainder < remainder.length) |
|||
&& |
|||
((fullNameIndexInRemainder + fullNameLen) <= remainder.length) |
|||
&& |
|||
(fullNameLen > 0)) |
|||
{ |
|||
System.arraycopy(remainder, |
|||
fullNameIndexInRemainder, |
|||
fullName, |
|||
0, |
|||
fullNameLen); |
|||
} |
|||
if (MobiCommon.debug) |
|||
{ |
|||
MobiCommon.logMessage("full name: " |
|||
+ StreamUtils.byteArrayToString(fullName)); |
|||
} |
|||
} |
|||
|
|||
public String getCharacterEncoding() |
|||
{ |
|||
return characterEncoding; |
|||
} |
|||
|
|||
public String getFullName() |
|||
{ |
|||
return StreamUtils.byteArrayToString(fullName, characterEncoding); |
|||
} |
|||
|
|||
public void setFullName(String s) |
|||
{ |
|||
byte[] fullBytes = StreamUtils.stringToByteArray(s, characterEncoding); |
|||
int len = fullBytes.length; |
|||
StreamUtils.intToByteArray(len, fullNameLength); |
|||
|
|||
// the string must be terminated by 2 null bytes
|
|||
// then this must end in a 4-byte boundary
|
|||
//
|
|||
int padding = (len + 2) % 4; |
|||
if (padding != 0) padding = 4 - padding; |
|||
padding += 2; |
|||
|
|||
byte[] buffer = new byte[len + padding]; |
|||
System.arraycopy(fullBytes, 0, buffer, 0, len); |
|||
for (int i=len; i<buffer.length; i++) buffer[i] = 0; |
|||
|
|||
fullName = buffer; |
|||
} |
|||
|
|||
public int getLocale() |
|||
{ |
|||
return StreamUtils.byteArrayToInt(locale); |
|||
} |
|||
|
|||
public void setLocale(int localeInt) |
|||
{ |
|||
StreamUtils.intToByteArray(localeInt, locale); |
|||
} |
|||
|
|||
public int getInputLanguage() |
|||
{ |
|||
return StreamUtils.byteArrayToInt(inputLanguage); |
|||
} |
|||
|
|||
public void setInputLanguage(int input) |
|||
{ |
|||
StreamUtils.intToByteArray(input, inputLanguage); |
|||
} |
|||
|
|||
public int getOutputLanguage() |
|||
{ |
|||
return StreamUtils.byteArrayToInt(outputLanguage); |
|||
} |
|||
|
|||
public void setOutputLanguage(int output) |
|||
{ |
|||
StreamUtils.intToByteArray(output, outputLanguage); |
|||
} |
|||
|
|||
public List<EXTHRecord> getEXTHRecords() |
|||
{ |
|||
return (exthHeader == null) ? (new LinkedList<EXTHRecord>()) |
|||
: exthHeader.getRecordList(); |
|||
} |
|||
|
|||
public void setEXTHRecords(List<EXTHRecord> list) |
|||
{ |
|||
int flag = StreamUtils.byteArrayToInt(exthFlags) & 0xffffbf; |
|||
if ((list == null) || (list.size() == 0)) |
|||
{ |
|||
exthHeader = null; |
|||
StreamUtils.intToByteArray(flag, exthFlags); |
|||
} |
|||
else |
|||
{ |
|||
if (exthHeader == null) |
|||
exthHeader = new EXTHHeader(list); |
|||
else |
|||
exthHeader.setRecordList(list); |
|||
|
|||
StreamUtils.intToByteArray(flag | 0x40, exthFlags); |
|||
} |
|||
} |
|||
|
|||
public void pack() |
|||
{ |
|||
if (!MobiCommon.safeMode) |
|||
{ |
|||
// dump existing remainder, set to fullName
|
|||
remainder = new byte[fullName.length]; |
|||
System.arraycopy(fullName, 0, remainder, 0, remainder.length); |
|||
|
|||
// adjust fullNameOffset
|
|||
StreamUtils.intToByteArray(132 + restOfMobiHeader.length |
|||
+ exthHeaderSize(), fullNameOffset); |
|||
} |
|||
} |
|||
|
|||
public int size() |
|||
{ |
|||
return 132 + restOfMobiHeader.length + exthHeaderSize() + remainder.length; |
|||
} |
|||
|
|||
public String getCompression() |
|||
{ |
|||
int comp = StreamUtils.byteArrayToInt(compression); |
|||
switch (comp) |
|||
{ |
|||
case 1: |
|||
return "None"; |
|||
case 2: |
|||
return "PalmDOC"; |
|||
case 17480: |
|||
return "HUFF/CDIC"; |
|||
default: |
|||
return "Unknown (" + comp + ")"; |
|||
} |
|||
} |
|||
|
|||
public long getTextLength() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(textLength); |
|||
} |
|||
|
|||
public int getRecordCount() |
|||
{ |
|||
return StreamUtils.byteArrayToInt(recordCount); |
|||
} |
|||
|
|||
public int getRecordSize() |
|||
{ |
|||
return StreamUtils.byteArrayToInt(recordSize); |
|||
} |
|||
|
|||
public String getEncryptionType() |
|||
{ |
|||
int enc = StreamUtils.byteArrayToInt(encryptionType); |
|||
switch (enc) |
|||
{ |
|||
case 0: return "None"; |
|||
case 1: return "Old Mobipocket"; |
|||
case 2: return "Mobipocket"; |
|||
default: return "Unknown (" + enc + ")"; |
|||
} |
|||
} |
|||
|
|||
public long getHeaderLength() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(headerLength); |
|||
} |
|||
|
|||
public String getMobiType() |
|||
{ |
|||
long type = StreamUtils.byteArrayToLong(mobiType); |
|||
if (type == 2) |
|||
return "Mobipocket Book"; |
|||
else if (type == 3) |
|||
return "PalmDoc Book"; |
|||
else if (type == 4) |
|||
return "Audio"; |
|||
else if (type == 257) |
|||
return "News"; |
|||
else if (type == 258) |
|||
return "News Feed"; |
|||
else if (type == 259) |
|||
return "News Magazine"; |
|||
else if (type == 513) |
|||
return "PICS"; |
|||
else if (type == 514) |
|||
return "WORD"; |
|||
else if (type == 515) |
|||
return "XLS"; |
|||
else if (type == 516) |
|||
return "PPT"; |
|||
else if (type == 517) |
|||
return "TEXT"; |
|||
else if (type == 518) |
|||
return "HTML"; |
|||
else |
|||
return "Unknown (" + type + ")"; |
|||
} |
|||
|
|||
public long getUniqueID() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(uniqueID); |
|||
} |
|||
|
|||
public long getFileVersion() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(fileVersion); |
|||
} |
|||
|
|||
public long getOrthographicIndex() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(orthographicIndex); |
|||
} |
|||
|
|||
public long getInflectionIndex() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(inflectionIndex); |
|||
} |
|||
|
|||
public long getIndexNames() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(indexNames); |
|||
} |
|||
|
|||
public long getIndexKeys() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(indexKeys); |
|||
} |
|||
|
|||
public long getExtraIndex0() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(extraIndex0); |
|||
} |
|||
|
|||
public long getExtraIndex1() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(extraIndex1); |
|||
} |
|||
|
|||
public long getExtraIndex2() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(extraIndex2); |
|||
} |
|||
|
|||
public long getExtraIndex3() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(extraIndex3); |
|||
} |
|||
|
|||
public long getExtraIndex4() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(extraIndex4); |
|||
} |
|||
|
|||
public long getExtraIndex5() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(extraIndex5); |
|||
} |
|||
|
|||
public long getFirstNonBookIndex() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(firstNonBookIndex); |
|||
} |
|||
|
|||
public long getFullNameOffset() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(fullNameOffset); |
|||
} |
|||
|
|||
public long getFullNameLength() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(fullNameLength); |
|||
} |
|||
|
|||
public long getMinVersion() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(minVersion); |
|||
} |
|||
|
|||
public long getHuffmanRecordOffset() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(huffmanRecordOffset); |
|||
} |
|||
|
|||
public long getHuffmanRecordCount() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(huffmanRecordCount); |
|||
} |
|||
|
|||
public long getHuffmanTableOffset() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(huffmanTableOffset); |
|||
} |
|||
|
|||
public long getHuffmanTableLength() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(huffmanTableLength); |
|||
} |
|||
|
|||
private int exthHeaderSize() |
|||
{ |
|||
return (exthHeader == null)?0:exthHeader.size(); |
|||
} |
|||
|
|||
public void write(OutputStream out) throws IOException |
|||
{ |
|||
out.write(compression); |
|||
out.write(unused0); |
|||
out.write(textLength); |
|||
out.write(recordCount); |
|||
out.write(recordSize); |
|||
out.write(encryptionType); |
|||
out.write(unused1); |
|||
out.write(identifier); |
|||
out.write(headerLength); |
|||
out.write(mobiType); |
|||
out.write(textEncoding); |
|||
out.write(uniqueID); |
|||
out.write(fileVersion); |
|||
out.write(orthographicIndex); |
|||
out.write(inflectionIndex); |
|||
out.write(indexNames); |
|||
out.write(indexKeys); |
|||
out.write(extraIndex0); |
|||
out.write(extraIndex1); |
|||
out.write(extraIndex2); |
|||
out.write(extraIndex3); |
|||
out.write(extraIndex4); |
|||
out.write(extraIndex5); |
|||
out.write(firstNonBookIndex); |
|||
out.write(fullNameOffset); |
|||
out.write(fullNameLength); |
|||
out.write(locale); |
|||
out.write(inputLanguage); |
|||
out.write(outputLanguage); |
|||
out.write(minVersion); |
|||
out.write(firstImageIndex); |
|||
out.write(huffmanRecordOffset); |
|||
out.write(huffmanRecordCount); |
|||
out.write(huffmanTableOffset); |
|||
out.write(huffmanTableLength); |
|||
out.write(exthFlags); |
|||
out.write(restOfMobiHeader); |
|||
if (exthHeader != null) exthHeader.write(out); |
|||
out.write(remainder); |
|||
} |
|||
} |
@ -0,0 +1,292 @@ |
|||
package mobimeta; |
|||
|
|||
import java.io.*; |
|||
import java.util.*; |
|||
|
|||
public class MobiMeta |
|||
{ |
|||
public final static int BUFFER_SIZE = 4096; |
|||
|
|||
protected PDBHeader pdbHeader; |
|||
protected MobiHeader mobiHeader; |
|||
protected String characterEncoding; |
|||
protected List<EXTHRecord> exthRecords; |
|||
private File inputFile; |
|||
|
|||
|
|||
public MobiMeta(File f) throws MobiMetaException |
|||
{ |
|||
inputFile = f; |
|||
FileInputStream in = null; |
|||
try |
|||
{ |
|||
in = new FileInputStream(f); |
|||
pdbHeader = new PDBHeader(in); |
|||
mobiHeader = new MobiHeader(in, pdbHeader.getMobiHeaderSize()); |
|||
exthRecords = mobiHeader.getEXTHRecords(); |
|||
characterEncoding = mobiHeader.getCharacterEncoding(); |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
throw new MobiMetaException("Could not parse mobi file " |
|||
+ f.getAbsolutePath() |
|||
+ ": " |
|||
+ e.getMessage()); |
|||
} |
|||
finally |
|||
{ |
|||
if (in != null) try { in.close(); } catch (IOException e) {} |
|||
} |
|||
} |
|||
|
|||
public void saveToNewFile(File outputFile) throws MobiMetaException |
|||
{ |
|||
saveToNewFile(outputFile, true); |
|||
} |
|||
|
|||
public void saveToNewFile(File outputFile, boolean packHeader) throws MobiMetaException |
|||
{ |
|||
long readOffset = pdbHeader.getOffsetAfterMobiHeader(); |
|||
|
|||
if (!MobiCommon.safeMode && packHeader) |
|||
{ |
|||
mobiHeader.pack(); |
|||
pdbHeader.adjustOffsetsAfterMobiHeader(mobiHeader.size()); |
|||
} |
|||
|
|||
FileInputStream in = null; |
|||
FileOutputStream out = null; |
|||
try |
|||
{ |
|||
out = new FileOutputStream(outputFile); |
|||
pdbHeader.write(out); |
|||
mobiHeader.write(out); |
|||
|
|||
int bytesRead; |
|||
byte[] buffer = new byte[BUFFER_SIZE]; |
|||
in = new FileInputStream(inputFile); |
|||
in.skip(readOffset); |
|||
while ((bytesRead = in.read(buffer)) != -1) |
|||
{ |
|||
out.write(buffer, 0, bytesRead); |
|||
} |
|||
} |
|||
catch (IOException e) |
|||
{ |
|||
throw new MobiMetaException( |
|||
"Problems encountered while writing to " |
|||
+ outputFile.getAbsolutePath() + ": " |
|||
+ e.getMessage()); |
|||
} |
|||
finally |
|||
{ |
|||
if (in != null) try { in.close(); } catch (IOException e) {} |
|||
if (out != null) try { out.close(); } catch (IOException e) {} |
|||
} |
|||
} |
|||
|
|||
public String getCharacterEncoding() |
|||
{ |
|||
return mobiHeader.getCharacterEncoding(); |
|||
} |
|||
|
|||
public String getFullName() |
|||
{ |
|||
return mobiHeader.getFullName(); |
|||
} |
|||
|
|||
public void setFullName(String s) |
|||
{ |
|||
mobiHeader.setFullName(s); |
|||
} |
|||
|
|||
public List<EXTHRecord> getEXTHRecords() |
|||
{ |
|||
return exthRecords; |
|||
} |
|||
|
|||
public void setEXTHRecords() |
|||
{ |
|||
mobiHeader.setEXTHRecords(exthRecords); |
|||
} |
|||
|
|||
public int getLocale() |
|||
{ |
|||
return mobiHeader.getLocale(); |
|||
} |
|||
|
|||
public int getDictInput() |
|||
{ |
|||
return mobiHeader.getInputLanguage(); |
|||
} |
|||
|
|||
public int getDictOutput() |
|||
{ |
|||
return mobiHeader.getOutputLanguage(); |
|||
} |
|||
|
|||
public void setLanguages(int locale, int dictInput, int dictOutput) |
|||
{ |
|||
mobiHeader.setLocale(locale); |
|||
mobiHeader.setInputLanguage(dictInput); |
|||
mobiHeader.setOutputLanguage(dictOutput); |
|||
} |
|||
|
|||
public String getMetaInfo() |
|||
{ |
|||
StringBuffer sb = new StringBuffer(); |
|||
sb.append("PDB Header\r\n"); |
|||
sb.append("----------\r\n"); |
|||
sb.append("Name: "); |
|||
sb.append(pdbHeader.getName()); |
|||
sb.append("\r\n"); |
|||
String[] attributes = getPDBHeaderAttributes(); |
|||
if (attributes.length > 0) |
|||
{ |
|||
sb.append("Attributes: "); |
|||
for (int i=0; i<attributes.length; i++) |
|||
{ |
|||
if (i > 0) sb.append(", "); |
|||
sb.append(attributes[i]); |
|||
} |
|||
sb.append("\r\n"); |
|||
} |
|||
sb.append("Version: "); |
|||
sb.append(pdbHeader.getVersion()); |
|||
sb.append("\r\n"); |
|||
sb.append("Creation Date: "); |
|||
sb.append(pdbHeader.getCreationDate()); |
|||
sb.append("\r\n"); |
|||
sb.append("Modification Date: "); |
|||
sb.append(pdbHeader.getModificationDate()); |
|||
sb.append("\r\n"); |
|||
sb.append("Last Backup Date: "); |
|||
sb.append(pdbHeader.getLastBackupDate()); |
|||
sb.append("\r\n"); |
|||
sb.append("Modification Number: "); |
|||
sb.append(pdbHeader.getModificationNumber()); |
|||
sb.append("\r\n"); |
|||
sb.append("App Info ID: "); |
|||
sb.append(pdbHeader.getAppInfoID()); |
|||
sb.append("\r\n"); |
|||
sb.append("Sort Info ID: "); |
|||
sb.append(pdbHeader.getSortInfoID()); |
|||
sb.append("\r\n"); |
|||
sb.append("Type: "); |
|||
sb.append(pdbHeader.getType()); |
|||
sb.append("\r\n"); |
|||
sb.append("Creator: "); |
|||
sb.append(pdbHeader.getCreator()); |
|||
sb.append("\r\n"); |
|||
sb.append("Unique ID Seed: "); |
|||
sb.append(pdbHeader.getUniqueIDSeed()); |
|||
sb.append("\r\n\r\n"); |
|||
|
|||
sb.append("PalmDOC Header\r\n"); |
|||
sb.append("--------------\r\n"); |
|||
sb.append("Compression: "); |
|||
sb.append(mobiHeader.getCompression()); |
|||
sb.append("\r\n"); |
|||
sb.append("Text Length: "); |
|||
sb.append(mobiHeader.getTextLength()); |
|||
sb.append("\r\n"); |
|||
sb.append("Record Count: "); |
|||
sb.append(mobiHeader.getRecordCount()); |
|||
sb.append("\r\n"); |
|||
sb.append("Record Size: "); |
|||
sb.append(mobiHeader.getRecordSize()); |
|||
sb.append("\r\n"); |
|||
sb.append("Encryption Type: "); |
|||
sb.append(mobiHeader.getEncryptionType()); |
|||
sb.append("\r\n\r\n"); |
|||
|
|||
sb.append("MOBI Header\r\n"); |
|||
sb.append("-----------\r\n"); |
|||
sb.append("Header Length: "); |
|||
sb.append(mobiHeader.getHeaderLength()); |
|||
sb.append("\r\n"); |
|||
sb.append("Mobi Type: "); |
|||
sb.append(mobiHeader.getMobiType()); |
|||
sb.append("\r\n"); |
|||
sb.append("Unique ID: "); |
|||
sb.append(mobiHeader.getUniqueID()); |
|||
sb.append("\r\n"); |
|||
sb.append("File Version: "); |
|||
sb.append(mobiHeader.getFileVersion()); |
|||
sb.append("\r\n"); |
|||
sb.append("Orthographic Index: "); |
|||
sb.append(mobiHeader.getOrthographicIndex()); |
|||
sb.append("\r\n"); |
|||
sb.append("Inflection Index: "); |
|||
sb.append(mobiHeader.getInflectionIndex()); |
|||
sb.append("\r\n"); |
|||
sb.append("Index Names: "); |
|||
sb.append(mobiHeader.getIndexNames()); |
|||
sb.append("\r\n"); |
|||
sb.append("Index Keys: "); |
|||
sb.append(mobiHeader.getIndexKeys()); |
|||
sb.append("\r\n"); |
|||
sb.append("Extra Index 0: "); |
|||
sb.append(mobiHeader.getExtraIndex0()); |
|||
sb.append("\r\n"); |
|||
sb.append("Extra Index 1: "); |
|||
sb.append(mobiHeader.getExtraIndex1()); |
|||
sb.append("\r\n"); |
|||
sb.append("Extra Index 2: "); |
|||
sb.append(mobiHeader.getExtraIndex2()); |
|||
sb.append("\r\n"); |
|||
sb.append("Extra Index 3: "); |
|||
sb.append(mobiHeader.getExtraIndex3()); |
|||
sb.append("\r\n"); |
|||
sb.append("Extra Index 4: "); |
|||
sb.append(mobiHeader.getExtraIndex4()); |
|||
sb.append("\r\n"); |
|||
sb.append("Extra Index 5: "); |
|||
sb.append(mobiHeader.getExtraIndex5()); |
|||
sb.append("\r\n"); |
|||
sb.append("First Non-Book Index: "); |
|||
sb.append(mobiHeader.getFirstNonBookIndex()); |
|||
sb.append("\r\n"); |
|||
sb.append("Full Name Offset: "); |
|||
sb.append(mobiHeader.getFullNameOffset()); |
|||
sb.append("\r\n"); |
|||
sb.append("Full Name Length: "); |
|||
sb.append(mobiHeader.getFullNameLength()); |
|||
sb.append("\r\n"); |
|||
sb.append("Min Version: "); |
|||
sb.append(mobiHeader.getMinVersion()); |
|||
sb.append("\r\n"); |
|||
sb.append("Huffman Record Offset: "); |
|||
sb.append(mobiHeader.getHuffmanRecordOffset()); |
|||
sb.append("\r\n"); |
|||
sb.append("Huffman Record Count: "); |
|||
sb.append(mobiHeader.getHuffmanRecordCount()); |
|||
sb.append("\r\n"); |
|||
sb.append("Huffman Table Offset: "); |
|||
sb.append(mobiHeader.getHuffmanTableOffset()); |
|||
sb.append("\r\n"); |
|||
sb.append("Huffman Table Length: "); |
|||
sb.append(mobiHeader.getHuffmanTableLength()); |
|||
sb.append("\r\n"); |
|||
|
|||
return sb.toString(); |
|||
} |
|||
|
|||
private String[] getPDBHeaderAttributes() |
|||
{ |
|||
LinkedList<String> list = new LinkedList<String>(); |
|||
int attr = pdbHeader.getAttributes(); |
|||
if ((attr & 0x02) != 0) list.add("Read-Only"); |
|||
if ((attr & 0x04) != 0) list.add("Dirty AppInfoArea"); |
|||
if ((attr & 0x08) != 0) list.add("Backup This Database"); |
|||
if ((attr & 0x10) != 0) list.add("OK To Install Newer Over Existing Copy"); |
|||
if ((attr & 0x20) != 0) list.add("Force The PalmPilot To Reset After This Database Is Installed"); |
|||
if ((attr & 0x40) != 0) list.add("Don't Allow Copy Of File To Be Beamed To Other Pilot"); |
|||
|
|||
String[] ret = new String[list.size()]; |
|||
int index = 0; |
|||
for (String s : list) ret[index++] = s; |
|||
|
|||
return ret; |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
package mobimeta; |
|||
|
|||
public class MobiMetaException extends Exception |
|||
{ |
|||
/** |
|||
* |
|||
*/ |
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
public MobiMetaException(String message) |
|||
{ |
|||
super(message); |
|||
} |
|||
} |
@ -0,0 +1,164 @@ |
|||
package mobimeta; |
|||
|
|||
import java.io.*; |
|||
import java.util.*; |
|||
|
|||
public class PDBHeader |
|||
{ |
|||
private byte[] name = { 0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0 }; |
|||
private byte[] attributes = { 0, 0 }; |
|||
private byte[] version = { 0, 0 }; |
|||
private byte[] creationDate = { 0, 0, 0, 0 }; |
|||
private byte[] modificationDate = { 0, 0, 0, 0 }; |
|||
private byte[] lastBackupDate = { 0, 0, 0, 0 }; |
|||
private byte[] modificationNumber = { 0, 0, 0, 0 }; |
|||
private byte[] appInfoID = { 0, 0, 0, 0 }; |
|||
private byte[] sortInfoID = { 0, 0, 0, 0 }; |
|||
private byte[] type = { 0, 0, 0, 0 }; |
|||
private byte[] creator = { 0, 0, 0, 0 }; |
|||
private byte[] uniqueIDSeed = { 0, 0, 0, 0 }; |
|||
private byte[] nextRecordListID = { 0, 0, 0, 0 }; |
|||
private byte[] numRecords = { 0, 0 }; |
|||
private List<RecordInfo> recordInfoList; |
|||
private byte[] gapToData = { 0, 0 }; |
|||
|
|||
public PDBHeader(InputStream in) |
|||
throws IOException |
|||
{ |
|||
MobiCommon.logMessage("*** PDBHeader ***"); |
|||
StreamUtils.readByteArray(in, name); |
|||
StreamUtils.readByteArray(in, attributes); |
|||
StreamUtils.readByteArray(in, version); |
|||
StreamUtils.readByteArray(in, creationDate); |
|||
StreamUtils.readByteArray(in, modificationDate); |
|||
StreamUtils.readByteArray(in, lastBackupDate); |
|||
StreamUtils.readByteArray(in, modificationNumber); |
|||
StreamUtils.readByteArray(in, appInfoID); |
|||
StreamUtils.readByteArray(in, sortInfoID); |
|||
StreamUtils.readByteArray(in, type); |
|||
StreamUtils.readByteArray(in, creator); |
|||
StreamUtils.readByteArray(in, uniqueIDSeed); |
|||
StreamUtils.readByteArray(in, nextRecordListID); |
|||
StreamUtils.readByteArray(in, numRecords); |
|||
|
|||
int recordCount = StreamUtils.byteArrayToInt(numRecords); |
|||
MobiCommon.logMessage("numRecords: " + recordCount); |
|||
recordInfoList = new LinkedList<RecordInfo>(); |
|||
for (int i=0; i<recordCount; i++) |
|||
{ |
|||
recordInfoList.add(new RecordInfo(in)); |
|||
} |
|||
|
|||
StreamUtils.readByteArray(in, gapToData); |
|||
} |
|||
|
|||
public long getMobiHeaderSize() |
|||
{ |
|||
return (recordInfoList.size() > 1) ? (recordInfoList.get(1) |
|||
.getRecordDataOffset() - recordInfoList.get(0) |
|||
.getRecordDataOffset()) : 0; |
|||
} |
|||
|
|||
public long getOffsetAfterMobiHeader() |
|||
{ |
|||
return (recordInfoList.size() > 1) ? recordInfoList.get(1) |
|||
.getRecordDataOffset() : 0; |
|||
} |
|||
|
|||
public void adjustOffsetsAfterMobiHeader(int newMobiHeaderSize) |
|||
{ |
|||
if (recordInfoList.size() < 2) return; |
|||
|
|||
int delta = (int)(newMobiHeaderSize - getMobiHeaderSize()); |
|||
int len = recordInfoList.size(); |
|||
for (int i=1; i<len; i++) |
|||
{ |
|||
RecordInfo rec = recordInfoList.get(i); |
|||
long oldOffset = rec.getRecordDataOffset(); |
|||
rec.setRecordDataOffset(oldOffset + delta); |
|||
} |
|||
} |
|||
|
|||
public void write(OutputStream out) throws IOException |
|||
{ |
|||
out.write(name); |
|||
out.write(attributes); |
|||
out.write(version); |
|||
out.write(creationDate); |
|||
out.write(modificationDate); |
|||
out.write(lastBackupDate); |
|||
out.write(modificationNumber); |
|||
out.write(appInfoID); |
|||
out.write(sortInfoID); |
|||
out.write(type); |
|||
out.write(creator); |
|||
out.write(uniqueIDSeed); |
|||
out.write(nextRecordListID); |
|||
out.write(numRecords); |
|||
for (RecordInfo rec : recordInfoList) rec.write(out); |
|||
out.write(gapToData); |
|||
} |
|||
|
|||
public String getName() |
|||
{ |
|||
return StreamUtils.byteArrayToString(name); |
|||
} |
|||
|
|||
public int getAttributes() |
|||
{ |
|||
return StreamUtils.byteArrayToInt(attributes); |
|||
} |
|||
|
|||
public int getVersion() |
|||
{ |
|||
return StreamUtils.byteArrayToInt(version); |
|||
} |
|||
|
|||
public long getCreationDate() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(creationDate); |
|||
} |
|||
|
|||
public long getModificationDate() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(modificationDate); |
|||
} |
|||
|
|||
public long getLastBackupDate() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(lastBackupDate); |
|||
} |
|||
|
|||
public long getModificationNumber() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(modificationNumber); |
|||
} |
|||
|
|||
public long getAppInfoID() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(appInfoID); |
|||
} |
|||
|
|||
public long getSortInfoID() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(sortInfoID); |
|||
} |
|||
|
|||
public long getType() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(type); |
|||
} |
|||
|
|||
public long getCreator() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(creator); |
|||
} |
|||
|
|||
public long getUniqueIDSeed() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(uniqueIDSeed); |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
package mobimeta; |
|||
|
|||
import java.io.*; |
|||
|
|||
public class RecordInfo |
|||
{ |
|||
private byte[] recordDataOffset = { 0, 0, 0, 0 }; |
|||
private byte recordAttributes = 0; |
|||
private byte[] uniqueID = { 0, 0, 0 }; |
|||
|
|||
public RecordInfo(InputStream in) |
|||
throws IOException |
|||
{ |
|||
StreamUtils.readByteArray(in, recordDataOffset); |
|||
recordAttributes = StreamUtils.readByte(in); |
|||
StreamUtils.readByteArray(in, uniqueID); |
|||
|
|||
if (MobiCommon.debug) |
|||
{ |
|||
MobiCommon.logMessage("RecordInfo uniqueID: " |
|||
+ StreamUtils.byteArrayToInt(uniqueID)); |
|||
} |
|||
} |
|||
|
|||
public long getRecordDataOffset() |
|||
{ |
|||
return StreamUtils.byteArrayToLong(recordDataOffset); |
|||
} |
|||
|
|||
public void setRecordDataOffset(long newOffset) |
|||
{ |
|||
StreamUtils.longToByteArray(newOffset, recordDataOffset); |
|||
} |
|||
|
|||
public void write(OutputStream out) throws IOException |
|||
{ |
|||
out.write(recordDataOffset); |
|||
out.write(recordAttributes); |
|||
out.write(uniqueID); |
|||
} |
|||
} |
@ -0,0 +1,183 @@ |
|||
package mobimeta; |
|||
|
|||
import java.io.*; |
|||
|
|||
public class StreamUtils |
|||
{ |
|||
public static String readCString(InputStream in, int len) |
|||
throws IOException |
|||
{ |
|||
byte[] buffer = new byte[len]; |
|||
int bytesLeft = len; |
|||
int offset = 0; |
|||
|
|||
while (bytesLeft > 0) |
|||
{ |
|||
int bytesRead = in.read(buffer, offset, bytesLeft); |
|||
if (bytesRead == -1) |
|||
throw new IOException("Supposed to read a " |
|||
+ len |
|||
+ " byte C string, but could not"); |
|||
offset += bytesRead; |
|||
bytesLeft -= bytesRead; |
|||
} |
|||
|
|||
String s = byteArrayToString(buffer); |
|||
MobiCommon.logMessage("readCString: " + s); |
|||
return s; |
|||
} |
|||
|
|||
public static byte readByte(InputStream in) |
|||
throws IOException |
|||
{ |
|||
int b = in.read(); |
|||
if (b == -1) |
|||
throw new IOException("Supposed to read a byte, but could not"); |
|||
MobiCommon.logMessage("readByte: " + b); |
|||
return (byte)(b & 0xff); |
|||
} |
|||
|
|||
public static void readByteArray(InputStream in, byte[] buffer) |
|||
throws IOException |
|||
{ |
|||
int len = buffer.length; |
|||
int bytesLeft = len; |
|||
int offset = 0; |
|||
|
|||
while (bytesLeft > 0) |
|||
{ |
|||
int bytesRead = in.read(buffer, offset, bytesLeft); |
|||
if (bytesRead == -1) |
|||
throw new IOException("Supposed to read a " |
|||
+ len |
|||
+ " byte array, but could not"); |
|||
offset += bytesRead; |
|||
bytesLeft -= bytesRead; |
|||
} |
|||
|
|||
if (MobiCommon.debug) |
|||
{ |
|||
MobiCommon.logMessage(dumpByteArray(buffer)); |
|||
} |
|||
} |
|||
|
|||
public static String byteArrayToString(byte[] buffer) |
|||
{ |
|||
return byteArrayToString(buffer, null); |
|||
} |
|||
|
|||
public static String byteArrayToString(byte[] buffer, String encoding) |
|||
{ |
|||
int len = buffer.length; |
|||
int zeroIndex = -1; |
|||
for (int i=0; i<len; i++) |
|||
{ |
|||
byte b = buffer[i]; |
|||
if (b == 0) |
|||
{ |
|||
zeroIndex = i; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (encoding != null) |
|||
{ |
|||
try |
|||
{ |
|||
if (zeroIndex == -1) |
|||
return new String(buffer, encoding); |
|||
else |
|||
return new String(buffer, 0, zeroIndex, encoding); |
|||
} |
|||
catch (java.io.UnsupportedEncodingException e) |
|||
{ |
|||
// let it fall through and use the default encoding
|
|||
} |
|||
} |
|||
|
|||
if (zeroIndex == -1) |
|||
return new String(buffer); |
|||
else |
|||
return new String(buffer, 0, zeroIndex); |
|||
} |
|||
|
|||
public static int byteArrayToInt(byte[] buffer) |
|||
{ |
|||
int total = 0; |
|||
int len = buffer.length; |
|||
for (int i=0; i<len; i++) |
|||
{ |
|||
total = (total << 8) + (buffer[i] & 0xff); |
|||
} |
|||
|
|||
return total; |
|||
} |
|||
|
|||
public static long byteArrayToLong(byte[] buffer) |
|||
{ |
|||
long total = 0; |
|||
int len = buffer.length; |
|||
for (int i=0; i<len; i++) |
|||
{ |
|||
total = (total << 8) + (buffer[i] & 0xff); |
|||
} |
|||
|
|||
return total; |
|||
} |
|||
|
|||
public static void intToByteArray(int value, byte[] dest) |
|||
{ |
|||
int lastIndex = dest.length - 1; |
|||
for (int i=lastIndex; i >=0; i--) |
|||
{ |
|||
dest[i] = (byte)(value & 0xff); |
|||
value = value >> 8; |
|||
} |
|||
} |
|||
|
|||
public static void longToByteArray(long value, byte[] dest) |
|||
{ |
|||
int lastIndex = dest.length - 1; |
|||
for (int i=lastIndex; i >=0; i--) |
|||
{ |
|||
dest[i] = (byte)(value & 0xff); |
|||
value = value >> 8; |
|||
} |
|||
} |
|||
|
|||
public static byte[] stringToByteArray(String s) |
|||
{ |
|||
return stringToByteArray(s, null); |
|||
} |
|||
|
|||
public static byte[] stringToByteArray(String s, String encoding) |
|||
{ |
|||
if (encoding != null) |
|||
{ |
|||
try |
|||
{ |
|||
return s.getBytes(encoding); |
|||
} |
|||
catch (UnsupportedEncodingException e) |
|||
{ |
|||
// let if fall through to use the default character encoding
|
|||
} |
|||
} |
|||
|
|||
return s.getBytes(); |
|||
} |
|||
|
|||
public static String dumpByteArray(byte[] buffer) |
|||
{ |
|||
StringBuffer sb = new StringBuffer(); |
|||
sb.append("{ "); |
|||
int len = buffer.length; |
|||
for (int i=0; i<len; i++) |
|||
{ |
|||
if (i > 0) sb.append(", "); |
|||
sb.append(buffer[i] & 0xff); |
|||
} |
|||
sb.append(" }"); |
|||
return sb.toString(); |
|||
} |
|||
} |
Loading…
Reference in new issue