001/*******************************************************************************
002 * Copyright (c) 2024, 2026, Olivier Ayache.  All rights reserved.
003 *
004 * This file is part of AVPKit.
005 *
006 * AVPKit is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU Lesser General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * AVPKit is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with AVPKit.  If not, see <http://www.gnu.org/licenses/>.
018 *******************************************************************************/
019
020package com.avpkit.core.io;
021
022import java.io.File;
023import java.io.IOException;
024import java.io.RandomAccessFile;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028import com.avpkit.core.io.IURLProtocolHandler;
029
030/**
031 * Implementation of URLProtocolHandler that can read and write files.
032 * 
033 * This just duplicates all the functionality in the default "file:" protocol
034 * that FFMPEG implemements, but demonstrates how you can have FFMPEG
035 * call back into Java.
036 * 
037 * @author aclarke
038 *
039 */
040public class FileProtocolHandler implements IURLProtocolHandler
041{
042  File file = null;
043  RandomAccessFile stream = null;
044
045  private final Logger log = LoggerFactory.getLogger(this.getClass());
046
047  public FileProtocolHandler()
048  {
049    log.debug("Initializing file protocol handler without file");
050    this.file = null;
051  }
052
053  public FileProtocolHandler(File file)
054  {
055    log.debug("Initializing file protocol handler: {}", file);
056    this.file = file;
057  }
058
059  public FileProtocolHandler(String filename)
060  {
061    log.debug("Initializing file protocol handler: {}", filename);
062    filename = getFilename(filename);
063    if (filename != null)
064    {
065      this.file = new File(filename);
066    }
067    else
068    {
069      this.file = null;
070    }
071  }
072
073  public int close()
074  {
075    log.debug("Closing file: {}", file);
076    try
077    {
078      stream.close();
079    }
080    catch (IOException e)
081    {
082      log.error("Error closing file: {}", file);
083      e.printStackTrace();
084      return -1;
085    }
086
087    log.debug("Succesfully closed file: {}", file);
088    return 0;
089  }
090
091  public int open(String url, int flags)
092  {
093    int retval = -1;
094    log.debug("attempting to open {} with flags {}", url == null ? file
095        : url, flags);
096    if (stream != null)
097      this.close();
098
099    if (this.file == null)
100    {
101      url = getFilename(url);
102      if (url != null)
103      {
104        file = new File(url);
105      }
106    }
107    log.debug("Opening file: {}", file);
108
109    final String mode;
110    switch (flags)
111    {
112    case URL_RDWR:
113      mode = "rw";
114      break;
115    case URL_WRONLY_MODE:
116      // RandomAccessFile doesn't support write-only
117      mode = "rw";
118      break;
119    case URL_RDONLY_MODE:
120      mode = "r";
121      break;
122    default:
123      log.error("Invalid flag passed to open: {}", file);
124      return retval;
125    }
126
127    log.debug("read mode \"{}\" for file: {}", mode, file);
128
129    try
130    {
131      stream = new RandomAccessFile(file, mode);
132      retval = 0;
133    }
134    catch (Exception e)
135    {
136      log.error("Could not find file: {}; ex: {}", file, e);
137      return retval;
138    }
139    log.debug("Opened file: {}", file);
140    return retval;
141  }
142
143  public int read(byte[] buf, int size)
144  {
145    //log.debug("Attempting to read {} bytes from: {}", size, file);
146    try
147    {
148      int ret = -1;
149      ret = stream.read(buf, 0, size);
150      //log.debug("Got result for read: {}", ret);
151      return ret;
152    }
153    catch (IOException e)
154    {
155      log.error("Got IO exception reading from file: {}", file);
156      e.printStackTrace();
157      return -1;
158    }
159  }
160
161  public long seek(long offset, int whence)
162  {
163    try
164    {
165      final long seek;
166      if (whence == SEEK_SET)
167        seek = offset;
168      else if (whence == SEEK_CUR)
169        seek = stream.getFilePointer() + offset;
170      else if (whence == SEEK_END)
171        seek = stream.length() + offset;
172      else if (whence == SEEK_SIZE)
173        // odd feature of the protocol handler; this request
174        // just returns the file size without actually seeking
175        return (int) stream.length();
176      else
177      {
178        log.error("invalid seek value \"{}\" for file: {}", whence, file);
179        return -1;
180      }
181
182      stream.seek(seek);
183      log.debug("seeking to \"{}\" in: {}", seek, file);
184      return seek;
185    }
186    catch (IOException e)
187    {
188      log.error("got io exception \"{}\" while seeking in: {}", e
189          .getMessage(), file);
190      e.printStackTrace();
191      return -1;
192    }
193  }
194
195  public int write(byte[] buf, int size)
196  {
197    //log.debug("writing {} bytes to: {}", size, file);
198    try
199    {
200      stream.write(buf, 0, size);
201      return size;
202    }
203    catch (IOException e)
204    {
205      log.error("Got error writing to file: {}", file);
206      e.printStackTrace();
207      return -1;
208    }
209  }
210
211  private String getFilename(String url)
212  {
213    String retval = url;
214    if (url != null && url.length() > 0)
215    {
216      int colonIndex = url.indexOf(":");
217      if (colonIndex > 0)
218      {
219        // remove the URL prefix
220        retval = url.substring(colonIndex + 1);
221      }
222    }
223    log.debug("url->filename: {}->{}", url, retval);
224    return retval;
225  }
226
227  public boolean isStreamed(String url, int flags)
228  {
229    return false;
230  }
231}