| Author | DaveJarvis <email> |
|---|---|
| Date | 2021-04-05 18:18:11 GMT-0700 |
| Commit | aa6f8ca8bac7d77a4bf6adcfc39f0718ce8a6f31 |
| Parent | c4d8101 |
| import com.keenwrite.service.Settings; | ||
| -import javafx.scene.image.Image; | ||
| -import javafx.scene.image.ImageView; | ||
| import java.io.File; | ||
| import java.nio.charset.Charset; | ||
| import java.nio.file.Path; | ||
| -import java.util.ArrayList; | ||
| -import java.util.List; | ||
| import java.util.Locale; | ||
| public static final String STYLESHEET_PREVIEW_LOCALE = | ||
| "file.stylesheet.preview.locale"; | ||
| - | ||
| - public static final List<Image> LOGOS = createImages( | ||
| - "file.logo.16", | ||
| - "file.logo.32", | ||
| - "file.logo.128", | ||
| - "file.logo.256", | ||
| - "file.logo.512" | ||
| - ); | ||
| - | ||
| - public static final Image ICON_DIALOG = LOGOS.get( 1 ); | ||
| - public static final ImageView ICON_DIALOG_NODE = new ImageView( ICON_DIALOG ); | ||
| public static final String FILE_PREFERENCES = getPreferencesFilename(); | ||
| } | ||
| - private static String get( final String key ) { | ||
| + static String get( final String key ) { | ||
| return sSettings.getSetting( key, "" ); | ||
| } | ||
| APP_TITLE_LOWERCASE | ||
| ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Converts the given file names to images, such as application icons. | ||
| - * | ||
| - * @param keys The file names to convert to images. | ||
| - * @return The images loaded from the file name references. | ||
| - */ | ||
| - private static List<Image> createImages( final String... keys ) { | ||
| - final List<Image> images = new ArrayList<>( keys.length ); | ||
| - | ||
| - for( final var key : keys ) { | ||
| - images.add( new Image( get( key ) ) ); | ||
| - } | ||
| - | ||
| - return images; | ||
| } | ||
| } | ||
| +/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | ||
| +package com.keenwrite; | ||
| + | ||
| +import javafx.scene.image.Image; | ||
| +import javafx.scene.image.ImageView; | ||
| + | ||
| +import java.util.ArrayList; | ||
| +import java.util.List; | ||
| + | ||
| +import static com.keenwrite.Constants.get; | ||
| + | ||
| +/** | ||
| + * Defines application-wide default values for GUI-related items. This helps | ||
| + * ensure that unit tests that have no graphical dependencies will pass. | ||
| + */ | ||
| +public class GraphicsConstants { | ||
| + public static final List<Image> LOGOS = createImages( | ||
| + "file.logo.16", | ||
| + "file.logo.32", | ||
| + "file.logo.128", | ||
| + "file.logo.256", | ||
| + "file.logo.512" | ||
| + ); | ||
| + | ||
| + public static final Image ICON_DIALOG = LOGOS.get( 1 ); | ||
| + | ||
| + public static final ImageView ICON_DIALOG_NODE = new ImageView( ICON_DIALOG ); | ||
| + | ||
| + /** | ||
| + * Converts the given file names to images, such as application icons. | ||
| + * | ||
| + * @param keys The file names to convert to images. | ||
| + * @return The images loaded from the file name references. | ||
| + */ | ||
| + private static List<Image> createImages( final String... keys ) { | ||
| + final List<Image> images = new ArrayList<>( keys.length ); | ||
| + | ||
| + for( final var key : keys ) { | ||
| + images.add( new Image( get( key ) ) ); | ||
| + } | ||
| + | ||
| + return images; | ||
| + } | ||
| +} | ||
| import static com.keenwrite.Bootstrap.APP_TITLE; | ||
| -import static com.keenwrite.Constants.LOGOS; | ||
| +import static com.keenwrite.GraphicsConstants.LOGOS; | ||
| import static com.keenwrite.preferences.WorkspaceKeys.*; | ||
| import static com.keenwrite.util.FontLoader.initFonts; |
| /** | ||
| - * Indicates that there are no issues to bring to the user's attention. | ||
| - */ | ||
| - private static final StatusEvent OK = | ||
| - new StatusEvent( get( STATUS_BAR_OK, "OK" ) ); | ||
| - | ||
| - /** | ||
| * Detailed information about a problem. | ||
| */ | ||
| */ | ||
| public static void clue() { | ||
| - OK.fire(); | ||
| + // Indicates that there are no issues to bring to the user's attention. | ||
| + new StatusEvent( get( STATUS_BAR_OK, "OK" ) ); | ||
| } | ||
| import java.util.zip.GZIPInputStream; | ||
| +import static com.keenwrite.events.StatusEvent.clue; | ||
| import static java.lang.System.getProperty; | ||
| import static java.lang.System.setProperty; | ||
| */ | ||
| public static Response httpGet( final URI uri ) throws IOException { | ||
| + clue( "Main.status.image.request.init" ); | ||
| return httpGet( uri.toURL() ); | ||
| } | ||
| mConn.setRequestProperty( "connection", "close" ); | ||
| mConn.connect(); | ||
| + clue( "Main.status.image.request.fetch", url.getHost() ); | ||
| final var code = mConn.getResponseCode(); | ||
| if( mediaType.isUndefined() ) { | ||
| - mediaType = StreamMediaType.getMediaType( mStream ); | ||
| + mediaType = MediaTypeSniffer.getMediaType( mStream ); | ||
| } | ||
| + clue( "Main.status.image.request.success", mediaType ); | ||
| return mediaType; | ||
| } | ||
| -/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | ||
| -package com.keenwrite.io; | ||
| - | ||
| -import java.io.IOException; | ||
| -import java.net.URI; | ||
| - | ||
| -import static com.keenwrite.events.StatusEvent.clue; | ||
| -import static com.keenwrite.io.HttpFacade.httpGet; | ||
| -import static com.keenwrite.io.MediaType.UNDEFINED; | ||
| - | ||
| -/** | ||
| - * Responsible for determining {@link MediaType} based on the content-type from | ||
| - * an HTTP request. | ||
| - */ | ||
| -public final class HttpMediaType { | ||
| - | ||
| - /** | ||
| - * Performs an HTTP request to determine the media type based on the | ||
| - * Content-Type header returned from the server. | ||
| - * | ||
| - * @param uri Determine the media type for this resource. | ||
| - * @return The data type for the resource or {@link MediaType#UNDEFINED} if | ||
| - * unmapped. | ||
| - * @throws IOException The {@link URI} could not be fetched. | ||
| - */ | ||
| - public static MediaType valueFrom( final URI uri ) throws IOException { | ||
| - var mediaType = UNDEFINED; | ||
| - | ||
| - clue( "Main.status.image.request.init" ); | ||
| - | ||
| - try( final var response = httpGet( uri ) ) { | ||
| - clue( "Main.status.image.request.fetch", uri.getHost() ); | ||
| - mediaType = response.getMediaType(); | ||
| - clue( "Main.status.image.request.success", mediaType ); | ||
| - } | ||
| - | ||
| - return mediaType; | ||
| - } | ||
| -} | ||
| +package com.keenwrite.io; | ||
| + | ||
| +import java.io.BufferedInputStream; | ||
| +import java.io.FileInputStream; | ||
| +import java.io.IOException; | ||
| +import java.io.InputStream; | ||
| +import java.nio.file.Path; | ||
| +import java.util.LinkedHashMap; | ||
| +import java.util.Map; | ||
| + | ||
| +import static com.keenwrite.io.MediaType.*; | ||
| +import static java.lang.System.arraycopy; | ||
| + | ||
| +/** | ||
| + * Responsible for associating file signatures with IANA-defined | ||
| + * {@link MediaType} instances. For details see: | ||
| + * <ul> | ||
| + * <li> | ||
| + * <a href="https://www.garykessler.net/library/file_sigs.html">Kessler's List</a> | ||
| + * </li> | ||
| + * <li> | ||
| + * <a href="https://en.wikipedia.org/wiki/List_of_file_signatures">Wikipedia's List</a> | ||
| + * </li> | ||
| + * <li> | ||
| + * <a href="https://github.com/veniware/Space-Maker/blob/master/FileSignatures.cs">Space Maker's List</a> | ||
| + * </li> | ||
| + * </ul> | ||
| + */ | ||
| +public class MediaTypeSniffer { | ||
| + private static final int FORMAT_LENGTH = 11; | ||
| + private static final int END_OF_DATA = -2; | ||
| + | ||
| + private static final Map<int[], MediaType> FORMAT = new LinkedHashMap<>(); | ||
| + | ||
| + static { | ||
| + //@formatter:off | ||
| + FORMAT.put( ints( 0x3C, 0x73, 0x76, 0x67, 0x20 ), IMAGE_SVG_XML ); | ||
| + FORMAT.put( ints( 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), IMAGE_PNG ); | ||
| + FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xE0 ), IMAGE_JPEG ); | ||
| + FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xEE ), IMAGE_JPEG ); | ||
| + FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xE1, -1, -1, 0x45, 0x78, 0x69, 0x66, 0x00 ), IMAGE_JPEG ); | ||
| + FORMAT.put( ints( 0x49, 0x49, 0x2A, 0x00 ), IMAGE_TIFF ); | ||
| + FORMAT.put( ints( 0x4D, 0x4D, 0x00, 0x2A ), IMAGE_TIFF ); | ||
| + FORMAT.put( ints( 0x47, 0x49, 0x46, 0x38 ), IMAGE_GIF ); | ||
| + FORMAT.put( ints( 0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E ), APP_PDF ); | ||
| + FORMAT.put( ints( 0x25, 0x21, 0x50, 0x53, 0x2D, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x2D ), APP_EPS ); | ||
| + FORMAT.put( ints( 0x25, 0x21, 0x50, 0x53 ), APP_PS ); | ||
| + FORMAT.put( ints( 0x38, 0x42, 0x50, 0x53, 0x00, 0x01 ), IMAGE_PHOTOSHOP ); | ||
| + FORMAT.put( ints( 0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), VIDEO_MNG ); | ||
| + FORMAT.put( ints( 0x42, 0x4D ), IMAGE_BMP ); | ||
| + FORMAT.put( ints( 0xFF, 0xFB, 0x30 ), AUDIO_MP3 ); | ||
| + FORMAT.put( ints( 0x49, 0x44, 0x33 ), AUDIO_MP3 ); | ||
| + FORMAT.put( ints( 0x3C, 0x21 ), TEXT_HTML ); | ||
| + FORMAT.put( ints( 0x3C, 0x68, 0x74, 0x6D, 0x6C ), TEXT_HTML ); | ||
| + FORMAT.put( ints( 0x3C, 0x68, 0x65, 0x61, 0x64 ), TEXT_HTML ); | ||
| + FORMAT.put( ints( 0x3C, 0x62, 0x6F, 0x64, 0x79 ), TEXT_HTML ); | ||
| + FORMAT.put( ints( 0x3C, 0x48, 0x54, 0x4D, 0x4C ), TEXT_HTML ); | ||
| + FORMAT.put( ints( 0x3C, 0x48, 0x45, 0x41, 0x44 ), TEXT_HTML ); | ||
| + FORMAT.put( ints( 0x3C, 0x42, 0x4F, 0x44, 0x59 ), TEXT_HTML ); | ||
| + FORMAT.put( ints( 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20 ), TEXT_XML ); | ||
| + FORMAT.put( ints( 0xFE, 0xFF, 0x00, 0x3C, 0x00, 0x3f, 0x00, 0x78 ), TEXT_XML ); | ||
| + FORMAT.put( ints( 0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00 ), TEXT_XML ); | ||
| + FORMAT.put( ints( 0x23, 0x64, 0x65, 0x66 ), IMAGE_X_BITMAP ); | ||
| + FORMAT.put( ints( 0x21, 0x20, 0x58, 0x50, 0x4D, 0x32 ), IMAGE_X_PIXMAP ); | ||
| + FORMAT.put( ints( 0x2E, 0x73, 0x6E, 0x64 ), AUDIO_BASIC ); | ||
| + FORMAT.put( ints( 0x64, 0x6E, 0x73, 0x2E ), AUDIO_BASIC ); | ||
| + FORMAT.put( ints( 0x52, 0x49, 0x46, 0x46 ), AUDIO_WAV ); | ||
| + FORMAT.put( ints( 0x50, 0x4B ), APP_ZIP ); | ||
| + FORMAT.put( ints( 0x41, 0x43, -1, -1, -1, -1, 0x00, 0x00, 0x00, 0x00, 0x00 ), APP_ACAD ); | ||
| + FORMAT.put( ints( 0xCA, 0xFE, 0xBA, 0xBE ), APP_JAVA ); | ||
| + FORMAT.put( ints( 0xAC, 0xED ), APP_JAVA_OBJECT ); | ||
| + //@formatter:on | ||
| + } | ||
| + | ||
| + private MediaTypeSniffer() { | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the {@link MediaType} for a given set of bytes. | ||
| + * | ||
| + * @param data Binary data to compare against the list of known formats. | ||
| + * @return The IANA-defined {@link MediaType}, or | ||
| + * {@link MediaType#UNDEFINED} if indeterminate. | ||
| + */ | ||
| + public static MediaType getMediaType( final byte[] data ) { | ||
| + assert data != null; | ||
| + | ||
| + final var source = new int[]{ | ||
| + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; | ||
| + | ||
| + for( int i = 0; i < data.length; i++ ) { | ||
| + source[ i ] = data[ i ] & 0xFF; | ||
| + } | ||
| + | ||
| + for( final var key : FORMAT.keySet() ) { | ||
| + int i = -1; | ||
| + boolean matches = true; | ||
| + | ||
| + while( ++i < FORMAT_LENGTH && key[ i ] != END_OF_DATA && matches ) { | ||
| + matches = key[ i ] == source[ i ] || key[ i ] == -1; | ||
| + } | ||
| + | ||
| + if( matches ) { | ||
| + return FORMAT.get( key ); | ||
| + } | ||
| + } | ||
| + | ||
| + return UNDEFINED; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Convenience method to return the probed media type for the given | ||
| + * {@link Path} instance by delegating to {@link #getMediaType(InputStream)}. | ||
| + * | ||
| + * @param path Path to ascertain the {@link MediaType}. | ||
| + * @return The IANA-defined {@link MediaType}, or | ||
| + * {@link MediaType#UNDEFINED} if indeterminate. | ||
| + * @throws IOException Could not read from the {@link File}. | ||
| + */ | ||
| + public static MediaType getMediaType( final Path path ) throws IOException { | ||
| + return getMediaType( path.toFile() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Convenience method to return the probed media type for the given | ||
| + * {@link File} instance by delegating to {@link #getMediaType(InputStream)}. | ||
| + * | ||
| + * @param file File to ascertain the {@link MediaType}. | ||
| + * @return The IANA-defined {@link MediaType}, or | ||
| + * {@link MediaType#UNDEFINED} if indeterminate. | ||
| + * @throws IOException Could not read from the {@link File}. | ||
| + */ | ||
| + public static MediaType getMediaType( final java.io.File file ) | ||
| + throws IOException { | ||
| + try( final var fis = new FileInputStream( file ) ) { | ||
| + return getMediaType( fis ); | ||
| + } | ||
| + } | ||
| + | ||
| + /** | ||
| + * Convenience method to return the probed media type for the given | ||
| + * {@link BufferedInputStream} instance. <strong>This resets the stream | ||
| + * pointer</strong> making the call idempotent. Users of this class should | ||
| + * prefer to call this method when operating on streams to avoid advancing | ||
| + * the stream. | ||
| + * | ||
| + * @param bis Data source to ascertain the {@link MediaType}. | ||
| + * @return The IANA-defined {@link MediaType}, or | ||
| + * {@link MediaType#UNDEFINED} if indeterminate. | ||
| + * @throws IOException Could not read from the {@link File}. | ||
| + */ | ||
| + public static MediaType getMediaType( final BufferedInputStream bis ) | ||
| + throws IOException { | ||
| + bis.mark( FORMAT_LENGTH ); | ||
| + final var result = getMediaType( (InputStream) bis ); | ||
| + bis.reset(); | ||
| + | ||
| + return result; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Helper method to return the probed media type for the given | ||
| + * {@link InputStream} instance. The caller is responsible for closing | ||
| + * the stream. <strong>This advances the stream pointer.</strong> | ||
| + * | ||
| + * @param is Data source to ascertain the {@link MediaType}. | ||
| + * @return The IANA-defined {@link MediaType}, or | ||
| + * {@link MediaType#UNDEFINED} if indeterminate. | ||
| + * @throws IOException Could not read from the {@link InputStream}. | ||
| + * @see #getMediaType(BufferedInputStream) to perform a non-destructive | ||
| + * read. | ||
| + */ | ||
| + private static MediaType getMediaType( final InputStream is ) | ||
| + throws IOException { | ||
| + final var input = new byte[ FORMAT_LENGTH ]; | ||
| + final var count = is.read( input, 0, FORMAT_LENGTH ); | ||
| + | ||
| + if( count > 1 ) { | ||
| + final var available = new byte[ count ]; | ||
| + arraycopy( input, 0, available, 0, count ); | ||
| + return getMediaType( available ); | ||
| + } | ||
| + | ||
| + return UNDEFINED; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates an array of integers from the given data, padded with {@link | ||
| + * #END_OF_DATA} values up to {@link #FORMAT_LENGTH}. | ||
| + * | ||
| + * @param data The input byte values to pad. | ||
| + * @return The data with padding. | ||
| + */ | ||
| + private static int[] ints( final int... data ) { | ||
| + final var magic = new int[ FORMAT_LENGTH ]; | ||
| + int i = -1; | ||
| + while( ++i < data.length ) { | ||
| + magic[ i ] = data[ i ]; | ||
| + } | ||
| + | ||
| + while( i < FORMAT_LENGTH ) { | ||
| + magic[ i++ ] = END_OF_DATA; | ||
| + } | ||
| + | ||
| + return magic; | ||
| + } | ||
| +} | ||
| -package com.keenwrite.io; | ||
| - | ||
| -import java.io.BufferedInputStream; | ||
| -import java.io.FileInputStream; | ||
| -import java.io.IOException; | ||
| -import java.io.InputStream; | ||
| -import java.nio.file.Path; | ||
| -import java.util.LinkedHashMap; | ||
| -import java.util.Map; | ||
| - | ||
| -import static com.keenwrite.io.MediaType.*; | ||
| -import static java.lang.System.arraycopy; | ||
| - | ||
| -/** | ||
| - * Responsible for associating file signatures with IANA-defined | ||
| - * {@link MediaType} instances. For details see: | ||
| - * <ul> | ||
| - * <li> | ||
| - * <a href="https://www.garykessler.net/library/file_sigs.html">Kessler's List</a> | ||
| - * </li> | ||
| - * <li> | ||
| - * <a href="https://en.wikipedia.org/wiki/List_of_file_signatures">Wikipedia's List</a> | ||
| - * </li> | ||
| - * <li> | ||
| - * <a href="https://github.com/veniware/Space-Maker/blob/master/FileSignatures.cs">Space Maker's List</a> | ||
| - * </li> | ||
| - * </ul> | ||
| - */ | ||
| -public class StreamMediaType { | ||
| - private static final int FORMAT_LENGTH = 11; | ||
| - private static final int END_OF_DATA = -2; | ||
| - | ||
| - private static final Map<int[], MediaType> FORMAT = new LinkedHashMap<>(); | ||
| - | ||
| - static { | ||
| - //@formatter:off | ||
| - FORMAT.put( ints( 0x3C, 0x73, 0x76, 0x67, 0x20 ), IMAGE_SVG_XML ); | ||
| - FORMAT.put( ints( 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), IMAGE_PNG ); | ||
| - FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xE0 ), IMAGE_JPEG ); | ||
| - FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xEE ), IMAGE_JPEG ); | ||
| - FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xE1, -1, -1, 0x45, 0x78, 0x69, 0x66, 0x00 ), IMAGE_JPEG ); | ||
| - FORMAT.put( ints( 0x49, 0x49, 0x2A, 0x00 ), IMAGE_TIFF ); | ||
| - FORMAT.put( ints( 0x4D, 0x4D, 0x00, 0x2A ), IMAGE_TIFF ); | ||
| - FORMAT.put( ints( 0x47, 0x49, 0x46, 0x38 ), IMAGE_GIF ); | ||
| - FORMAT.put( ints( 0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), VIDEO_MNG ); | ||
| - FORMAT.put( ints( 0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E ), APP_PDF ); | ||
| - FORMAT.put( ints( 0x38, 0x42, 0x50, 0x53, 0x00, 0x01 ), IMAGE_PHOTOSHOP ); | ||
| - FORMAT.put( ints( 0x25, 0x21, 0x50, 0x53, 0x2D, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x2D ), APP_EPS ); | ||
| - FORMAT.put( ints( 0x25, 0x21, 0x50, 0x53 ), APP_PS ); | ||
| - FORMAT.put( ints( 0xFF, 0xFB, 0x30 ), AUDIO_MP3 ); | ||
| - FORMAT.put( ints( 0x49, 0x44, 0x33 ), AUDIO_MP3 ); | ||
| - FORMAT.put( ints( 0x3C, 0x21 ), TEXT_HTML ); | ||
| - FORMAT.put( ints( 0x3C, 0x68, 0x74, 0x6D, 0x6C ), TEXT_HTML ); | ||
| - FORMAT.put( ints( 0x3C, 0x68, 0x65, 0x61, 0x64 ), TEXT_HTML ); | ||
| - FORMAT.put( ints( 0x3C, 0x62, 0x6F, 0x64, 0x79 ), TEXT_HTML ); | ||
| - FORMAT.put( ints( 0x3C, 0x48, 0x54, 0x4D, 0x4C ), TEXT_HTML ); | ||
| - FORMAT.put( ints( 0x3C, 0x48, 0x45, 0x41, 0x44 ), TEXT_HTML ); | ||
| - FORMAT.put( ints( 0x3C, 0x42, 0x4F, 0x44, 0x59 ), TEXT_HTML ); | ||
| - FORMAT.put( ints( 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20 ), TEXT_XML ); | ||
| - FORMAT.put( ints( 0xFE, 0xFF, 0x00, 0x3C, 0x00, 0x3f, 0x00, 0x78 ), TEXT_XML ); | ||
| - FORMAT.put( ints( 0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00 ), TEXT_XML ); | ||
| - FORMAT.put( ints( 0x42, 0x4D ), IMAGE_BMP ); | ||
| - FORMAT.put( ints( 0x23, 0x64, 0x65, 0x66 ), IMAGE_X_BITMAP ); | ||
| - FORMAT.put( ints( 0x21, 0x20, 0x58, 0x50, 0x4D, 0x32 ), IMAGE_X_PIXMAP ); | ||
| - FORMAT.put( ints( 0x2E, 0x73, 0x6E, 0x64 ), AUDIO_BASIC ); | ||
| - FORMAT.put( ints( 0x64, 0x6E, 0x73, 0x2E ), AUDIO_BASIC ); | ||
| - FORMAT.put( ints( 0x52, 0x49, 0x46, 0x46 ), AUDIO_WAV ); | ||
| - FORMAT.put( ints( 0x50, 0x4B ), APP_ZIP ); | ||
| - FORMAT.put( ints( 0x41, 0x43, -1, -1, -1, -1, 0x00, 0x00, 0x00, 0x00, 0x00 ), APP_ACAD ); | ||
| - FORMAT.put( ints( 0xCA, 0xFE, 0xBA, 0xBE ), APP_JAVA ); | ||
| - FORMAT.put( ints( 0xAC, 0xED ), APP_JAVA_OBJECT ); | ||
| - //@formatter:on | ||
| - } | ||
| - | ||
| - private StreamMediaType() { | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the {@link MediaType} for a given set of bytes. | ||
| - * | ||
| - * @param data Binary data to compare against the list of known formats. | ||
| - * @return The IANA-defined {@link MediaType}, or | ||
| - * {@link MediaType#UNDEFINED} if indeterminate. | ||
| - */ | ||
| - public static MediaType getMediaType( final byte[] data ) { | ||
| - assert data != null; | ||
| - | ||
| - final var source = new int[]{ | ||
| - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; | ||
| - | ||
| - for( int i = 0; i < data.length; i++ ) { | ||
| - source[ i ] = data[ i ] & 0xFF; | ||
| - } | ||
| - | ||
| - for( final var key : FORMAT.keySet() ) { | ||
| - int i = -1; | ||
| - boolean matches = true; | ||
| - | ||
| - while( ++i < FORMAT_LENGTH && key[ i ] != END_OF_DATA && matches ) { | ||
| - matches = key[ i ] == source[ i ] || key[ i ] == -1; | ||
| - } | ||
| - | ||
| - if( matches ) { | ||
| - return FORMAT.get( key ); | ||
| - } | ||
| - } | ||
| - | ||
| - return UNDEFINED; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Convenience method to return the probed media type for the given | ||
| - * {@link Path} instance by delegating to {@link #getMediaType(InputStream)}. | ||
| - * | ||
| - * @param path Path to ascertain the {@link MediaType}. | ||
| - * @return The IANA-defined {@link MediaType}, or | ||
| - * {@link MediaType#UNDEFINED} if indeterminate. | ||
| - * @throws IOException Could not read from the {@link File}. | ||
| - */ | ||
| - public static MediaType getMediaType( final Path path ) throws IOException { | ||
| - return getMediaType( path.toFile() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Convenience method to return the probed media type for the given | ||
| - * {@link File} instance by delegating to {@link #getMediaType(InputStream)}. | ||
| - * | ||
| - * @param file File to ascertain the {@link MediaType}. | ||
| - * @return The IANA-defined {@link MediaType}, or | ||
| - * {@link MediaType#UNDEFINED} if indeterminate. | ||
| - * @throws IOException Could not read from the {@link File}. | ||
| - */ | ||
| - public static MediaType getMediaType( final java.io.File file ) | ||
| - throws IOException { | ||
| - try( final var fis = new FileInputStream( file ) ) { | ||
| - return getMediaType( fis ); | ||
| - } | ||
| - } | ||
| - | ||
| - /** | ||
| - * Convenience method to return the probed media type for the given | ||
| - * {@link BufferedInputStream} instance. <strong>This resets the stream | ||
| - * pointer</strong> making the call idempotent. Users of this class should | ||
| - * prefer to call this method when operating on streams to avoid advancing | ||
| - * the stream. | ||
| - * | ||
| - * @param bis Data source to ascertain the {@link MediaType}. | ||
| - * @return The IANA-defined {@link MediaType}, or | ||
| - * {@link MediaType#UNDEFINED} if indeterminate. | ||
| - * @throws IOException Could not read from the {@link File}. | ||
| - */ | ||
| - public static MediaType getMediaType( final BufferedInputStream bis ) | ||
| - throws IOException { | ||
| - bis.mark( FORMAT_LENGTH ); | ||
| - final var result = getMediaType( (InputStream) bis ); | ||
| - bis.reset(); | ||
| - | ||
| - return result; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Helper method to return the probed media type for the given | ||
| - * {@link InputStream} instance. The caller is responsible for closing | ||
| - * the stream. <strong>This advances the stream pointer.</strong> | ||
| - * | ||
| - * @param is Data source to ascertain the {@link MediaType}. | ||
| - * @return The IANA-defined {@link MediaType}, or | ||
| - * {@link MediaType#UNDEFINED} if indeterminate. | ||
| - * @throws IOException Could not read from the {@link InputStream}. | ||
| - * @see #getMediaType(BufferedInputStream) to perform a non-destructive | ||
| - * read. | ||
| - */ | ||
| - private static MediaType getMediaType( final InputStream is ) | ||
| - throws IOException { | ||
| - final var input = new byte[ FORMAT_LENGTH ]; | ||
| - final var count = is.read( input, 0, FORMAT_LENGTH ); | ||
| - | ||
| - if( count > 1 ) { | ||
| - final var available = new byte[ count ]; | ||
| - arraycopy( input, 0, available, 0, count ); | ||
| - return getMediaType( available ); | ||
| - } | ||
| - | ||
| - return UNDEFINED; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Creates an array of integers from the given data, padded with {@link | ||
| - * #END_OF_DATA} values up to {@link #FORMAT_LENGTH}. | ||
| - * | ||
| - * @param data The input byte values to pad. | ||
| - * @return The data with padding. | ||
| - */ | ||
| - private static int[] ints( final int... data ) { | ||
| - final var magic = new int[ FORMAT_LENGTH ]; | ||
| - int i = -1; | ||
| - while( ++i < data.length ) { | ||
| - magic[ i ] = data[ i ]; | ||
| - } | ||
| - | ||
| - while( i < FORMAT_LENGTH ) { | ||
| - magic[ i++ ] = END_OF_DATA; | ||
| - } | ||
| - | ||
| - return magic; | ||
| - } | ||
| -} | ||
| import static com.dlsc.formsfx.model.structure.Field.ofStringType; | ||
| import static com.dlsc.preferencesfx.PreferencesFxEvent.EVENT_PREFERENCES_SAVED; | ||
| -import static com.keenwrite.Constants.ICON_DIALOG; | ||
| +import static com.keenwrite.GraphicsConstants.ICON_DIALOG; | ||
| import static com.keenwrite.Messages.get; | ||
| import static com.keenwrite.preferences.LocaleProperty.localeListProperty; |
| import org.controlsfx.dialog.FontSelectorDialog; | ||
| -import static com.keenwrite.Constants.ICON_DIALOG; | ||
| +import static com.keenwrite.GraphicsConstants.ICON_DIALOG; | ||
| import static com.keenwrite.events.StatusEvent.clue; | ||
| import static java.lang.System.currentTimeMillis; |
| package com.keenwrite.preview; | ||
| -import com.keenwrite.io.HttpMediaType; | ||
| import com.keenwrite.io.MediaType; | ||
| import com.keenwrite.ui.adapters.ReplacedElementAdapter; | ||
| import static com.keenwrite.events.StatusEvent.clue; | ||
| -import static com.keenwrite.io.MediaType.UNDEFINED; | ||
| +import static com.keenwrite.io.HttpFacade.httpGet; | ||
| import static com.keenwrite.preview.MathRenderer.MATH_RENDERER; | ||
| import static com.keenwrite.preview.SvgRasterizer.BROKEN_IMAGE_PLACEHOLDER; | ||
| case HTML_IMAGE -> { | ||
| final var source = e.getAttribute( HTML_IMAGE_SRC ); | ||
| + var mediaType = MediaType.fromFilename( source ); | ||
| URI uri = null; | ||
| if( getProtocol( source ).isHttp() ) { | ||
| - var mediaType = MediaType.fromFilename( source ); | ||
| - | ||
| - if( mediaType.isSvg() || mediaType == UNDEFINED ) { | ||
| + if( mediaType.isSvg() || mediaType.isUndefined() ) { | ||
| uri = new URI( source ); | ||
| + | ||
| + try( final var response = httpGet( uri ) ) { | ||
| + mediaType = response.getMediaType(); | ||
| + } | ||
| // Attempt to rasterize SVG depending on URL resource content. | ||
| - if( !HttpMediaType.valueFrom( uri ).isSvg() ) { | ||
| + if( !mediaType.isSvg() ) { | ||
| uri = null; | ||
| } | ||
| } | ||
| } | ||
| - else if( MediaType.fromFilename( source ).isSvg() ) { | ||
| + else if( mediaType.isSvg() ) { | ||
| // Attempt to rasterize based on file name. | ||
| final var path = Path.of( new URI( source ).getPath() ); | ||
| import java.nio.file.Path; | ||
| -import static com.keenwrite.Constants.ICON_DIALOG_NODE; | ||
| +import static com.keenwrite.GraphicsConstants.ICON_DIALOG_NODE; | ||
| import static com.keenwrite.Messages.get; | ||
| import static javafx.scene.control.Alert.AlertType.CONFIRMATION; |
| import static com.keenwrite.Bootstrap.*; | ||
| -import static com.keenwrite.Constants.ICON_DIALOG_NODE; | ||
| +import static com.keenwrite.GraphicsConstants.ICON_DIALOG_NODE; | ||
| import static com.keenwrite.ExportFormat.*; | ||
| import static com.keenwrite.Messages.get; |
| import javafx.stage.Window; | ||
| -import static com.keenwrite.Constants.ICON_DIALOG; | ||
| +import static com.keenwrite.GraphicsConstants.ICON_DIALOG; | ||
| import static com.keenwrite.Messages.get; | ||
| import static javafx.scene.control.ButtonType.CANCEL; |
| import static com.keenwrite.Bootstrap.APP_TITLE_LOWERCASE; | ||
| import static com.keenwrite.Constants.ACTION_PREFIX; | ||
| -import static com.keenwrite.Constants.ICON_DIALOG; | ||
| +import static com.keenwrite.GraphicsConstants.ICON_DIALOG; | ||
| import static com.keenwrite.Messages.get; | ||
| import static com.keenwrite.events.Bus.register; |
| +package com.keenwrite.io; | ||
| + | ||
| +import org.junit.jupiter.api.Test; | ||
| + | ||
| +import static com.keenwrite.io.MediaTypeExtension.valueFrom; | ||
| +import static org.apache.commons.io.FilenameUtils.getExtension; | ||
| +import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| +import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| + | ||
| +/** | ||
| + * Responsible for testing that {@link MediaTypeSniffer} can return the | ||
| + * correct IANA-defined {@link MediaType} for known file types. | ||
| + */ | ||
| +class MediaTypeSnifferTest { | ||
| + | ||
| + @Test | ||
| + void test_Read_KnownFileTypes_MediaTypeReturned() | ||
| + throws Exception { | ||
| + final var clazz = getClass(); | ||
| + final var pkgName = clazz.getPackageName(); | ||
| + final var dir = pkgName.replace( '.', '/' ); | ||
| + | ||
| + final var urls = clazz.getClassLoader().getResources( dir + "/images" ); | ||
| + assertTrue( urls.hasMoreElements() ); | ||
| + | ||
| + while( urls.hasMoreElements() ) { | ||
| + final var url = urls.nextElement(); | ||
| + final var path = new File( url.toURI().getPath() ); | ||
| + | ||
| + for( final var image : path.listFiles() ) { | ||
| + final var media = MediaTypeSniffer.getMediaType( image ); | ||
| + final var actualExtension = valueFrom( media ).getExtension(); | ||
| + final var expectedExtension = getExtension( image.toString() ); | ||
| + assertEquals( expectedExtension, actualExtension ); | ||
| + } | ||
| + } | ||
| + } | ||
| +} | ||
| import java.util.Map; | ||
| +import static com.keenwrite.io.HttpFacade.httpGet; | ||
| import static com.keenwrite.io.MediaType.*; | ||
| import static org.junit.jupiter.api.Assertions.*; | ||
| /** | ||
| - * Test that {@link HttpMediaType#valueFrom(URI)} will pull and identify the | ||
| - * type of resource based on the HTTP Content-Type header. | ||
| + * Test that remote fetches will pull and identify the type of resource | ||
| + * based on the HTTP Content-Type header (or shallow decoding). | ||
| */ | ||
| @Test | ||
| public void test_HttpRequest_Supported_Success() { | ||
| //@formatter:off | ||
| final var map = Map.of( | ||
| "https://stackoverflow.com/robots.txt", TEXT_PLAIN, | ||
| "https://place-hold.it/300x500", IMAGE_GIF, | ||
| + "https://placekitten.com/g/200/300", IMAGE_JPEG, | ||
| "https://upload.wikimedia.org/wikipedia/commons/9/9f/Vimlogo.svg", IMAGE_SVG_XML, | ||
| - "https://kroki.io//graphviz/svg/eNpLyUwvSizIUHBXqPZIzcnJ17ULzy_KSanlAgB1EAjQ", TEXT_PLAIN | ||
| + "https://kroki.io//graphviz/svg/eNpLyUwvSizIUHBXqPZIzcnJ17ULzy_KSanlAgB1EAjQ", IMAGE_SVG_XML | ||
| ); | ||
| //@formatter:on | ||
| map.forEach( ( k, v ) -> { | ||
| - try { | ||
| - assertEquals( v, HttpMediaType.valueFrom( new URI( k ) ) ); | ||
| + try( var response = httpGet( new URI( k ) ) ) { | ||
| + assertEquals( v, response.getMediaType() ); | ||
| } catch( Exception e ) { | ||
| fail(); | ||
| -package com.keenwrite.io; | ||
| - | ||
| -import org.junit.jupiter.api.Test; | ||
| - | ||
| -import static com.keenwrite.io.MediaTypeExtension.valueFrom; | ||
| -import static org.apache.commons.io.FilenameUtils.getExtension; | ||
| -import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| -import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| - | ||
| -/** | ||
| - * Responsible for testing that {@link StreamMediaType} can return the | ||
| - * correct IANA-defined {@link MediaType} for known file types. | ||
| - */ | ||
| -class StreamMediaTypeTest { | ||
| - | ||
| - @Test | ||
| - void test_Read_KnownFileTypes_MediaTypeReturned() | ||
| - throws Exception { | ||
| - final var clazz = getClass(); | ||
| - final var pkgName = clazz.getPackageName(); | ||
| - final var dir = pkgName.replace( '.', '/' ); | ||
| - | ||
| - final var urls = clazz.getClassLoader().getResources( dir + "/images" ); | ||
| - assertTrue( urls.hasMoreElements() ); | ||
| - | ||
| - while( urls.hasMoreElements() ) { | ||
| - final var url = urls.nextElement(); | ||
| - final var path = new File( url.toURI().getPath() ); | ||
| - | ||
| - for( final var image : path.listFiles() ) { | ||
| - final var media = StreamMediaType.getMediaType( image ); | ||
| - final var actualExtension = valueFrom( media ).getExtension(); | ||
| - final var expectedExtension = getExtension( image.toString() ); | ||
| - assertEquals( expectedExtension, actualExtension ); | ||
| - } | ||
| - } | ||
| - } | ||
| -} | ||
| Delta | 320 lines added, 343 lines removed, 23-line decrease |
|---|