August 2009

Uwaga! Informacje na tej stronie mają ponad 5 lat. Nadal je udostępniam, ale prawdopodobnie nie odzwierciedlają one mojej aktualnej wiedzy ani przekonań.

# My Theory of Communication

21:21
Mon
31
Aug 2009

Today is the Blog Day. I'm not going to link the blogday.org website or apply their advices about recommending other blogs, but instead I want to present my little theory explaining what's the place of blogging and tweeting among other forms of communication.

I think that communication and knowledge sharing can be seen as a spectrum, where each form has its place between two extremes - first is writing/reading a book and the second is a casual real-life chat. I hope this diagram explains everything:

Theory of Communication

So if you only like to read books and you disregard communication via the Internet and casual chatting or the opposite - you are used to only write and read short messages to your friends and you never read books or write long letters - then think for a while whether your way of communication is really better than others or maybe your beliefs make you losing some of the opporunities from this full spectrum that you may enjoy...

Comments | #philosophy #web Share

# Calculating Linear and Quadratic Equation Coefficients

21:35
Sun
30
Aug 2009

Some time ago I've written about formulas to calculate coefficients of linear and quadratic equation [pl] when having 2 or 3 given points (x,y). Yesterday I suddenly noticed that my code needs to do it every frame so I need functions to calculate these coefficients. Below you can find the code of my functions. They are templates, so they work with many types including float, double, as well as vectors of any dimmension and other types which have addition, subtraction and assignment operators, as well as multiplication and divistion by float scalar.

Read full entry > | Comments | #rendering #math #algorithms Share

# Disadventages of Nested Classes and Anonymous Namespaces

21:29
Fri
28
Aug 2009

Some of techniques I have been using to arrange my C++ code were anonymous namespaces and nested classes. I liked them till I've discovered their big disadventage - they are not compatible with forward declarations. Here is an example. Let's say we have two classes - Problematic and Second - and we want to place them in separate HPP+CPP files, while Second will use Problematic. The obvious solution is to define them like this:

////// Problematic.hpp

class Problematic
{
};

////// Second.hpp

#include "Problematic.hpp"

class Second
{
  Problematic *obj;
  void CreateObj();
};

We all know that the fewer inclues the better (especially we should avoid including headers in other headers), so it's good to only declare Problematic class in Second header and include its definition in CPP file. We can do it as Problematic class is used in the Second header only by pointer (references are also OK) and not by value.

////// Second.hpp

class Problematic;

class Second
{
  Problematic *obj; // Here class declaration is enough
  void CreateObj();
};

////// Second.cpp

#include "Second.hpp"
#include "Problematic.hpp" // Included here, not in HPP

void Second::CreateObj()
{
  obj = new Problematic(); // Here full class definition must be known
}

Read full entry > | Comments | #c++ Share

# Reflect and Refract Functions

22:55
Mon
24
Aug 2009

Every game developer must have some knowledge about maths and especially geometry. Math library for game developers always contains structures like vector, matrix and functions like vector normalization or matrix inversion. But high level shader languages, designed to do geometrical computations, have many more interesting intrinsic functions. Some of more complex ones are reflect and refract, which calculate vector reflection and refraction in relation to the normal of a surface. How to implement them in C++?

Reflect and Refract functions - vector reflection and refraction

Reflection works according the simple rule that "angle of incidence equals angle of reflection". Although there are no angles in the formula - all the necessary work is done by dot product.

void Reflect(VEC3 &out, const VEC3 &incidentVec, const VEC3 &normal)
{
  out = incidentVec - 2.f * Dot(incidentVec, normal) * normal;
}

For reflect function, the formula is given in th HLSL documentation, but for refract function it is not. It took me some time to find the implementation, but finally I've found it in the GLSL Specification (thanks KriS!). So here is the code converted to C++. Eta is the refraction index (e.g. 1.5f).

inline void Refract(
  VEC3 &out, const VEC3 &incidentVec, const VEC3 &normal, float eta)
{
  float N_dot_I = Dot(normal, incidentVec);
  float k = 1.f - eta * eta * (1.f - N_dot_I * N_dot_I);
  if (k < 0.f)
    out = VEC3(0.f, 0.f, 0.f);
  else
    out = eta * incidentVec - (eta * N_dot_I + sqrtf(k)) * N;
}

These functions are part of my library: CommonLib.

Comments | #math #rendering Share

# About the Guy Who Made Love

16:08
Sat
22
Aug 2009

