/*
* Copyright (C) 2007 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: Nov 15, 2007
*/
package uk.me.parabola.mkgmap.combiners;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import uk.me.parabola.imgfmt.FileSystemParam;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.BufferedImgFileReader;
import uk.me.parabola.imgfmt.app.lbl.LBLFileReader;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.trergn.TREFileReader;
import uk.me.parabola.imgfmt.app.trergn.TREHeader;
import uk.me.parabola.imgfmt.fs.DirectoryEntry;
import uk.me.parabola.imgfmt.fs.FileSystem;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.imgfmt.sys.FileImgChannel;
import uk.me.parabola.imgfmt.sys.ImgFS;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.CommandArgs;
import uk.me.parabola.mkgmap.srt.SrtTextReader;
import static uk.me.parabola.mkgmap.combiners.FileKind.*;
/**
* Used for holding information about an individual file that will be made into
* a gmapsupp file.
*
* @author Steve Ratcliffe
*/
public class FileInfo {
private static final Logger log = Logger.getLogger(FileInfo.class);
private static final int ENTRY_SIZE = 240;
private static final List<String> KNOWN_FILE_TYPE_EXT = Arrays.asList(
"TRE", "RGN", "LBL", "NET", "NOD",
"TYP"
);
// The name of the file.
private final String filename;
// The kind of file, see *KIND definitions above.
private FileKind kind;
private String mapname;
private int hexname;
private String innername;
private String description;
// If this is an img file, the size of various sections.
private int rgnsize;
private int tresize;
private int lblsize;
private int netsize;
private int nodsize;
private final List<Integer> fileSizes = new ArrayList<Integer>();
private String[] licenceInfo;
private CommandArgs args;
private String mpsName;
private int codePage;
private int sortOrderId;
private FileInfo(String filename, FileKind kind) {
this.filename = filename;
this.kind = kind;
}
// The area covered by the map, if it is a IMG file
private Area bounds;
public String getMapname() {
return mapname;
}
protected void setMapname(String mapname) {
this.mapname = mapname;
}
public String getDescription() {
return description;
}
protected void setDescription(String description) {
this.description = description;
}
public int getRgnsize() {
return rgnsize;
}
protected void setRgnsize(int rgnsize) {
this.rgnsize = rgnsize;
}
public int getTresize() {
return tresize;
}
protected void setTresize(int tresize) {
this.tresize = tresize;
}
public int getLblsize() {
return lblsize;
}
protected void setLblsize(int lblsize) {
this.lblsize = lblsize;
}
public Area getBounds() {
return bounds;
}
/**
* Create a file info the the given file.
*
* @param inputName The filename to examine.
* @return The FileInfo structure giving information about the file.
* @throws FileNotFoundException If the file doesn't actually exist.
*/
public static FileInfo getFileInfo(String inputName) throws FileNotFoundException {
int end = inputName.length();
String ext = inputName.substring(end - 3).toUpperCase(Locale.ENGLISH);
FileInfo info;
if (ext.equals("IMG")) {
info = imgInfo(inputName);
} else if ("TYP".equals(ext)) {
info = fileInfo(inputName, TYP_KIND);
} else if (KNOWN_FILE_TYPE_EXT.contains(ext)) {
info = fileInfo(inputName, APP_KIND);
} else {
info = new FileInfo(inputName, UNKNOWN_KIND);
}
return info;
}
/**
* A TYP file or a component file that goes into a .img (a TRE, LBL etc).
* The component files are not usually given on the command line like this
* but you can do.
*
* @param inputName The input file name.
* @param kind The kind of file being added.
*/
private static FileInfo fileInfo(String inputName, FileKind kind) {
FileInfo info = new FileInfo(inputName, kind);
// Get the size of the file.
File f = new File(inputName);
info.fileSizes.add((int) f.length());
if (inputName.toLowerCase().endsWith(".lbl")) {
lblInfo(inputName, info);
} else if (inputName.toLowerCase().endsWith(".typ")) {
typInfo(inputName, info);
}
return info;
}
/**
* Read information from the TYP file that we might need when combining it with other files.
* @param filename The name of the file.
* @param info The information will be stored here.
*/
private static void typInfo(String filename, FileInfo info) {
ImgChannel chan = new FileImgChannel(filename, "r");
try {
BufferedImgFileReader fr = new BufferedImgFileReader(chan);
fr.position(0x15);
info.setCodePage(fr.getChar());
} finally {
Utils.closeFile(chan);
}
}
/**
* An IMG file, this involves real work. We have to read in the file and
* extract several pieces of information from it.
*
* @param inputName The name of the file.
* @return The information obtained.
* @throws FileNotFoundException If the file doesn't exist.
*/
private static FileInfo imgInfo(String inputName) throws FileNotFoundException {
try (FileSystem imgFs = ImgFS.openFs(inputName)) {
FileSystemParam params = imgFs.fsparam();
log.info("Desc", params.getMapDescription());
log.info("Blocksize", params.getBlockSize());
FileInfo info = new FileInfo(inputName, UNKNOWN_KIND);
info.setDescription(params.getMapDescription());
File f = new File(inputName);
String name = f.getName();
if (OverviewBuilder.isOverviewImg(name))
name = OverviewBuilder.getMapName(name);
int dot = name.lastIndexOf('.');
if (dot < 0) {
name = "0";
} else {
if (dot > name.length())
dot = name.length();
if (dot > 8)
dot = 8;
name = name.substring(0, dot);
}
info.setMapname(name);
boolean hasTre = false;
DirectoryEntry treEnt = null;
List<DirectoryEntry> entries = imgFs.list();
for (DirectoryEntry ent : entries) {
if (ent.isSpecial())
continue;
log.info("file", ent.getFullName());
String ext = ent.getExt();
if ("TRE".equals(ext)) {
info.setTresize(ent.getSize());
info.setInnername(ent.getName());
hasTre = true;
treEnt = ent;
} else if ("RGN".equals(ext)) {
int size = ent.getSize();
info.setRgnsize(size);
} else if ("LBL".equals(ext)) {
info.setLblsize(ent.getSize());
lblInfo(imgFs, ent, info);
} else if ("NET".equals(ext)) {
info.setNetsize(ent.getSize());
} else if ("NOD".equals(ext)) {
info.setNodsize(ent.getSize());
} else if ("MDR".equals(ext)) {
// It is not actually a regular img file, so change the kind.
info.setKind(MDR_KIND);
} else if ("MPS".equals(ext)) {
// This is a gmapsupp file containing several maps.
info.setKind(GMAPSUPP_KIND);
info.mpsName = ent.getFullName();
}
info.fileSizes.add(ent.getSize());
}
if (hasTre)
treInfo(imgFs, treEnt, info);
if (info.getKind() == UNKNOWN_KIND && hasTre)
info.setKind(IMG_KIND);
return info;
}
}
/**
* Obtain the information that we need from the TRE section.
* @param imgFs The filesystem
* @param ent The filename within the filesystem of the TRE file.
* @param info This is where the information will be saved.
* @throws FileNotFoundException If the file is not found in the filesystem.
*/
private static void treInfo(FileSystem imgFs, DirectoryEntry ent, FileInfo info) throws FileNotFoundException {
TREFileReader treFile = null;
try {
ImgChannel treChan = imgFs.open(ent.getFullName(), "r");
treFile = new TREFileReader(treChan);
info.setBounds(treFile.getBounds());
info.setLicenceInfo(treFile.getMapInfo(info.getCodePage()));
info.setHexname(((TREHeader) treFile.getHeader()).getMapId());
} finally {
Utils.closeFile(treFile);
}
}
/**
* Obtain the information we need from a LBL file.
*/
private static void lblInfo(FileSystem imgFs, DirectoryEntry ent, FileInfo info) throws FileNotFoundException {
ImgChannel chan = imgFs.open(ent.getFullName(), "r");
lblInfo(chan, info);
}
private static void lblInfo(String filename, FileInfo info) {
FileImgChannel r = new FileImgChannel(filename, "r");
try {
lblInfo(r, info);
} finally {
Utils.closeFile(r);
}
}
private static void lblInfo(ImgChannel chan, FileInfo info) {
LBLFileReader lblFile = new LBLFileReader(chan);
info.setCodePage(lblFile.getCodePage());
info.setSortOrderId(lblFile.getSortOrderId());
lblFile.close();
}
private void setBounds(Area area) {
this.bounds = area;
}
public String getFilename() {
return filename;
}
public boolean isImg() {
return kind == IMG_KIND;
}
protected void setKind(FileKind kind) {
this.kind = kind;
}
public FileKind getKind() {
return kind;
}
/**
* Get the number header slots (512 byte entry) required to represent this file
* at the given block size.
* Each sub-file will need at least one block and so we go through each
* separately and round up for each and return the total.
*
* @param blockSize The block size.
* @return The number of 512 byte header entries that are needed for all the subfiles
* in this .img file.
*/
public int getNumHeaderEntries(int blockSize) {
int totHeaderSlots = 0;
for (int size : fileSizes) {
// You use up one header slot for every 240 blocks with a minimum
// of one slot
int nblocks = (size + (blockSize-1)) / blockSize;
totHeaderSlots += (nblocks + (ENTRY_SIZE - 1)) / ENTRY_SIZE;
}
return totHeaderSlots;
}
/**
* Get the number of blocks for all the sub-files of this file at the given block size.
* Note that a complete block is always used for a file.
*
* For TYP files and other files that do not have sub-files, then it is just the number of blocks
* for the complete file.
*
* @param bs The block size at which to calculate the value.
* @return The number of blocks at the given size required to save all the sub-files of this file.
*/
public int getNumBlocks(int bs) {
int totBlocks = 0;
for (int size : fileSizes) {
int nblocks = (size + (bs - 1)) / bs;
totBlocks += nblocks;
}
return totBlocks;
}
public int getMapnameAsInt() {
try {
return Integer.valueOf(mapname);
} catch (NumberFormatException e) {
return 0;
}
}
private void setLicenceInfo(String[] info) {
this.licenceInfo = info;
}
public String[] getLicenseInfo() {
return licenceInfo;
}
public int getNetsize() {
return netsize;
}
protected void setNetsize(int netsize) {
this.netsize = netsize;
}
public int getNodsize() {
return nodsize;
}
protected void setNodsize(int nodsize) {
this.nodsize = nodsize;
}
public void setArgs(CommandArgs args) {
this.args = args;
}
public String getFamilyName() {
return args.get("family-name", "OSM map");
}
public String getSeriesName() {
return args.get("series-name", "series name");
}
public int getFamilyId() {
return args.get("family-id", CommandArgs.DEFAULT_FAMILYID);
}
public int getProductId() {
return args.get("product-id", 1);
}
public Sort getSort() {
Sort sort = SrtTextReader.sortForCodepage(codePage);
if (sort == null)
sort = args.getSort();
sort.setSortOrderId(sortOrderId);
return sort;
}
public String getOutputDir() {
return args.getOutputDir();
}
public String getMpsName() {
return mpsName;
}
public String getInnername() {
return innername;
}
public void setInnername(String name) {
this.innername = name;
}
public void setHexname(int hexname) {
this.hexname = hexname;
}
public int getHexname() {
return hexname;
}
public int getCodePage() {
return codePage;
}
public void setCodePage(int codePage) {
this.codePage = codePage;
}
public void setSortOrderId(int sortOrderId) {
this.sortOrderId = sortOrderId;
}
public boolean hasSortOrder() {
return sortOrderId != 0;
}
public String getOverviewName() {
return args.get("overview-mapname", "osmmap");
}
}