lovefs.lua 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. --[[------------------------------------
  2. LoveFS v1.0
  3. Pure Lua FileSystem Access
  4. Under the MIT license.
  5. copyright(c) 2016 Caldas Lopes aka linux-man
  6. --]]------------------------------------
  7. local ffi = require("ffi")
  8. ffi.cdef[[
  9. #pragma pack(push)
  10. #pragma pack(1)
  11. struct WIN32_FIND_DATAW {
  12. uint32_t dwFileWttributes;
  13. uint64_t ftCreationTime;
  14. uint64_t ftLastAccessTime;
  15. uint64_t ftLastWriteTime;
  16. uint32_t dwReserved[4];
  17. char cFileName[520];
  18. char cAlternateFileName[28];
  19. };
  20. #pragma pack(pop)
  21. void* FindFirstFileW(const char* pattern, struct WIN32_FIND_DATAW* fd);
  22. bool FindNextFileW(void* ff, struct WIN32_FIND_DATAW* fd);
  23. bool FindClose(void* ff);
  24. bool CopyFileW(const char* src, const char* dst, bool bFailIfExists);
  25. int GetLogicalDrives(void);
  26. int MultiByteToWideChar(unsigned int CodePage, uint32_t dwFlags, const char* lpMultiByteStr,
  27. int cbMultiByte, const char* lpWideCharStr, int cchWideChar);
  28. int WideCharToMultiByte(unsigned int CodePage, uint32_t dwFlags, const char* lpWideCharStr,
  29. int cchWideChar, const char* lpMultiByteStr, int cchMultiByte,
  30. const char* default, int* used);
  31. ]]
  32. ffi.cdef[[
  33. struct dirent {
  34. unsigned long d_ino; /* inode number */
  35. unsigned long d_off; /* not an offset */
  36. unsigned short d_reclen; /* length of this record */
  37. unsigned char d_type; /* type of file; not supported by all filesystem types */
  38. char d_name[256]; /* filename */
  39. };
  40. struct DIR *opendir(const char *name);
  41. struct dirent *readdir(struct DIR *dirstream);
  42. int closedir (struct DIR *dirstream);
  43. ]]
  44. local WIN32_FIND_DATA = ffi.typeof('struct WIN32_FIND_DATAW')
  45. local INVALID_HANDLE = ffi.cast('void*', -1)
  46. local function u2w(str, code)
  47. local size = ffi.C.MultiByteToWideChar(code or 65001, 0, str, #str, nil, 0)
  48. local buf = ffi.new("char[?]", size * 2 + 2)
  49. ffi.C.MultiByteToWideChar(code or 65001, 0, str, #str, buf, size * 2)
  50. return buf
  51. end
  52. local function w2u(wstr, code)
  53. local size = ffi.C.WideCharToMultiByte(code or 65001, 0, wstr, -1, nil, 0, nil, nil)
  54. local buf = ffi.new("char[?]", size + 1)
  55. size = ffi.C.WideCharToMultiByte(code or 65001, 0, wstr, -1, buf, size, nil, nil)
  56. return ffi.string(buf)
  57. end
  58. local function join(tb1, tb2)
  59. local tb = {}
  60. for _,v in ipairs(tb1) do table.insert(tb, v) end
  61. for _,v in ipairs(tb2) do table.insert(tb, v) end
  62. return tb
  63. end
  64. local function findValue(tb, value)
  65. for _, v in ipairs(tb) do
  66. if v == value then return true end
  67. end
  68. return false
  69. end
  70. local function removeValue(tb, value)
  71. for n = #tb, 1, -1 do
  72. if tb[n] == value then table.remove(tb, n) end
  73. end
  74. end
  75. filesystem = {}
  76. filesystem.__index = filesystem
  77. function filesystem:absPath(path)
  78. if path == '.' then path = self.current end
  79. if (path:sub(1,2) == '.'..self.sep) then path = self.current..path:sub(2) end
  80. if self.win then
  81. if not (path:sub(2,2) == ':') then path = self.current..self.sep..path end
  82. path = path:gsub('/', self.sep)
  83. else
  84. if not (path:sub(1,1) == '/') then path = self.current..self.sep..path end
  85. path = path:gsub('\\', self.sep)
  86. end
  87. path = path:gsub(self.sep..self.sep, self.sep)
  88. if #path > 1 and path:sub(-1) == self.sep then path = path:sub(1, -2) end
  89. return path
  90. end
  91. function filesystem:ls(dir)
  92. dir = dir or self.current
  93. dir = self:absPath(dir)
  94. local tDirs = {}
  95. local tFiles = {}
  96. if self.win then
  97. local fd = ffi.new(WIN32_FIND_DATA)
  98. local hFile = ffi.C.FindFirstFileW(u2w(dir..'\\*'), fd)
  99. ffi.gc(hFile, ffi.C.FindClose)
  100. if hFile ~= INVALID_HANDLE then
  101. repeat
  102. local fn = w2u(fd.cFileName)
  103. if fd.dwFileWttributes == 16 or fd.dwFileWttributes == 17 or fd.dwFileWttributes == 8210 then
  104. table.insert(tDirs, fn)
  105. elseif fd.dwFileWttributes == 32 then
  106. table.insert(tFiles, fn)
  107. end
  108. until not ffi.C.FindNextFileW(hFile, fd)
  109. end
  110. ffi.C.FindClose(ffi.gc(hFile, nil))
  111. else
  112. local hDir = ffi.C.opendir(dir)
  113. ffi.gc(hDir, ffi.C.closedir)
  114. if hDir ~= nil then
  115. local dirent = ffi.C.readdir(hDir)
  116. while dirent ~= nil do
  117. local fn = ffi.string(dirent.d_name)
  118. if dirent.d_type == 4 then
  119. table.insert(tDirs, fn)
  120. elseif dirent.d_type == 8 then
  121. table.insert(tFiles, fn)
  122. end
  123. dirent = ffi.C.readdir(hDir)
  124. end
  125. end
  126. ffi.C.closedir(ffi.gc(hDir, nil))
  127. end
  128. if #tDirs == 0 then return false end
  129. removeValue(tDirs, '.')
  130. removeValue(tDirs, '..')
  131. table.sort(tDirs)
  132. if self.filter then
  133. for n = #tFiles, 1, -1 do
  134. local ext = tFiles[n]:match('[^.]+$')
  135. local valid = false
  136. for _, v in ipairs(self.filter) do
  137. valid = valid or (ext == v)
  138. end
  139. if not (valid) then table.remove(tFiles, n) end
  140. end
  141. end
  142. table.sort(tFiles)
  143. return dir, tDirs, tFiles, join(tDirs, tFiles)
  144. end
  145. function filesystem:dir(dir)
  146. return self:ls(dir)
  147. end
  148. function filesystem:cd(dir)
  149. current, tDirs, tFiles, tAll = self:ls(dir)
  150. if current then
  151. self.current = current
  152. self.dirs = tDirs
  153. self.files = tFiles
  154. self.all = tAll
  155. return true
  156. end
  157. return false
  158. end
  159. function filesystem:up()
  160. self:cd(self.current:match('(.*'..self.sep..')'))
  161. end
  162. function filesystem:exists(path)
  163. path = self:absPath(path)
  164. dir = self:absPath(path:match('(.*'..self.sep..')'))
  165. name = path:match('[^'..self.sep..']+$')
  166. --ext = name:match('[^.]+$')
  167. dir, dirs, files, all = self:ls(dir)
  168. if dir then
  169. return findValue(all, name), findValue(dirs, name), findValue(files, name)
  170. end
  171. return false
  172. end
  173. function filesystem:isDirectory(path)
  174. exists, isDir = self:exists(path)
  175. if exists then return isDir
  176. else return false end
  177. end
  178. function filesystem:isFile(path)
  179. exists, isDir, isFile = self:exists(path)
  180. if exists then return isFile
  181. else return false end
  182. end
  183. function filesystem:updateDrives()
  184. drives = {}
  185. if self.win then
  186. aCode = string.byte('A')
  187. drv = ffi.C.GetLogicalDrives()
  188. for n = 0, 15, 1 do
  189. if not(drv % 2) then table.insert(drives, string.char(aCode + n)..':\\') end
  190. drv = math.floor(drv / 2)
  191. end
  192. elseif ffi.os == 'Linux' then
  193. dir, dirs = self:ls('/media')
  194. if dir then
  195. for n, d in ipairs(dirs) do dirs[n] = '/media/'..dirs[n] end
  196. drives = dirs
  197. end
  198. table.insert(drives, 1, '/')
  199. else
  200. dir, dirs = self:ls('/Volumes')
  201. if dir then
  202. for n, d in ipairs(dirs) do dirs[n] = '/Volumes/'..dirs[n] end
  203. drives = dirs
  204. end
  205. table.insert(drives, 1, '/')
  206. end
  207. self.drives = drives
  208. end
  209. function filesystem:copy(source, dest)
  210. if self.win then
  211. ffi.C.CopyFileW(u2w(source), u2w(dest), false)
  212. else
  213. local inp = assert(io.open(source, "rb"))
  214. local out = assert(io.open(dest, "wb"))
  215. local data = inp:read("*all")
  216. out:write(data)
  217. assert(out:close())
  218. end
  219. end
  220. function filesystem:loadImage(source)
  221. source = source or self.selectedFile
  222. self.selectedFile = nil
  223. source = self:absPath(source)
  224. love.filesystem.createDirectory('lovefs_temp')
  225. self:copy(source, love.filesystem.getSaveDirectory()..'/lovefs_temp/temp.file')
  226. return love.graphics.newImage('lovefs_temp/temp.file')
  227. end
  228. function filesystem:loadSource(source)
  229. source = source or self.selectedFile
  230. self.selectedFile = nil
  231. source = self:absPath(source)
  232. love.filesystem.createDirectory('lovefs_temp')
  233. --local name = path:match('[^'..self.sep..']+$')
  234. local ext = source:match('[^'..self.sep..']+$'):match('[^.]+$')
  235. self:copy(source, love.filesystem.getSaveDirectory()..'/lovefs_temp/temp.'..ext)
  236. return love.audio.newSource('lovefs_temp/temp.'..ext)
  237. end
  238. function filesystem:loadFont(size, source)
  239. source = source or self.selectedFile
  240. self.selectedFile = nil
  241. source = self:absPath(source)
  242. love.filesystem.createDirectory('lovefs_temp')
  243. self:copy(source, love.filesystem.getSaveDirectory()..'/lovefs_temp/temp.file')
  244. return love.graphics.newFont('lovefs_temp/temp.file', size)
  245. end
  246. function filesystem:saveImage(img, dest)
  247. if not pcall(function() love.graphics.newCanvas(img:getWidth(), img:getHeight()) end) then return false end
  248. dest = dest or self.selectedFile
  249. dest = self:absPath(dest)
  250. self.selectedFile = nil
  251. love.filesystem.createDirectory('lovefs_temp')
  252. love.filesystem.remove('lovefs_temp/temp.file')
  253. love.graphics.setColor(255, 255, 255)
  254. local canvas = love.graphics.newCanvas(img:getWidth(), img:getHeight())
  255. love.graphics.setCanvas(canvas)
  256. love.graphics.draw(img, 0, 0)
  257. local id = canvas:newImageData()
  258. id:encode('png', 'lovefs_temp/temp.file')
  259. love.graphics.setCanvas()
  260. self:copy(love.filesystem.getSaveDirectory()..'/lovefs_temp/temp.file', dest)
  261. return true
  262. end
  263. function lovefs(dir)
  264. local temp = {}
  265. setmetatable(temp, filesystem)
  266. temp.win = ffi.os == "Windows"
  267. temp.selectedFile = nil
  268. temp.filter = nil
  269. temp.home = love.filesystem.getUserDirectory()
  270. temp.current = temp.home
  271. temp.sep = package.config:sub(1,1)
  272. dir = dir or temp.home
  273. if not temp:cd(dir) then
  274. if not temp:cd(temp.home) then
  275. if temp.win then temp:cd('c:'..sep)
  276. else temp:cd(sep) end
  277. end
  278. end
  279. temp:updateDrives()
  280. return temp
  281. end