Today I want to talk a bit about what's the dream of almost every passionate game developer. It seems very hard or almost impossible to achieve, but younger amateurs still hope that they will manage to do it someday. Of course I'm talking about making a 3D MMO game.

As it turned out for me today (thanks for the link KriS!), it actually IS possible. I'm talking about the game called Love written entirely by one person - Eskil Steenberg. He have coded all the software from modeling tools through network protocol and renderer until game mechanics. To see it working I recommend watching these videos. The game is powered by his engine called Quel Solaar, which is actually available for download.

I must admit I haven't been impressed so much for a long time. I suppose the amount of time and passion that had to be put into this code is enormous. Graphical style and gameplay, as well as the user interface of his tools are very unusual and surprising. And all of this is made by one guy...

I recommend watching his lecture from this year's Assembly party titled Developing the technology behind "Love". You can see many technical details and if you don't want to watch the entire one hour video, at least watch the beginning (where he talks about his "smarter way of doing things") and the ending (where he expresses his thoughts about the value of good tools).

BTW it's also nice to watch new videos from GC 2009 of the CryEngine 3. "What you see is what you play" and instant asset update (including textures) - that's how good game editor should look like :)

Comments | #software engineering #networking #web #rendering #games #events #engine #demoscene #philosophy #tools Share

# Beautiful Wallpapers on Flickr

20:57
Fri
21
Aug 2009

Yesterday I've found Flickr profile of Reciprocity - Alan Jaras - a research scientist and microscopist playing with photography of caustics and other light effects. Of course it's all the matter of taste, but for me his photos are really amazing. They look so abstract and so natural at the same time. Just look at the galleries Taming Light, Bending Light and Twisting Light. I think it wouldn't be easy to procedurally generate such images. Also check out his Favourites for more unusual images.

Comments | #graphics #web Share

# Source Code and Ray Tracer

21:11
Wed
19
Aug 2009

As some of you are interested in seeing source code of my 2D Software Renderer, I'll fullfill this request, although I don't think there is anything special about this code. Here is the file: SoftRender01.zip and here is a brief description of this project:

BTW, I've started coding a raytracer. Of course the first primitive I support is a sphere and first effects I've coded are shadows and reflections. Ray tracing is not magic. It's just like "foreach pixel, foreach object, render", while rasterization is the opposite: "foreach object, foreach pixel, render". But It's quite fascinating that raytracer suppors directly some of effects that require special preprocessing in standard rasterization (like preparing shadow map and enviromental map in case of my effects).

Raytracer

Comments | #productions #rendering #algorithms Share

# My 2D Software Renderer

20:45
Tue
18
Aug 2009

I've been recently working on rendering 2D primitives in pure software, without any hardware acceleration. Sure It's about foundations of computer graphics, but I did it just for fun. External libraries I've used are DevIL to read bitmap files (like TGA) and FreeType to read and render font files (like TTF). I compose graphics in my software renderer to my "surface" in RAM and finally present it on the window with SetDIBitsToDevice function. As you can see I've managed to reach real-time performance, despite I haven't done any advanced optimizations like MMX, SSE or multithreading.

Software Renderer 2D

My renderer supports drawing rectangles, lines, polygons, circles, ellipses, triangles, blitting bitmaps, as well as scissor test (clipping to rect), alpha test and alpha blending. Here is how the renderer interface looks like:

Read full entry > | Comments | #rendering #productions Share

# My Way of Implementing Streams #3

23:00
Mon
17
Aug 2009

I've decribed my way of implementing base classes for binary data streams in my recent posts. This time I want to show some of my specific stream classes. The main one is of course FileStream, which gives access to disk files. If opens file in the constructor (throws exception if opening fails) and of course closes it in the destructor.

