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.util.concurrent.ConcurrentHashMap;
023import java.util.concurrent.ConcurrentMap;
024
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028
029import com.avpkit.core.io.FfmpegIO;
030import com.avpkit.core.io.FileProtocolHandlerFactory;
031import com.avpkit.core.io.IURLProtocolHandler;
032import com.avpkit.core.io.IURLProtocolHandlerFactory;
033import com.avpkit.core.io.NullProtocolHandlerFactory;
034import com.avpkit.core.io.URLProtocolManager;
035
036/**
037 * This class manages URL protocols that we have handlers for.  It is used
038 * (and called from) the Native code that integrates with FFMPEG.
039 * 
040 * To register a handler you would use the singleton manager as follows:
041 * <code>
042 * <pre>
043 *  URLProtocolManager mgr = URLProtocolManager.getManager();
044 *  mgr.registerFactory("myprotocol", myProtocolHandlerFactory);
045 * </pre>
046 * </code>
047 */
048public class URLProtocolManager
049{
050  private final Logger log = LoggerFactory.getLogger(this.getClass());
051  private final ConcurrentMap<String, IURLProtocolHandlerFactory> mProtocols;
052
053  public static final String DEFAULT_PROTOCOL = "avpkitfile";
054  public static final String NULL_PROTOCOL = "avpkitnull";
055  
056  private static final URLProtocolManager sManager = new URLProtocolManager();
057
058  /**
059   * Factory method to get the global protocol manager.
060   */
061  public static URLProtocolManager getManager()
062  {
063    return sManager;
064  }
065
066  /**
067   * Method to be called to initialize this library.
068   */
069  public static void init()
070  {
071    // just calling this method causes sManager to be initialized
072    // in the static constructor, which is the only initialization
073    // we currently need.
074  }
075
076  // Don't let people create one of these; instead they must
077  // use the Singleton factory method.
078  private URLProtocolManager()
079  {
080    mProtocols = new ConcurrentHashMap<String, IURLProtocolHandlerFactory>();
081    // Always register the DEFAULT
082    registerFactory(DEFAULT_PROTOCOL, new FileProtocolHandlerFactory());
083    // And the NULL protocols
084    registerFactory(NULL_PROTOCOL, new NullProtocolHandlerFactory());        
085  }
086
087  /**
088   * Register a new factory for IURLProtocolHandlers for a given protocol.
089   * <p>
090   * FFMPEG is very picky; protocols must be only alpha characters (no numbers).
091   * </p>
092   * @param protocol The protocol, without colon, this factory is to be used for.
093   * @param factory The factory for the manager to use whenever a handler is requested for a registered protocol;
094   *   null means disable handling for this factory.
095   * @return The factory that was previously used for that protocol (or null if none).
096   */
097  public IURLProtocolHandlerFactory registerFactory(String protocol,
098      IURLProtocolHandlerFactory factory)
099  {
100    if (protocol == null)
101      throw new IllegalArgumentException("protocol required");
102    
103    IURLProtocolHandlerFactory oldFactory;
104    if (factory == null)
105      oldFactory = mProtocols.remove(protocol);
106    else
107      oldFactory = mProtocols.put(protocol, factory);
108    
109    log.trace("Registering factory for URLProtocol: {}", protocol);
110
111    if (oldFactory == null)
112    {
113      // we previously didn't have something registered for this factory
114      // so tell FFMPEG we're now the protocol manager for this protocol.
115      log.trace("Letting FFMPEG know about an additional protocol: {}",
116          protocol);
117      FfmpegIO.registerProtocolHandler(protocol, this);
118    }
119    return oldFactory;
120  }
121  
122  public IURLProtocolHandlerFactory getHandlerFactory(String protocol)
123  {
124    IURLProtocolHandlerFactory result = null;
125
126    log.trace("looking for protocol handler for: {}", protocol);
127    if (protocol == null || protocol.length() == 0)
128      throw new IllegalArgumentException("expected valid protocol");
129
130    IURLProtocolHandlerFactory factory = mProtocols.get(protocol);
131    if (factory != null)
132    {
133      result = factory;
134    }
135    else
136    {
137      log.error("asked to get handler for unsupported protocol: {}",
138          protocol);
139    }
140
141    return result;      
142  }
143
144  /*
145   * Get a IURLProtocolHandler for this url.
146   * <p>
147   * IMPORTANT: This function is called from native code, and so the name and signature cannot
148   * change without changing the native code.
149   * </p><p>
150   * This function is eventually invoked whenever someone tries to call url_open("yourprotocol:...", flags)
151   * from FFMPEG native code.  It returns a protocol handler which will then have open(...) called on it.
152   * </p>
153   * @param url The URL we want to handle.
154   * @param flags   Any flags that the url_open() function will want to pass.
155   * 
156   * @returns A protocol handler to use.
157   */
158  public IURLProtocolHandler getHandler(String url, int flags)
159  {
160    IURLProtocolHandler result = null;
161
162    log.trace("looking for protocol handler for: {}", url);
163    if (url == null || url.length() == 0)
164      throw new IllegalArgumentException("expected valid URL");
165    int colonIndex = url.indexOf(":");
166    String protocol = null;
167    if (colonIndex > 0)
168    {
169      protocol = url.substring(0, colonIndex);
170    }
171    else
172    {
173      protocol = DEFAULT_PROTOCOL;
174    }
175
176    IURLProtocolHandlerFactory factory = mProtocols.get(protocol);
177    if (factory != null)
178    {
179      result = factory.getHandler(url, flags);
180    }
181    else
182    {
183      log.error("asked to get handler for unsupported protocol: {}",
184          protocol);
185    }
186
187    return result;
188  }
189  
190  /**
191   * Get the resource portion of a url.  For example for the URL
192   * <pre>
193   * http://www.avpkit.com/core
194   * </pre>
195   * The protocol string is <code>http</code> and the resource
196   * string is <code>www.avpkit.com/core</code>
197   * 
198   * @param url The url to parse
199   * @return The resource
200   */
201  
202  public static String getResourceFromURL(String url)
203  {
204    String retval = url;
205    if (url != null && url.length() > 0)
206    {
207      int colonIndex;
208      colonIndex = url.indexOf("://");
209      if (colonIndex > 0)
210        retval = url.substring(colonIndex+3);
211      else {
212        colonIndex = url.indexOf(":");
213        if (colonIndex > 0)
214        {
215          // remove the URL prefix
216          retval = url.substring(colonIndex + 1);
217        }
218      }
219    }
220    return retval;
221  }
222
223  /**
224   * Get the protocol portion of a url.  For example for the URL
225   * <pre>
226   * http://www.avpkit.com/core
227   * </pre>
228   * The protocol string is <code>http</code> and the resource
229   * string is <code>//www.avpkit.com/core</code>
230   * 
231   * @param url The url to parse
232   * @return The protocol
233   */
234  
235  public static String getProtocolFromURL(String url)
236  {
237    String retval = null;
238    if (url != null && url.length() > 0)
239    {
240      int colonIndex = url.indexOf(":");
241      if (colonIndex > 0)
242      {
243        // remove the URL suffix
244        retval = url.substring(0, colonIndex);
245      }
246    }
247    return retval;
248  }
249
250}