Merge pull request #40 from McSinyx/travis
[alure.git] / examples / alure-physfs.cpp
blobbf95c2faab38ac7e25aa2676ad3d2192507c68f4
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);
200 // Helper class+method to print the time with human-readable formatting.
201 struct PrettyTime {
202 alure::Seconds mTime;
204 inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs)
206 using hours = std::chrono::hours;
207 using minutes = std::chrono::minutes;
208 using seconds = std::chrono::seconds;
209 using centiseconds = std::chrono::duration<int64_t, std::ratio<1, 100>>;
210 using std::chrono::duration_cast;
212 centiseconds t = duration_cast<centiseconds>(rhs.mTime);
213 if(t.count() < 0)
215 os << '-';
216 t *= -1;
219 // Only handle up to hour formatting
220 if(t >= hours(1))
221 os << duration_cast<hours>(t).count() << 'h' << std::setfill('0') << std::setw(2)
222 << duration_cast<minutes>(t).count() << 'm';
223 else
224 os << duration_cast<minutes>(t).count() << 'm' << std::setfill('0');
225 os << std::setw(2) << (duration_cast<seconds>(t).count() % 60) << '.' << std::setw(2)
226 << (t.count() % 100) << 's' << std::setw(0) << std::setfill(' ');
227 return os;
230 } // namespace
233 int main(int argc, char *argv[])
235 alure::ArrayView<const char*> args(argv, argc);
237 if(args.size() < 2)
239 std::cerr<< "Usage: "<<args.size()<<" [-device <device name>] "
240 "-add <directory | archive> file_entries ..." <<std::endl;
241 return 1;
244 // Set our custom factory for file IO. From now on, all filenames given to
245 // Alure will be used with our custom factory.
246 alure::FileIOFactory::set(alure::MakeUnique<FileFactory>(args.front()));
247 args = args.slice(1);
249 alure::DeviceManager devMgr = alure::DeviceManager::getInstance();
251 alure::Device dev;
252 if(args.size() > 2 && args[0] == alure::StringView("-device"))
254 dev = devMgr.openPlayback(args[1], std::nothrow);
255 if(!dev) std::cerr<< "Failed to open \""<<args[1]<<"\" - trying default" <<std::endl;
256 args = args.slice(2);
258 if(!dev) dev = devMgr.openPlayback();
259 std::cout<< "Opened \""<<dev.getName()<<"\"" <<std::endl;
261 alure::Context ctx = dev.createContext();
262 alure::Context::MakeCurrent(ctx);
264 for(;!args.empty();args = args.slice(1))
266 if(args.size() > 1 && alure::StringView("-add") == args[0])
268 args = args.slice(1);
269 FileFactory::Mount(args.front());
270 std::cout<<"Available files:\n";
271 FileFactory::ListDirectory("/");
272 std::cout.flush();
273 continue;
276 alure::SharedPtr<alure::Decoder> decoder = ctx.createDecoder(args.front());
277 alure::Source source = ctx.createSource();
278 source.play(decoder, 12000, 4);
279 std::cout<< "Playing "<<args.front()<<" ("
280 << alure::GetSampleTypeName(decoder->getSampleType())<<", "
281 << alure::GetChannelConfigName(decoder->getChannelConfig())<<", "
282 << decoder->getFrequency()<<"hz)" <<std::endl;
284 double invfreq = 1.0 / decoder->getFrequency();
285 while(source.isPlaying())
287 std::cout<< "\r "<<PrettyTime{source.getSecOffset()}<<" / "<<
288 PrettyTime{alure::Seconds(decoder->getLength()*invfreq)};
289 std::cout.flush();
290 std::this_thread::sleep_for(std::chrono::milliseconds(25));
291 ctx.update();
293 std::cout<<std::endl;
295 source.destroy();
296 decoder.reset();
299 alure::Context::MakeCurrent(nullptr);
300 ctx.destroy();
301 dev.close();
303 return 0;