Avoid unnecessary templates
[alure.git] / examples / alure-physfs.cpp
blob2e1c1fd7e29e2f856cfb9d07729b63bbe8464a0b
1 /*
2 * An example showing how to read files using custom I/O routines. This
3 * specific example uses PhysFS to read files from zip, 7z, and some other
4 * archive formats.
5 */
7 #include <iostream>
8 #include <iomanip>
9 #include <cstring>
10 #include <limits>
11 #include <thread>
12 #include <chrono>
14 #include "physfs.h"
16 #include "alure2.h"
18 namespace {
20 // Inherit from std::streambuf to handle custom I/O (PhysFS for this example)
21 class PhysFSBuf final : public std::streambuf {
22 alure::Array<char_type,4096> mBuffer;
23 PHYSFS_File *mFile{nullptr};
25 int_type underflow() override
27 if(mFile && gptr() == egptr())
29 // Read in the next chunk of data, and set the read pointers on
30 // success
31 PHYSFS_sint64 got = PHYSFS_read(mFile,
32 mBuffer.data(), sizeof(char_type), mBuffer.size()
34 if(got != -1) setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got);
36 if(gptr() == egptr())
37 return traits_type::eof();
38 return traits_type::to_int_type(*gptr());
41 pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override
43 if(!mFile || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
44 return traits_type::eof();
46 // PhysFS only seeks using absolute offsets, so we have to convert cur-
47 // and end-relative offsets.
48 PHYSFS_sint64 fpos;
49 switch(whence)
51 case std::ios_base::beg:
52 break;
54 case std::ios_base::cur:
55 // Need to offset for the file offset being at egptr() while
56 // the requested offset is relative to gptr().
57 offset -= off_type(egptr()-gptr());
58 if((fpos=PHYSFS_tell(mFile)) == -1)
59 return traits_type::eof();
60 // If the offset remains in the current buffer range, just
61 // update the pointer.
62 if(offset < 0 && -offset <= off_type(egptr()-eback()))
64 setg(eback(), egptr()+offset, egptr());
65 return fpos + offset;
67 offset += fpos;
68 break;
70 case std::ios_base::end:
71 if((fpos=PHYSFS_fileLength(mFile)) == -1)
72 return traits_type::eof();
73 offset += fpos;
74 break;
76 default:
77 return traits_type::eof();
80 if(offset < 0) return traits_type::eof();
81 if(PHYSFS_seek(mFile, offset) == 0)
83 // HACK: Workaround a bug in PhysFS. Certain archive types error
84 // when trying to seek to the end of the file. So if seeking to the
85 // end of the file fails, instead try seeking to the last byte and
86 // read it.
87 if(offset != PHYSFS_fileLength(mFile))
88 return traits_type::eof();
89 if(PHYSFS_seek(mFile, offset-1) == 0)
90 return traits_type::eof();
91 PHYSFS_read(mFile, mBuffer.data(), 1, 1);
93 // Clear read pointers so underflow() gets called on the next read
94 // attempt.
95 setg(nullptr, nullptr, nullptr);
96 return offset;
99 pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override
101 // Simplified version of seekoff
102 if(!mFile || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
103 return traits_type::eof();
105 if(PHYSFS_seek(mFile, pos) == 0)
106 return traits_type::eof();
107 setg(nullptr, nullptr, nullptr);
108 return pos;
111 public:
112 bool open(const char *filename) noexcept
114 mFile = PHYSFS_openRead(filename);
115 if(!mFile) return false;
116 return true;
119 PhysFSBuf() = default;
120 ~PhysFSBuf() override
122 PHYSFS_close(mFile);
123 mFile = nullptr;
127 // Inherit from std::istream to use our custom streambuf
128 class Stream final : public std::istream {
129 PhysFSBuf mStreamBuf;
131 public:
132 Stream(const char *filename) : std::istream(nullptr)
134 init(&mStreamBuf);
136 // Set the failbit if the file failed to open.
137 if(!mStreamBuf.open(filename)) clear(failbit);
141 // Inherit from alure::FileIOFactory to use our custom istream
142 class FileFactory final : public alure::FileIOFactory {
143 public:
144 FileFactory(const char *argv0)
146 // Need to initialize PhysFS before using it
147 if(PHYSFS_init(argv0) == 0)
148 throw std::runtime_error(alure::String("Failed to initialize PhysFS: ") +
149 PHYSFS_getLastError());
151 std::cout<< "Initialized PhysFS, supported archive formats:";
152 for(const PHYSFS_ArchiveInfo **i = PHYSFS_supportedArchiveTypes();*i != NULL;i++)
153 std::cout<< "\n "<<(*i)->extension<<": "<<(*i)->description;
154 std::cout<<std::endl;
156 ~FileFactory() override
158 PHYSFS_deinit();
161 alure::UniquePtr<std::istream> openFile(const alure::String &name) noexcept override
163 auto stream = alure::MakeUnique<Stream>(name.c_str());
164 if(stream->fail()) stream = nullptr;
165 return std::move(stream);
168 // A PhysFS-specific function to mount a new path to the virtual directory
169 // tree.
170 static bool Mount(const char *path, const char *mountPoint=nullptr, int append=0)
172 std::cout<< "Adding new file source "<<path;
173 if(mountPoint) std::cout<< " to "<<mountPoint;
174 std::cout<<"..."<<std::endl;
176 if(PHYSFS_mount(path, mountPoint, append) == 0)
178 std::cerr<< "Failed to add "<<path<<": "<<PHYSFS_getLastError() <<std::endl;
179 return false;
181 return true;
184 static void ListDirectory(std::string&& dir)
186 char **files = PHYSFS_enumerateFiles(dir.c_str());
187 for(int i = 0;files[i];i++)
189 std::string file = dir + files[i];
190 if(PHYSFS_isDirectory(file.c_str()))
191 ListDirectory(file+"/");
192 else
193 std::cout<<" "<<file<<"\n";
195 PHYSFS_freeList(files);
199 } // namespace
202 int main(int argc, char *argv[])
204 if(argc < 2)
206 std::cerr<< "Usage: "<<argv[0]<<" [-device <device name>]"
207 " -add <directory | archive> file_entries ..." <<std::endl;
208 return 1;
211 // Set our custom factory for file IO. From now on, all filenames given to
212 // Alure will be used with our custom factory.
213 alure::FileIOFactory::set(alure::MakeUnique<FileFactory>(argv[0]));
215 alure::DeviceManager devMgr = alure::DeviceManager::getInstance();
217 int fileidx = 1;
218 alure::Device dev;
219 if(argc > 3 && strcmp(argv[1], "-device") == 0)
221 fileidx = 3;
222 dev = devMgr.openPlayback(argv[2], std::nothrow);
223 if(!dev)
224 std::cerr<< "Failed to open \""<<argv[2]<<"\" - trying default" <<std::endl;
226 if(!dev)
227 dev = devMgr.openPlayback();
228 std::cout<< "Opened \""<<dev.getName()<<"\"" <<std::endl;
230 alure::Context ctx = dev.createContext();
231 alure::Context::MakeCurrent(ctx);
233 for(int i = fileidx;i < argc;i++)
235 if(alure::StringView("-add") == argv[i] && argc-i > 1)
237 FileFactory::Mount(argv[++i]);
238 std::cout<<"Available files:\n";
239 FileFactory::ListDirectory("/");
240 std::cout.flush();
241 continue;
244 alure::SharedPtr<alure::Decoder> decoder(ctx.createDecoder(argv[i]));
245 alure::Source source = ctx.createSource();
246 source.play(decoder, 12000, 4);
247 std::cout<< "Playing "<<argv[i]<<" ("<<alure::GetSampleTypeName(decoder->getSampleType())<<", "
248 <<alure::GetChannelConfigName(decoder->getChannelConfig())<<", "
249 <<decoder->getFrequency()<<"hz)" <<std::endl;
251 float invfreq = 1.0f / decoder->getFrequency();
252 while(source.isPlaying())
254 std::cout<< "\r "<<std::fixed<<std::setprecision(2)<<
255 source.getSecOffset().count()<<" / "<<(decoder->getLength()*invfreq);
256 std::cout.flush();
257 std::this_thread::sleep_for(std::chrono::milliseconds(25));
258 ctx.update();
260 std::cout<<std::endl;
262 source.release();
263 decoder.reset();
266 alure::Context::MakeCurrent(nullptr);
267 ctx.destroy();
268 dev.close();
270 return 0;