class FileStream : public SeekableStream
{
public:
  FileStream(const std::string &FileName, FILE_MODE FileMode, bool Lock = true);
  ...

I use standard C functions on Linux (fopen, fclose, fread, fwrite) and WinAPI functions on Windows (CreateFile, CloseHandle, ReadFile, WriteFile). These two API are very different. WinAPI gives much more flexibility, because using CreateFile you can choose separately whether you want write and/or read access, whether to protect file from opening by another application, whether clear the file or access existing data, whether create new file or fail if it doesn't exist etc. But still I've decided to define my FILE_MODE enum in the C-like style (where you pass mode as string like "wb", "rb+" etc.) together with comments explaining details of each mode.

enum FILE_MODE
{
  // write: yes, read: no, initial pos: 0
  // not exists: create, exists: clear
  FM_WRITE,
  // write: yes, read: yes, initial pos: 0
  // not exists: create, exists: clear
  FM_WRITE_PLUS,
  // write: no, read: yes, initial pos: 0
  // not exists: error, exists: open
  FM_READ,
  // write: yes, read: yes, initial pos: 0
  // not exists: error, exists: open
  FM_READ_PLUS,
  // write: yes, read: no, initial pos: end
  // not exists: create, exists: open
  FM_APPEND,
  // write: yes, read: yes, initial pos: end
  // not exists: create, exists: open
  FM_APPEND_PLUS,
};

There is one issue in the Linux implementation though. Documentation says that seeking doesn't work when file is opened in "a" or "a+" mode. WinAPI doesn't have such problems :)

Read full entry > | Comments | #c++ Share

# Reg__ @ Twitter

13:09
Sun
16
Aug 2009

I've created Twitter account - http://twitter.com/Reg__. I don't know what will I write there yet but you cannot blame me for joining this popular social networking portal if I find profiles of people like Maciej Sinilo or Josh Petrie there.

Comments | #web Share

# My Way of Implementing Streams #2

19:45
Sat
15
Aug 2009

In my previous post My Way of Implementing Streams #1 I've revealed what's my attitude towards stream class hierarchy. I want to explore this subject further by looking into details of my code. Let's start with writing and reading a single value to/from a stream. I know that processing bigger chunk of data with single call is more optimal, but sometimes we want to save a single number, boolean value, vector or whatever. We traditionally do it by passing address of the value and its size, just like that:

fwrite(&value, sizeof(value), 1, file);

But in C++ we can automate this by defining a template method, which I like to call "WriteEx":

template <typename T>
void WriteEx(const T &x)
{
  return Write(&x, sizeof(x));
}

template <typename T>
void ReadEx(T *x)
{
  MustRead(x, sizeof(*x));
}

Now I can read and write single values from/to my streams much easier:

stream.WriteEx(value);

Character strings are special kinds of values because they have variable size. Traditional C approach is to keep strings NULL-terminated, but I don't like this. I prefer to remember string length together with characters because: 1. I can do stream read and write with just two calls (one for length and one for data) instead of reading/writing byte after byte until I meet '\0', 2. I know how many bytes to allocate for character buffer when reading a string, 3. string can contain any characters including 0. On the other hand, I have to decide how many bytes do I want to use as string length, thus limiting maximum string length to 255 B (1 B), 64 KB (2 B) or 4 GB (4 B). To leave this choice to the user, I've defined many functions for reading and writing string to my streams:

// Write string preceded by length as 1 B
void WriteString1(const string &s);
// Write string preceded by length as 2 B
void WriteString2(const string &s);
// Write string preceded by length as 4 B
void WriteString4(const string &s);
// Write only string content
void WriteStringF(const string &s);

// Read string preceded by length as 1 B
void ReadString1(string *s);
// Read string preceded by length as 2 B
void ReadString2(string *s);
// Read string preceded by length as 4 B
void ReadString4(string *s);
// Read only string content
void ReadStringF(string *s, size_t NumChars);
// This one does not need a comment :)
void ReadStringToEnd(string *s);

Another thing I want to mention is the concept of skipping bytes while reading from a stream. Sometimes we want to ignore some bytes, so I've defined Skip method for this purpose.

virtual size_t Skip(size_t MaxLength);
void MustSkip(size_t Length);

Interesting here is that I provide default implementation in my both base stream classes. Default implementation in Stream class just reads given number of bytes to an internal buffer and ignores it:

size_t Stream::Skip(size_t MaxLength)
{
  if (MaxLength == 0) return 0;
  char Buf[BUFFER_SIZE];
  uint BlockSize, ReadSize, Sum = 0;
  while (MaxLength > 0)
  {
    BlockSize = std::min(MaxLength, BUFFER_SIZE);
    ReadSize = Read(Buf, BlockSize);
    Sum += ReadSize;
    MaxLength -= ReadSize;
    if (ReadSize < BlockSize)
      break;
  }
  return Sum;
}

In the SeekableStream class more optimal algorithm can be used, which just sets cursor position. Derived classes of specific types of streams are still free to reimplement Skip method to provide even better versions.

