Monday, November 27, 2017

New solution to build parent path

It took me a few weeks to work on this POC with Boost Intrusive. I was thinking to use Boost Intrusive build a directory path. Just like a tree structure, the branches representing the files and folders, spread until the end of the sub directory. Thus the setup for this class is as follows:
class FileNode : public boost::intrusive::list_base_hook<link_mode<auto_unlink> >
{
private:
    boost::filesystem::path name; // file name
    char type; // f is file, d is directory

public:
    boost::intrusive::list< FileNode, base_hook<list_base_hook<link_mode<auto_unlink> > >, constant_time_size<false> > sibling;

};
I created a FileNode class represent the file in a path, and each object of this class can neither be a file or a folder, and the a name too. These are the private member declared in the class. The sibling member is to tell whether they are any sub directory available, if the size is greater than zero, it means there are files in it.

Now come to the main dish. There are 2 part of it; first would be the load the given path into the memory by using the class structure mention above, second is to constructed of the files underneath the given path. For this POC, I'm focusing on the first part, the second part has not yet done.

I have though should I skip the first part for some while? Say when user keyed in /home/path_1, can I just treat the value as a single FileNode? Since I doesn't really care what the value are, it would be easier for me to do the job. Coming from the perfectionism view-point, I think it would be nice to load every single file entity as a separate FileNode object. Meaning to said that /home is one object, and /path_1 is another object.

For this purpose, I create a function to handle this job for me.
FileNode* FileBot::constructParentPath(string path)
{
    vector<string> words;
    FileNode *node = new FileNode();
    string unixFilePath = path;

    boost::replace_last(unixFilePath, "/", "|");
    boost::split(words, unixFilePath, boost::is_any_of("|"), boost::token_compress_on);

    node->setType('d');
    node->setName(words[1]);

    // there is a parent node
    if( words[0] != "" ) {
        FileNode *parent = constructParentPath(words[0]);

        cout << "parent address: " << parent << endl;

        parent->sibling.push_back(*node);
    }
    else {
        fileList.push_back(*node);
        cout << "node address: " << node << " " << node->getName() << endl;
    }

    words.clear();

    return node;
}
This function is as good as it works only on Linux, but failed on Windows. It can't pass the test case in Windows specific path. After many round of rework, then only I figure out a new solution that tested out on both Linux and Windows:
int FileBot::initialize()
{
    // bail out if the path doesn't exists
    if( !exists(fileNode.getName().generic_string()) )
        return 0;

    // construct the parent path first
    string path = fileNode.getName().generic_string();
    FileNode *parent = NULL;

    typedef split_iterator<string::iterator> string_split_iterator;
    for( string_split_iterator it = make_split_iterator(path, first_finder("/", is_equal()));
         it != string_split_iterator();
         ++it)
    {
        FileNode *node = new FileNode();
        node->setType('d');
        node->setName(copy_range<string>(*it));

        cout << "value inserted: [" << copy_range<string>(*it) << "]" << endl;

        if( parent == NULL ) {
            fileList.push_back(*node);
        }
        else
            parent->sibling.push_back(*node);

        parent = node;
        cout << "parent: " << parent;
        cout << " parent value: " << parent->getName();
        cout << " sibling size: " << parent->sibling.size() << endl;
    }

    return 1;
}
The difference between the 2 solutions is that the first one is using the recursive loop and the later one is using for loop. On top of that the second solution is much easier to read than the first. To check my work is being done correctly, I have created following test case for the unit test.
/* load 1 level parent path
 *
 */
BOOST_AUTO_TEST_CASE(TL_4)
{
    BOOST_TEST_MESSAGE("TC4 : load 1 level parent path");

#if defined(WIN32)
    BOOST_TEST_MESSAGE("Test path: D:");
    FileBot fb("D:");
#else
    BOOST_TEST_MESSAGE("Test path: /home");
    FileBot fb("/home");
#endif

    string path = "";
    if( fb.initialize() == 1 )
       path = fb.verifyFileList();

#if defined(WIN32)
    BOOST_TEST(path == "D:");
    fb.clearMemory();
#else
    BOOST_TEST(path == "/home");
#endif
}


/* load 2 level parent path
 *
 */
BOOST_AUTO_TEST_CASE(TL_5)
{
    BOOST_TEST_MESSAGE("TC5 : load 2 level parent path");

#if defined(WIN32)
    BOOST_TEST_MESSAGE("Test path: D:/workspaceqt");
    FileBot fb("D:/workspaceqt");
#else
    BOOST_TEST_MESSAGE("Test path: /home/kokhoe");
    FileBot fb("/home/kokhoe");
#endif

    string path = "";
    if( fb.initialize() == 1 )
       path = fb.verifyFileList();

#if defined(WIN32)
    BOOST_TEST(path == "D:/workspaceqt");
    fb.clearMemory();
#else
    BOOST_TEST(path == "/home/kokhoe");
#endif
}


/* load 3 level parent path
 *
 */
BOOST_AUTO_TEST_CASE(TL_6)
{
    BOOST_TEST_MESSAGE("TC6 : load 3 level parent path");

#if defined(WIN32)
    BOOST_TEST_MESSAGE("Test path: D:/workspaceqt/ui1");
    FileBot fb("D:/workspaceqt/ui1");
#else
    BOOST_TEST_MESSAGE("Test path: /home/kokhoe/workspaceqt");
    FileBot fb("/home/kokhoe/workspaceqt");
#endif

    string path = "";
    if( fb.initialize() == 1 )
       path = fb.verifyFileList();

#if defined(WIN32)
    BOOST_TEST(path == "D:/workspaceqt/ui1");
    fb.clearMemory();
#else
    BOOST_TEST(path == "/home/kokhoe/workspaceqt");
#endif
}

Wednesday, November 8, 2017

Accessing the last element of an Intrusive list

I always thought that retrieving the last element from an intrusive list just as easy as one two three. In fact, it is not. Let's dive into the story. Here I have the intrusive list declaration:
/***** FileNode.h *****/

class FileNode : public boost::intrusive::list_base_hook<link_mode<auto_unlink> >
{
...
...

};


/***** FileBot.h *****/

typedef boost::intrusive::list<FileNode, base_hook<list_base_hook<link_mode<auto_unlink> > >, constant_time_size<false> > FileNodeListType;

class FileBot
{
private:
    FileNodeListType fileList;

...
...
};
And then I'll use the iterator as shown below go straight to access the element located at the end of the list, but somehow this a hit segmentation fault error.
        FileNodeListType::iterator it(fileList.end());

        // following piece basically doing some data manipulation
        // on the element retrieve from the list.
        FileNode *p = &*it;
        (*it).sibling.push_back(*node);

        cout << p << endl; // memory address shows 0x7ffedff8e808
The error is due to the wrong memory address is being accessed. As I verify on the element being inserted into the list (only one element is inserted in this test case) is having an address different from the one mention in the code above. Thus, according to the expert, in order to access the correct element located at the end of the list is by doing this:
        FileNodeListType::iterator it(fileList.end());
        FileNode *p = &*(--it);
        cout << p << endl; // memory address shows 0xa01bc0