Skip to content

Commit ac6d8e6

Browse files
committed
Added a basic filesystem module
1 parent 9f874c8 commit ac6d8e6

File tree

6 files changed

+652
-0
lines changed

6 files changed

+652
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
filesystem module
2+
==================
3+
4+
.. automodule:: filesystem
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# ../filesystem.py
2+
3+
"""Provides access to engine's filesystem interface."""
4+
5+
# =============================================================================
6+
# >> FORWARD IMPORTS
7+
# =============================================================================
8+
# Source.Python Imports
9+
# Filesystem
10+
from _filesystem import is_vpk_file
11+
from _filesystem import SeekType
12+
from _filesystem import SourceFile
13+
14+
15+
# =============================================================================
16+
# >> IMPORTS
17+
# =============================================================================
18+
# Python
19+
# Os
20+
import os
21+
# tempfile
22+
import tempfile
23+
# Contextlib
24+
from contextlib import contextmanager
25+
26+
27+
# =============================================================================
28+
# >> ALL DECLARATION
29+
# =============================================================================
30+
__all__ = ('is_vpk_file',
31+
'source_file',
32+
'SeekType',
33+
'SourceFile',
34+
)
35+
36+
37+
# =============================================================================
38+
# >> FUNCTIONS
39+
# =============================================================================
40+
@contextmanager
41+
def source_file(file_path):
42+
"""Ensures that the given file has a visible name on the file system.
43+
44+
.. note:: If the file was packed in a VPK archive, it gets copied to a
45+
temporary file. Thus, modifying the file has no effect on the original
46+
file.
47+
48+
Example:
49+
50+
.. code:: python
51+
52+
with source_file('credits.txt') as file_path:
53+
print('Visible path:', file_path)
54+
with open(file_path) as f:
55+
print('Content:', f.read())
56+
"""
57+
if is_vpk_file(file_path):
58+
temp_file = _get_temp_file_name()
59+
60+
# Copy the file to the real file system
61+
f = SourceFile.open(file_path, 'rb')
62+
f.save(temp_file)
63+
f.close()
64+
else:
65+
temp_file = file_path
66+
67+
try:
68+
yield temp_file
69+
finally:
70+
if temp_file != file_path:
71+
os.remove(temp_file)
72+
73+
74+
# =============================================================================
75+
# >> FUNCTIONS
76+
# =============================================================================
77+
def _get_temp_file_name():
78+
"""Generate a temporary file name with an absolute path."""
79+
return (tempfile._get_default_tempdir()
80+
+ os.sep
81+
+ next(tempfile._get_candidate_names()))

src/CMakeLists.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,18 @@ Set(SOURCEPYTHON_EVENTS_MODULE_SOURCES
230230
core/modules/events/events_wrap.cpp
231231
)
232232