size_t SeekableStream::Skip(size_t MaxLength)
{
  uint Pos = GetPos();
  uint Size = GetSize();
  uint BytesToSkip = std::min(MaxLength, Size - Pos);
  SetPosFromCurrent((int)BytesToSkip);
  return BytesToSkip;
}

Last thing I want to tell about today are some utility methods and functions which I've created just for convenience. I think their names are self-explanatory :)

class Stream
{
  ...
  size_t CopyFrom(Stream *src, size_t Size);
  void MustCopyFrom(Stream *src, size_t Size);
  void CopyFromToEnd(Stream *src);
};

void SaveStringToFile(const std::string &FileName, const std::string &Data);
void LoadStringFromFile(const std::string &FileName, std::string *Data);
void SaveDataToFile(const std::string &FileName, const void *Data, size_t NumBytes);

Next time I will show specific classes of my streams with some examples of how do I use them.

Comments | #c++ Share

# My Way of Implementing Streams #1

21:25
Thu
13
Aug 2009

Today I want to write a bit about a part of my library containing classes that represent binary data streams. My concept of streams is based on Delphi approach and maybe also Java. I use it for many years and I think it's very convenient. General assumtions are: I use object-oriented approach with base, abstract Stream class and virtual methods. I report errors using C++ exceptions. Of course they both cause some performance overhead, but flexibility and good error control has higher priority for me. Reading and writing disk files is orders of magnitude slower than CPU computations anyway :)

Streams in C++ standard library have separate classes for input stream (stream that can be read) and output stream (stream that can be written). I don't like this idea. First, disk files can be opened for both reading and writing, which forces basic_iostream class to use multiple inheritance and basic_ios to be inherited virtually. Second, I rather see opening a file for writing and/or reading as a mode, wchich is more suitable for constructor parameter than for separate classes. I prefer different division instead. I distinguish simple streams - these that can be only read and/or written and "seekable" streams - these that can also tell their data size as well as get and set current cursor position.

There are still two issues related to data streams that have to be considered. First is an end of stream concept. Standard library of C, Java and many other languages handle this in a way that looks totally weird for me. You can know about reaching end of file only if you have already tried to read past the end and that operation failed. Another strange thing is reading single byte (getc function) but returing type int instead of char, where value EOF == -1 means end of file. Maybe that's why some (beginner) programmers think that there is a magical "end of file" character standing at the end of every file, while the truth is that file has just its remembered size in bytes and each byte can have any of 256 possible values.

My approach for handling end of file is to implement bool End() method which returns true if the cursor is at the end of stream so that no more bytes can be read (cursor position == data size). Of course I have standard Read method which tries to read as many bytes as possible up to given buffer size and returns number of bytes read. It's convenient when you want to process a big file without loading it all to memory, using a buffer of constant size.

But I believe that more often you want to read a single value (like a number or a header structure) and you expect this read operation to succeed. If you do I/O with standard C, C++ or WinAPI functions, admit to yourself now whether you check for success of any read operation as well as number of bytes actually read? If you don't, then analyze what happens if you are already at the end of file and you do the following code:

unsigned elemCount;
fread(&elemCount, sizeof(elemCount), 1, myFile);
// What's the value of elemCount now and what will happen next?!
Vector3 *vectors = new Vector3[elemCount];
fread(vectors, sizeof(Vector3), elemCount, myFile);

That's why I also implement MustRead method, which throws an exception if expected number of bytes couldn't be read. I also expect every write operation to fully succeed by writing all bytes given or to throw the exception if it couldn't be done. Thanks to that I can be sure that I will always know about any failure in reading or writing any single value.

Second thing that has to be stated is whether any read/write operation can fail (or just not read all expected bytes) "for now", but succeed later. For example that's the case for network sockes, where write buffer can be currently full and read buffer can be empty so you have to wait for a while to be able to write/read more bytes. I assume that my streams cannot work this way, so I can't implement socket as a class derived from my Stream. It simplifies my streams because now I know that if I can't read more bytes, I've just reached end of data.

That's it for now. This entry is long enought :) I'll continue this streams topic next time. Let's say the interface of my base stream classes looks like this:

class Stream
{
public:
  virtual ~Stream() { }
  virtual void Write(const void *Data, size_t Size);
  virtual void Flush() { }
  virtual size_t Read(void *Data, size_t MaxLength);
  virtual void MustRead(void *Data, size_t Length);
  virtual bool End();
  ...
};