233+
# ------------------------------------------------------------------
234+
# Filesystem module.
235+
# ------------------------------------------------------------------
236+
Set(SOURCEPYTHON_FILESYSTEM_MODULE_HEADERS
237+
core/modules/filesystem/filesystem.h
238+
)
239+
240+
Set(SOURCEPYTHON_FILESYSTEM_MODULE_SOURCES
241+
core/modules/filesystem/filesystem.cpp
242+
core/modules/filesystem/filesystem_wrap.cpp
243+
)
244+
233245
# ------------------------------------------------------------------
234246
# Filters module.
235247
# ------------------------------------------------------------------
@@ -484,6 +496,9 @@ Set(SOURCEPYTHON_MODULE_FILES
484496
${SOURCEPYTHON_FILTERS_MODULE_HEADERS}
485497
${SOURCEPYTHON_FILTERS_MODULE_SOURCES}
486498

499+
${SOURCEPYTHON_FILESYSTEM_MODULE_HEADERS}
500+
${SOURCEPYTHON_FILESYSTEM_MODULE_SOURCES}
501+
487502
${SOURCEPYTHON_STEAM_MODULE_HEADERS}
488503
${SOURCEPYTHON_STEAM_MODULE_SOURCES}
489504

@@ -523,6 +538,7 @@ Source_Group("Header Files\\Modules\\Messages" FILES ${SOURCEPYTHO
523538
Source_Group("Header Files\\Modules\\NetChannel" FILES ${SOURCEPYTHON_NET_CHANNEL_MODULE_HEADERS})
524539
Source_Group("Header Files\\Modules\\Physics" FILES ${SOURCEPYTHON_PHYSICS_MODULE_HEADERS})
525540
Source_Group("Header Files\\Modules\\Players" FILES ${SOURCEPYTHON_PLAYERS_MODULE_HEADERS})
541+
Source_Group("Header Files\\Modules\\Filesystem" FILES ${SOURCEPYTHON_FILESYSTEM_MODULE_HEADERS})
526542
Source_Group("Header Files\\Modules\\Filters" FILES ${SOURCEPYTHON_FILTERS_MODULE_HEADERS})
527543
Source_Group("Header Files\\Modules\\StringTables" FILES ${SOURCEPYTHON_STRINGTABLES_MODULE_HEADERS})
528544
Source_Group("Header Files\\Modules\\Steam" FILES ${SOURCEPYTHON_STEAM_MODULE_HEADERS})
@@ -551,6 +567,7 @@ Source_Group("Source Files\\Modules\\Messages" FILES ${SOURCEPYTHO
551567
Source_Group("Source Files\\Modules\\NetChannel" FILES ${SOURCEPYTHON_NET_CHANNEL_MODULE_SOURCES})
552568
Source_Group("Source Files\\Modules\\Physics" FILES ${SOURCEPYTHON_PHYSICS_MODULE_SOURCES})
553569
Source_Group("Source Files\\Modules\\Players" FILES ${SOURCEPYTHON_PLAYERS_MODULE_SOURCES})
570+
Source_Group("Source Files\\Modules\\Filesystem" FILES ${SOURCEPYTHON_FILESYSTEM_MODULE_SOURCES})
554571
Source_Group("Source Files\\Modules\\Filters" FILES ${SOURCEPYTHON_FILTERS_MODULE_SOURCES})
555572
Source_Group("Source Files\\Modules\\Steam" FILES ${SOURCEPYTHON_STEAM_MODULE_SOURCES})
556573
Source_Group("Source Files\\Modules\\StringTables" FILES ${SOURCEPYTHON_STRINGTABLES_MODULE_SOURCES})
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
/**
2+
* =============================================================================
3+
* Source Python
4+
* Copyright (C) 2012-2016 Source Python Development Team. All rights reserved.
5+
* =============================================================================
6+
*
7+
* This program is free software; you can redistribute it and/or modify it under
8+
* the terms of the GNU General Public License, version 3.0, as published by the
9+
* Free Software Foundation.
10+
*
11+
* This program is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14+
* details.
15+
*
16+
* You should have received a copy of the GNU General Public License along with
17+
* this program. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
* As a special exception, the Source Python Team gives you permission
20+
* to link the code of this program (as well as its derivative works) to
21+
* "Half-Life 2," the "Source Engine," and any Game MODs that run on software
22+
* by the Valve Corporation. You must obey the GNU General Public License in
23+
* all respects for all other code used. Additionally, the Source.Python
24+
* Development Team grants this exception to all derivative works.
25+
*/
26+
27+
//---------------------------------------------------------------------------------
28+
// Includes.
29+
//---------------------------------------------------------------------------------
30+
// Source.Python
31+
#include "utilities/wrap_macros.h"
32+
#include "filesystem.h"
33+
34+
35+
//---------------------------------------------------------------------------------
36+
// External variables.
37+
//---------------------------------------------------------------------------------
38+
extern IFileSystem* filesystem;
39+
40+
41+
//---------------------------------------------------------------------------------
42+
// Functions
43+
//---------------------------------------------------------------------------------
44+
bool IsVPKFile(const char* file_path)
45+
{
46+
struct stat buf;
47+
return stat(file_path, &buf) != 0 && filesystem->FileExists(file_path);
48+
}
49+
50+
51+
//---------------------------------------------------------------------------------
52+
// SourceFile
53+
//---------------------------------------------------------------------------------
54+
SourceFile::SourceFile(FileHandle_t handle)
55+
{
56+
if (handle == FILESYSTEM_INVALID_HANDLE)
57+
BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Handle is invalid.")
58+
59+
m_handle = handle;
60+
m_mode = NULL;
61+
}
62+
63+
SourceFile::SourceFile(FileHandle_t handle, const char* mode)
64+
{
65+
if (handle == FILESYSTEM_INVALID_HANDLE)
66+
BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Handle is invalid.")
67+
68+
m_handle = handle;
69+
m_mode = strdup(mode);
70+
}
71+
72+
SourceFile::~SourceFile()
73+
{
74+
if (m_mode)
75+
free(m_mode);
76+
}
77+
78+
void SourceFile::Save(const char* file_path)
79+
{
80+
CheckClosed();
81+
FileHandle_t handle = filesystem->Open(file_path, "wb");
82+
if (handle == FILESYSTEM_INVALID_HANDLE)
83+
BOOST_RAISE_EXCEPTION(PyExc_IOError, "Failed to open file: %s", file_path)
84+
85+
int size = Size();
86+
void* buffer = new char[size];
87+
int bytesRead = filesystem->Read(buffer, size, m_handle);
88+
89+
filesystem->Write(buffer, bytesRead, handle);
90+
filesystem->Close(handle);
91+
92+
delete buffer;
93+
}
94+
95+
PyObject* SourceFile::Read(int size)
96+
{
97+
CheckClosed();
98+
if (size == -1) {
99+
size = filesystem->Size(m_handle);
100+
}
101+
102+
void* pOutput = new char[size+1];
103+
int bytesRead = filesystem->Read(pOutput, size, m_handle);
104+
return ConsumeBuffer(pOutput, bytesRead);
105+
}
106+
107+
object SourceFile::Readline(int size)
108+
{
109+
CheckClosed();
110+
// TODO
111+
BOOST_RAISE_EXCEPTION(PyExc_NotImplementedError, "Not implemented yet.")
112+
return object();
113+
}
114+
115+
list SourceFile::Readlines(int hint)
116+
{
117+
CheckClosed();
118+
// TODO
119+
BOOST_RAISE_EXCEPTION(PyExc_NotImplementedError, "Not implemented yet.")
120+
return list();
121+
}
122+
123+
PyObject* SourceFile::ConsumeBuffer(void* buffer, int bytesRead)
124+
{
125+
PyObject* result = NULL;
126+
if (IsBinaryMode()) {
127+
result = PyBytes_FromStringAndSize((char*) buffer, bytesRead);
128+
}
129+
else {
130+
result = PyUnicode_FromStringAndSize((char*) buffer, bytesRead);
131+
}
132+
133+
delete buffer;
134+
return result;
135+
}
136+
137+
int SourceFile::Write(object data)
138+
{
139+
CheckClosed();
140+
// TODO
141+
BOOST_RAISE_EXCEPTION(PyExc_NotImplementedError, "Not implemented yet.")
142+
return 0;
143+
}
144+
145+
void SourceFile::Writelines(list lines)
146+
{
147+
CheckClosed();
148+
for (int i=0; i < len(lines); ++i) {
149+
Writeline(lines[i]);
150+
}
151+
}
152+
153+
bool SourceFile::IsBinaryMode()
154+
{
155+
return !m_mode || strchr(m_mode, 'b');
156+
}
157+
158+
void SourceFile::Writeline(object line)
159+
{
160+
// TODO
161+
BOOST_RAISE_EXCEPTION(PyExc_NotImplementedError, "Not implemented yet.")
162+
}
163+
164+
int SourceFile::Truncate(int size)
165+
{
166+
// TODO
167+
BOOST_RAISE_EXCEPTION(PyExc_NotImplementedError, "Not implemented yet.")
168+
return 0;
169+
}
170+
171+
void SourceFile::Close()
172+
{
173+
if (m_handle != FILESYSTEM_INVALID_HANDLE) {
174+
filesystem->Close(m_handle);
175+
m_handle = NULL;
176+
}
177+
}
178+
179+
void SourceFile::Seek(int pos, FileSystemSeek_t seekType)
180+
{
181+
CheckClosed();
182+
filesystem->Seek(m_handle, pos, seekType);
183+
}
184+
185+
unsigned int SourceFile::Tell()
186+
{
187+
CheckClosed();
188+
return filesystem->Tell(m_handle);
189+
}
190+
191+
unsigned int SourceFile::Size()
192+
{
193+
CheckClosed();
194+
return filesystem->Size(m_handle);
195+
}
196+
197+
void SourceFile::Flush()
198+
{
199+
CheckClosed();
200+
filesystem->Flush(m_handle);
201+
}
202+
203+
FileHandle_t SourceFile::GetHandle()
204+
{
205+
return m_handle;
206+
}
207+
208+
bool SourceFile::IsAtty()
209+
{
210+
CheckClosed();
211+
return false;
212+
}
213+
214+
bool SourceFile::Seekable()
215+
{
216+
CheckClosed();
217+
return true;
218+
}
219+
220+
int SourceFile::Fileno()
221+
{
222+
CheckClosed();
223+
BOOST_RAISE_EXCEPTION(PyExc_IOError, "File does not use a file descriptor.")
224+
return 0;
225+
}
226+
227+
bool SourceFile::Closed()
228+
{
229+
return m_handle == NULL;
230+
}
231+
232+
bool SourceFile::Readable()
233+
{
234+
return m_mode != NULL && (
235+
strchr(m_mode, 'r')
236+
|| strchr(m_mode, '+')
237+
);
238+
}
239+
240+
bool SourceFile::Writeable()
241+
{
242+
return m_mode != NULL && (
243+
strchr(m_mode, 'w')
244+
|| strchr(m_mode, 'x')
245+
|| strchr(m_mode, 'a')
246+
|| strchr(m_mode, '+')
247+
);
248+
}
249+
250+
void SourceFile::CheckClosed()
251+
{
252+
if (m_handle == FILESYSTEM_INVALID_HANDLE)
253+
BOOST_RAISE_EXCEPTION(PyExc_ValueError, "File is already closed.")
254+
}
255+
256+
SourceFile* SourceFile::Open(const char* pFileName, const char* pMode, const char* pathID)
257+
{
258+
FileHandle_t handle = filesystem->Open(pFileName, pMode, pathID);
259+
if (handle == FILESYSTEM_INVALID_HANDLE)
260+
BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unable to open file: %s", pFileName)
261+
262+
return new SourceFile(handle, pMode);
263+
}

0 commit comments

Comments
 (0)