class SeekableStream : public Stream
{
public:
  virtual size_t GetSize();
  virtual void SetSize(size_t Size);
  virtual int GetPos();
  virtual void SetPos(int pos);
  virtual void SetPosFromCurrent(int pos);
  virtual void SetPosFromEnd(int pos);
  ...
};

Comments | #c++ Share

# Magic Numbers in Visual C++

23:37
Mon
10
Aug 2009

Uninitialized variables in C++ language are not filled automatically. They can contain any random data. It's as good as no range checking in access to arrays, because it doesn't waste time out of programmer's control. But on the other hand this rule makes us programmers responsible for thinking about when, where and how do we ensure that we don't use variables before their initialization or index arrays out of their range.

Visual C++ helps us with that. While in Release compilation, variables and fields are really uninitialized and contain random data, but under Debug configuration they are filled with special magic numbers. If we debug the program, hexadecimal codes of these numbers can tell us about the kind of error we made in the code. Just press "Hex" button on Debug toolbar to see all numbers as hexadecimal. Here are some of magic numbers used by Visual C++:

Interesting thing is that compiler doesn't warn about uninitialized fields in classes, despite it knows about uninitialized local variables and shows "Run-Time Check Failure #3 - The variable 'v' is being used without being initialized.". So whenever you have a bug which appears only in Release configuration and not in Debug, good chances are that you've forgotten to initialize a field in a class.

By the way, some programmers think that debugging is possible only in Debug configuration (as its name says). You acually can debug also in Release mode, although step by step execution may sometimes work strange due to optimizations like inlining, some watches may show invalid values and sometimes looking into objects doesn't work (probably bacause of optimized way "this" pointer is passed).

Comments | #visual studio #c++ Share

# Dialog Layout Manager in MFC

21:08
Sat
08
Aug 2009

Sometimes I write some tools using C++ and MFC. In the Linux world it is common that GUI windows are resizeable. In Windows its not the case, but sometimes it would be nice to be able to resize a dialog window to see more information, like more rows and colums in a list. After repositioning and resizing controls in a window with my custom code I've decided to automate this task.

There are many possible approaches to this problem. WinAPI (and thus MFC) does not provide by itself any solution to automatically align controls inside a resizeable window. Each control has just its fixed rectangle (left, top, width, height) inside parent window. Delphi VCL uses Align property to snap selected controls (like Panel containing child controls) to left, right, top or bottom edge of the window. Qt encourages to design all windows with Layouts. For example, Vertical Layout splits the window into rows and automatically adjusts controls inside, one under the other.

But the solution of my choice is the one based on .NET. Controls in Windows Forms have a property called Anchor so they can be anchored to any of four possible window edges: left, top, right and bottom. If a controls is anchored only to left and top edges, it just doesn't change its position or size. If the control is anchored to right and bottom edges (for example: a button), it changes its position as window is resized so it preserves its distance to right and bottom edge of the window. If the control is anchored to all four possible window edges, it is resized to preserve distance to all window edges same as designed (for example: a list occupying central part of the window).

I've written a class which I called DialogLayoutManager. It's very easy to use and automates control resizing and repositioning inside an MFC window. All you need to do at the beginning is to create an object of this class, register your controls with selected anchors and call Save method:

m_LayoutManager->SetControl(GetDlgItem(ID_BTN_CANCEL),
  DialogLayoutManager::ANCHOR_RIGHT | DialogLayoutManager::ANCHOR_BOTTOM);
m_LayoutManager->SetControl(GetDlgItem(ID_BTN_OK),
  DialogLayoutManager::ANCHOR_RIGHT | DialogLayoutManager::ANCHOR_BOTTOM);
m_LayoutManager->Save(this);

Layout manager remembers positions and sizes of registered controls together with starting window size. Now all you need to do when the window is resized is to call Restore method. Layout manager will adjust registered controls according to new window size and specified anchors. For example, two buttons showed above will stay in bottom-right corner of the window.

void CDialog01::OnSize(UINT nType, int cx, int cy)
{
  ...
  if (LayoutManager && LayoutManager->IsSaved())
    m_LayoutManager->Restore(this);
}

Here is the code of my DialogLayoutManager class and usage example: DialogLayoutManager.cpp. It's easy to translate this code to pure WinAPI.

Comments | #gui #mfc #winapi Share

# How Do People Write Bad Code

00:07
Wed
05
Aug 2009

Recently yarpen posted some thoughts about source code scalability on his blog. His blog entries are always very valuable and this time he touched broader subject which is connected to software antipatterns.

I want to expand this subject further and think a bit about how is such bad code created (we all agree that non-scalable code is bad, right? :) In my personal opinion it's generally caused by the lack of thinking. For example, if a coder have to code something (or, how some people would like to say: to solve a problem), he should first consider what possible solutions are. This may be choice of a technology, programming languages and libraries, some design patterns, algorithms, data structures or even names for some objects. If he doesn't think about it, he just selects the first option that came to his mind without considering alternatives and thus risks that he didn't make the best possible decision.

Next, the coder should design his solution. Designing is all about thinking before doing. If he skips this step, he may meet during his coding a situation when sees he came the wrong way and now he must refactor or even delete big part of his code and then just... rethink it :) I like the saying It's better to think twice before you start coding than to code twice before you start thinking. :)

The same applies to all changes made to an existing code. If somebody have to change something in a program, he'd better review the code carefully to discover and understand its architecture. If he just starts making changes to the code, he risks that he will break some hidden assumptions in some aspects of the code (like variable values, objects lifetime, order of function calls etc.) and thus create bugs.

The reason behind writing poor code may be rush. If a coder wants to code something as quick and simply as possible (or his boss tells him to do it this way), he will do ugly hacks and never refactor anything, even if some refactoring is necessary at this point. Of course this it to bad code and will backfire in the future, when the code will have to be changed and it will turn out to be totally unscalable. So we obviously see that such attitude is just the lack of thinking.

But this problem may also be something deeper. I can see that some coders just always write bad, unscalable code by their nature. I don't want to offend anyone, but I think maybe that's the crucial difference between junior and more experienced programmers. To work as a programmer, everyone needs to "speak fluently" his programming language to freely express his thoughts using it, but experienced programmers are also able to see a bigger picture - some invisible assumptions and more abstract concepts behind the code. They see words like "handle", "manager", "alignment", "serialization", "callback" and hundreds of other terms (anyone thought about creating coder's dictionary with words like these?) and just knows what hides behind them.

Three days after yarpen's entry there appeared a blog entry about code documentation on EntBlog. It describes two main types of documents - API documentation and technical articles. I fully agree with this text. I value documentation very much and I think that good documentation is another piece of this big puzzle.

They say that there are two types of people: these who make backups and these who will make it :) I think it's the same with writing good code and documentation. It's just the matter of thinking forward.

Comments | #software engineering #philosophy Share

# Backup Script in PHP

11:46
Sun
02
Aug 2009

One of my personal security procedures is daily backup of the code I'm working on to my FTP server. This process requires packing files to an archive and upload the archive to the FTP server. Most annoying part when doing it manually was always selecting aproppriate files, without all these big temporary files and directories like Debug, Release, .ncb, .suo, .user etc.

Today in the morning I've decided to automate this backup task with a script. As I like to use PHP as shell scripting language, here is my backup script in PHP: Backup.php_. It was not a suprise for me to see that PHP already have all the functionality I need, including building ZIP archive and FTP client. Some comments to my code:

To run the script, you must have PHP installed. Then you can just enter something like this into command line:

php Backup.php D:\MyGreatProject

Or you can create BAT file to support backup of your particular project:

@php D:\Backup.php D:\MyGreatProject
@pause

After the script have been started, you have to enter "backup name". It is the name of the ZIP file that will be uploaded to FTP. Script recursively searches for files and subdirectories in the given project directory, filters them using functions FilterDir and FilterFile, packs them to a temporary archive file and then uploads the archive to the FTP server. All remaining parameters (temporary archive path, FTP hostname, login, password and remote directory) are hardcoded as constants.

When it comes to the script code, one of the issues was to ask user about the backup name using system console. PHP has an extension called Readline, but unfortunately it doesn't work on Windows. The solution was to use fgets function with STDIN constant:

print("Enter backup name (default: \"$sSuggestedBackupName\"): ");
$sBackupName = trim(fgets(STDIN, 1024));
if (!$sBackupName)
  $sBackupName = $sSuggestedBackupName;

When it comes to building ZIP archive and handling FTP connection, PHP also has extensions for that and they seem to just work in my PHP without any problems. Zip extension exposes class ZipArchive and FTP extension exposes functions ftp_, like ftp_connect or ftp_put. All is documented in the official PHP documentation.

Comments | #scripts #php #tools Share

STAT NO AD
[Stat] [STAT NO AD] [Download] [Dropbox] [pub] [Mirror]
Copyright © 2004-2017