Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/WolfireGames/overgrowth.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Ogda/jobhandler.cpp')
-rw-r--r--Source/Ogda/jobhandler.cpp707
1 files changed, 707 insertions, 0 deletions
diff --git a/Source/Ogda/jobhandler.cpp b/Source/Ogda/jobhandler.cpp
new file mode 100644
index 00000000..35964264
--- /dev/null
+++ b/Source/Ogda/jobhandler.cpp
@@ -0,0 +1,707 @@
+//-----------------------------------------------------------------------------
+// Name: jobhandler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "database_manifest.h"
+#include "jobhandler.h"
+#include <trex/trex.h>
+#include <XML/Parsers/jobxmlparser.h>
+#include <Logging/logdata.h>
+#include "item.h"
+#include <Internal/filesystem.h>
+#include <Ogda/Builders/builderfactory.h>
+#include <Ogda/Searchers/searcherfactory.h>
+#include <Ogda/Generators/generatorfactory.h>
+#include "manifest.h"
+#include "database_manifest.h"
+#include <algorithm>
+#include <cstdio>
+#include <Utility/hash.h>
+#include "jobhandlerthreadpool.h"
+#include <cassert>
+#include "ogda_config.h"
+#include "main.h"
+#include <Utility/strings.h>
+
+JobHandler::JobHandler(std::string output_folder, std::string manifest_dest, std::string manifest_source, std::string databasedir, bool perform_removes, bool force_removes, int threads)
+: output_folder(output_folder), manifest_dest(manifest_dest), manifest_source(manifest_source), databasedir(databasedir), perform_removes(perform_removes),force_removes(force_removes), threads(threads)
+{
+
+}
+
+bool JobHandler::Run(const std::string& path )
+{
+ bool ret = true;
+ Manifest old_manifest(threads);
+ DatabaseManifest database_manifest;
+
+ std::string database_file = AssemblePath(databasedir, "database_manifest.xml");
+
+ if(!manifest_source.empty())
+ {
+ old_manifest.Load(manifest_source);
+ LOGI << "Calculating hashes of previously built files... [" << threads << "]" << std::endl;
+ {
+ old_manifest.PrecalculateCurrentDestinationHashes(output_folder);
+ }
+ }
+
+ if(!databasedir.empty() )
+ {
+ if( config_load_from_database || config_save_to_database ) {
+ database_manifest.Load(database_file);
+ }
+ }
+
+ {
+ JobXMLParser jobparser;
+ if( !jobparser.Load( path ) )
+ {
+ LOGE << "Error" << std::endl;
+ ret = false;
+ }
+ else
+ {
+ input_folders = jobparser.inputs;
+
+ LOGI << "Adding Items..." << std::endl;
+ {
+ size_t item_count = jobparser.items.size();
+ size_t cur = 0;
+ std::vector<JobXMLParser::Item>::iterator itemit;
+ for( itemit = jobparser.items.begin(); itemit != jobparser.items.end(); itemit++ )
+ {
+ std::string chosen_input_folder;
+ std::string item_path;
+ bool is_ok = false;
+
+ LOGD << "Loading Transfer: " << itemit->path << std::endl;
+ for(std::string input_folder : input_folders)
+ {
+ if(is_ok == false) {
+ if( CheckFileAccess(AssemblePath(input_folder,itemit->path).c_str()) ) {
+ item_path = itemit->path;
+ chosen_input_folder = input_folder;
+ is_ok = true;
+ } else {
+ std::string case_corrected = CaseCorrect(AssemblePath(input_folder,itemit->path));
+ if(CheckFileAccess(case_corrected.c_str())) {
+ item_path = case_corrected.substr(input_folder.size());
+ chosen_input_folder = input_folder;
+ is_ok = true;
+ LOGW << "Path \"" << itemit->path << "\" for item had to be case corrected to " << item_path << ". Row: " << itemit->row << std::endl;
+ }
+ }
+ }
+ }
+
+ if( is_ok ) {
+ if( itemit->recursive )
+ {
+ LOGD << "Transfer is recursive" << std::endl;
+
+ std::vector<std::string> manifest;
+ std::string manifest_path = AssemblePath(chosen_input_folder, item_path);
+ GenerateManifest(manifest_path.c_str(), manifest);
+
+ LOGD << "Loaded from " << manifest_path << " found " << manifest.size() << " files." << std::endl;
+
+ std::vector<std::string>::iterator manifestit;
+
+ for( manifestit = manifest.begin(); manifestit != manifest.end(); manifestit++ )
+ {
+ std::string full_sub_path = AssemblePath(item_path,*manifestit);
+ if(endswith(full_sub_path.c_str(), itemit->path_ending.c_str()))
+ {
+ LOGD << "Including " << full_sub_path << std::endl;
+ AddItem(chosen_input_folder, full_sub_path,itemit->type,*itemit);
+ }
+ else
+ {
+ LOGD << "Ignoring " << full_sub_path << std::endl;
+ }
+ }
+ }
+ else
+ {
+ AddItem(chosen_input_folder, item_path,itemit->type,*itemit);
+ }
+ } else {
+ LOGE << "Path \"" << itemit->path << "\" for item is invalid, even after case correction. Row: " << itemit->row << std::endl;
+ }
+
+ cur++;
+ SetPercent( item_path.c_str(), (int)(100.0f*((float)cur/(float)item_count)) );
+ }
+ }
+
+ if( config_print_item_list )
+ {
+ LOGI << "Printing item list from deployment file..." << std::endl;
+
+ std::vector<Item>::iterator itemit = items.begin();
+
+ for( ;itemit != items.end();itemit++ )
+ {
+ LOGI << *itemit << std::endl;
+ }
+ }
+
+ LOGI << "Adding Searchers..." << std::endl;
+ {
+ SearcherFactory sf;
+ std::vector<JobXMLParser::Searcher>::iterator searcherit;
+ for( searcherit = jobparser.searchers.begin(); searcherit != jobparser.searchers.end(); searcherit++ )
+ {
+ if( sf.HasSearcher(searcherit->searcher))
+ {
+ Searcher searcher = sf.CreateSearcher( searcherit->searcher, searcherit->path_ending, searcherit->type_pattern_re);
+ searchers.push_back(searcher);
+ }
+ else
+ {
+ ret = false;
+ LOGE << "Unable to find searcher matching name " << searcherit->searcher << std::endl;
+ }
+ }
+ }
+
+ LOGI << "Adding Builders..." << std::endl;
+ {
+ BuilderFactory bf;
+ std::vector<JobXMLParser::Builder>::iterator builderit;
+ for( builderit = jobparser.builders.begin(); builderit != jobparser.builders.end(); builderit++ )
+ {
+ if( bf.HasBuilder( builderit->builder ) )
+ {
+ Builder builder = bf.CreateBuilder( builderit->builder, builderit->path_ending, builderit->type_pattern_re);
+ builders.push_back(builder);
+ }
+ else
+ {
+ LOGE << "Unable to find builder matching name " << builderit->builder << std::endl;
+ ret = false;
+ }
+ }
+ }
+
+ LOGI << "Adding Generators..." << std::endl;
+ {
+ GeneratorFactory bf;
+ std::vector<JobXMLParser::Generator>::iterator generatorit;
+ for( generatorit = jobparser.generators.begin(); generatorit != jobparser.generators.end(); generatorit++ )
+ {
+ if( bf.HasGenerator( generatorit->generator ) )
+ {
+ Generator generator = bf.CreateGenerator( generatorit->generator );
+ generators.push_back(generator);
+ }
+ else
+ {
+ LOGE << "Unable to find generator matching name " << generatorit->generator << std::endl;
+ ret = false;
+ }
+ }
+
+ }
+
+ LOGD << jobparser << std::endl;
+ }
+ }
+
+ LOGI << "Running searchers through Items..." << std::endl;
+ {
+ std::vector<Item>::iterator itemit;
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ RunRecursiveSearchOn( *itemit );
+ }
+
+ LOGI << "Found a total of " << foundItems.size() << " objects when searching" << std::endl;
+ items.insert( items.end(), foundItems.begin(), foundItems.end() );
+ }
+
+ LOGI << "Marking overshadowed Items" << std::endl;
+ {
+ std::vector<Item>::iterator itemit;
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ std::vector<Item>::iterator item2it;
+ for( item2it = items.begin(); item2it != items.end(); item2it++ )
+ {
+ if( itemit->Overshadows(*item2it) )
+ {
+ item2it->SetOvershadowed(true);
+ }
+ }
+ }
+ }
+
+ if( config_print_duplicates )
+ {
+ LOGI << "Looking for duplicate items..." << std::endl;
+ {
+ std::vector<Item>::iterator itemit;
+ std::vector<Item>::iterator itemit2;
+
+ std::set<JobXMLParser::Item> first_level_duplicates;
+
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ for( itemit2 = itemit + 1; itemit2 != items.end(); itemit2++ )
+ {
+ if( itemit->GetAbsPath() == itemit2->GetAbsPath()
+ && itemit->type == itemit2->type )
+ {
+ if( *itemit == itemit->source )
+ {
+ first_level_duplicates.insert( itemit->source );
+ LOGW << "Found duplicated of " << *itemit << " " << "sourced from rows: " << itemit->source.row << " and " << itemit2->source.row << "." << std::endl;
+ }
+ else if( *itemit2 == itemit2->source )
+ {
+ first_level_duplicates.insert( itemit2->source );
+ LOGW << "Found duplicated of " << *itemit << " " << "sourced from rows: " << itemit->source.row << " and " << itemit2->source.row << "." << std::endl;
+ }
+ }
+ }
+ }
+
+ LOGI << "Listing all first-level first-time references to duplicates... (For simple removal)" << std::endl;
+ {
+ std::set<JobXMLParser::Item>::iterator parserit;
+
+ for( parserit = first_level_duplicates.begin(); parserit != first_level_duplicates.end(); parserit++ )
+ {
+ fprintf( stderr, "RMLN:%d\n", parserit->row );
+ }
+ }
+ }
+ }
+
+ if( config_print_missing )
+ {
+ LOGI << "Printing files in folder but not in deploy list..." << std::endl;
+
+ std::vector<std::string> manifest;
+
+ for(std::string input_folder : input_folders)
+ {
+ GenerateManifest( input_folder.c_str(), manifest );
+ }
+
+ std::vector<std::string>::iterator manifestit = manifest.begin();
+
+ for( ; manifestit != manifest.end(); manifestit++ )
+ {
+ if( false == HasItemWithPath( *manifestit ) )
+ {
+ std::cout << *manifestit << std::endl;
+ }
+ }
+ }
+
+ /*
+ LOGI << "Checking what items don't have a searcher..." << std::endl;
+ {
+ std::map<std::string,int>::iterator searchTypeIt;
+
+ for( searchTypeIt = typeSearcherCount.begin(); searchTypeIt != typeSearcherCount.end(); searchTypeIt++ )
+ {
+ if( searchTypeIt->second == 0 )
+ {
+ LOGW << "Item type: " << searchTypeIt->first << " has no assigned searchers." << std::endl;
+ }
+ }
+ }
+ */
+
+ LOGI << "Calculating Item hashes...[" << threads << "]"<< std::endl;
+ {
+ JobHandlerThreadPool jhtp(threads);
+ jhtp.RunHashCalculation(items);
+ }
+
+ if( !config_mute_missing )
+ {
+ LOGI << "Listing referenced items missing from disk..." << std::endl;
+ {
+ std::vector<Item>::iterator itemit;
+
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ if( itemit->hash.empty() && itemit->IsOvershadowed() == false )
+ {
+ LOGE << "Missing item " << *itemit << std::endl;
+ }
+ }
+ }
+ }
+
+ Manifest result_manifest(threads);
+ LOGI << "Running Builders through Items..." << std::endl;
+ {
+ size_t item_count = items.size();
+ size_t cur = 0;
+ std::vector<Item>::iterator itemit;
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ if( itemit->IsOvershadowed() )
+ {
+ LOGI << "Skipping " << *itemit << " because it's overshadowed" << std::endl;
+ }
+ else if( itemit->IsOnlySearch() )
+ {
+ LOGD << "Skipping " << *itemit << " because it's search only" << std::endl;
+ }
+ else
+ {
+ std::vector<Builder>::iterator builderit;
+ int count = 0;
+ for( builderit = builders.begin(); builderit != builders.end(); builderit++ )
+ {
+ if( builderit->IsMatch( *itemit ))
+ {
+ count++;
+ if(builderit->RunEvenOnIdenticalSource() == false && old_manifest.IsUpToDate( *this, *itemit, *builderit ) )
+ {
+ LOGD << "Using cached result on " << *itemit << std::endl;
+ result_manifest.AddResult(old_manifest.GetPreviouslyBuiltResult( *itemit, *builderit ));
+ }
+ else if( config_load_from_database && builderit->RunEvenOnIdenticalSource() == false && builderit->StoreResultInDatabase() && database_manifest.HasBuiltResultFor(*this, *itemit, *builderit) )
+ {
+ LOGI << "Using database value on " << *itemit << std::endl;
+ DatabaseManifestResult dmr = database_manifest.GetPreviouslyBuiltResult(*itemit, *builderit);
+
+ std::string result_source_path = AssemblePath(databasedir,AssemblePath("files",AssemblePath(dmr.item.hash,dmr.dest_hash)));
+ std::string result_dest_path = AssemblePath(output_folder,dmr.dest);
+
+ CreateParentDirs(result_dest_path);
+ copyfile(result_source_path,result_dest_path);
+ result_manifest.AddResult(ManifestResult(dmr.dest_hash, *itemit, dmr.dest, true, dmr.name, dmr.version, ManifestResult::DATABASE, dmr.type));
+ }
+ else
+ {
+ //Check if item has source hash, meaning if the source file exists.
+ if( !itemit->hash.empty() )
+ {
+ LOGD << "Running " << builderit->GetBuilderName() << " on " << *itemit << std::endl;
+ result_manifest.AddResult(builderit->Run(*this, *itemit));
+ }
+ else
+ {
+ LOGE << "Unable to run " << builderit->GetBuilderName() << " on " << *itemit << " file missing." << std::endl;
+ ret = false;
+ }
+ }
+ }
+ else
+ {
+ LOGD << "Skipping " << builderit->GetBuilderName() << " on " << *itemit << ", doesn't match pattern." << std::endl;
+ }
+ }
+
+ if( count == 0 && !itemit->hash.empty() )
+ LOGW << *itemit << " has no assigned builder" << std::endl;
+ if( count > typeBuilderCount[itemit->type] )
+ typeBuilderCount[itemit->type] = count;
+ }
+ cur++;
+ SetPercent( itemit->GetPath().c_str(), (int)(100.0f*((float)cur/(float)item_count)) );
+ }
+ }
+
+ //Copy manifest to use when running the generators.
+ const Manifest generated_manifest = result_manifest;
+
+ LOGI << "Running Generators..." << std::endl;
+ {
+ std::vector<Generator>::iterator generatorit;
+ int count = 0;
+ for( generatorit = generators.begin(); generatorit != generators.end(); generatorit++ )
+ {
+ count++;
+ LOGD << "Running " << generatorit->GetGeneratorName() << std::endl;
+ result_manifest.AddResult(generatorit->Run(*this,generated_manifest));
+ }
+ }
+
+ LOGI << "Checking what items don't have a builder..." << std::endl;
+ {
+ std::map<std::string,int>::iterator builderTypeIt;
+
+ for( builderTypeIt = typeBuilderCount.begin(); builderTypeIt != typeBuilderCount.end(); builderTypeIt++ )
+ {
+ if( builderTypeIt->second == 0 )
+ {
+ LOGW << "Item type: " << builderTypeIt->first << " has no assigned builder." << std::endl;
+ }
+ }
+ }
+
+ if( result_manifest.HasError() )
+ {
+ LOGE << "Some builder(s) caused an error, see manifest for more info." << std::endl;
+ ret = false;
+ }
+
+ //Unlinks are serious business, so we do this carefully
+ if( ret || force_removes )
+ {
+ LOGI << "Removing items not listed in the generated manifest" << std::endl;
+
+ std::vector<std::string> destination_file_list;
+ GenerateManifest( output_folder.c_str(), destination_file_list );
+ {
+ std::vector<std::string>::iterator missit;
+ for(missit = destination_file_list.begin(); missit != destination_file_list.end(); missit++ )
+ {
+ LOGD << "In Output Folder: " << *missit << std::endl;
+ }
+ }
+
+ std::vector<std::string> new_manifest_file_list = result_manifest.GetDestinationFiles();
+ {
+ std::vector<std::string>::iterator missit;
+ for(missit = new_manifest_file_list.begin(); missit != new_manifest_file_list.end(); missit++ )
+ {
+ LOGD << "New Manifest File: " << *missit << std::endl;
+ }
+ }
+
+ std::vector<std::string> old_manifest_file_list = old_manifest.GetDestinationFiles();
+ {
+ std::vector<std::string>::iterator missit;
+ for(missit = old_manifest_file_list.begin(); missit != old_manifest_file_list.end(); missit++ )
+ {
+ LOGD << "Old Manifest File: " << *missit << std::endl;
+ }
+ }
+
+ std::vector<std::string> unlisted_files;
+
+ std::sort( destination_file_list.begin(), destination_file_list.end() );
+ std::sort( new_manifest_file_list.begin(), new_manifest_file_list.end() );
+ std::sort( old_manifest_file_list.begin(), old_manifest_file_list.end() );
+
+ std::set_difference( destination_file_list.begin(), destination_file_list.end(),
+ new_manifest_file_list.begin(), new_manifest_file_list.end(),
+ std::back_inserter(unlisted_files) );
+
+ {
+ std::vector<std::string>::iterator missit;
+ for(missit = unlisted_files.begin(); missit != unlisted_files.end(); missit++ )
+ {
+ LOGI << "Unlisted: " << *missit << std::endl;
+ }
+ }
+
+ std::vector<std::string> remove_list;
+
+
+ //Remove files that are in the folder but not known from previous builds?
+ if( config_remove_unlisted_files )
+ {
+ LOGI << "Adding all unlisted files into the remove list. (--remove-unlisted)" << std::endl;
+ remove_list.insert(remove_list.begin(), unlisted_files.begin(), unlisted_files.end() );
+ }
+ else
+ {
+ //Only remove unlisted files that are mentioned in the old manifest.
+ std::set_intersection( unlisted_files.begin(), unlisted_files.end(),
+ old_manifest_file_list.begin(), old_manifest_file_list.end(),
+ std::back_inserter(remove_list) );
+ }
+
+ //If this passes i'm comfortable removing files.
+ if( remove_list == unlisted_files || force_removes )
+ {
+ if( remove_list == unlisted_files )
+ {
+ LOGI << "Unlisted files match old manifest, removing them" << std::endl;
+ }
+ else
+ {
+ LOGI << "Dictated to forcefully remove all found items." << std::endl;
+ }
+
+ {
+ std::vector<std::string>::iterator missit;
+ for(missit = remove_list.begin(); missit != remove_list.end(); missit++ )
+ {
+ std::string full_remove_path = AssemblePath(output_folder,*missit);
+ if( perform_removes )
+ {
+ LOGW << "Removing " << full_remove_path << std::endl;
+ remove( full_remove_path.c_str() );
+ }
+ else
+ {
+ LOGI << "Pretending to remove (no --perform-removes): " << full_remove_path << std::endl;
+ }
+ }
+ }
+ }
+ else
+ {
+ LOGF << "Unlisted files and old manifest files don't match, i refuse to try and remove anything because this is a hint that something isn't right in this fully managed directory." << std::endl;
+ ret = false;
+ }
+ }
+ else
+ {
+ LOGE << "Skipping removal due to previous error(s)" << std::endl;
+ }
+
+ LOGI << "Removing temporary items..." << std::endl;
+ {
+ std::vector<Item>::iterator itemit;
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ if( itemit->IsDeleteOnExit() )
+ {
+ if( perform_removes )
+ {
+ LOGD << "Removing " << *itemit << std::endl;
+ std::string path = itemit->GetAbsPath();
+ remove( path.c_str() );
+ }
+ else
+ {
+ LOGD << "Skipping remove of " << *itemit << " as --perform-removes isn't specified" << std::endl;
+ }
+
+ }
+ }
+ }
+
+ if( !manifest_dest.empty() )
+ {
+ LOGI << "Saving resulting manifest to disk: " << manifest_dest << std::endl;
+ result_manifest.Save(manifest_dest);
+ }
+
+ if(!databasedir.empty() ) {
+ if( config_save_to_database ) {
+ std::vector<ManifestResult>::const_iterator mareit = result_manifest.ResultsBegin();
+ BuilderFactory builder_factory;
+ for(; mareit != result_manifest.ResultsEnd(); mareit++ ) {
+ if( mareit->mr_type == ManifestResult::BUILT ) {
+ if( mareit->items.size() == 1 ) {
+ if( builder_factory.StoreResultInDatabase(mareit->name) ) {
+
+ LOGI << "Storing " << *mareit << " in database for future use." << std::endl;
+ std::string result_source_path = AssemblePath(output_folder,mareit->dest);
+ std::string result_dest_path = AssemblePath(databasedir,AssemblePath("files",AssemblePath(mareit->items[0].hash,mareit->dest_hash)));
+
+ CreateParentDirs(result_dest_path);
+ copyfile(result_source_path,result_dest_path);
+
+ database_manifest.AddResult(DatabaseManifestResult(
+ mareit->items[0],
+ mareit->dest_hash,
+ mareit->dest,
+ mareit->name,
+ mareit->version,
+ mareit->type
+ ));
+ }
+ } else {
+ LOGW << "Database doesn't support multi item sources" << std::endl;
+ }
+ }
+ }
+ CreateParentDirs(database_file);
+ database_manifest.Save(database_file);
+ }
+ }
+
+ return ret;
+}
+
+void JobHandler::RunRecursiveSearchOn( const Item& item )
+{
+ std::vector<Item> foundSum;
+ if( std::find(searchedItems.begin(),searchedItems.end(),item) == searchedItems.end() )
+ {
+ std::vector<Searcher>::iterator searcherit;
+ int count = 0;
+ for( searcherit = searchers.begin(); searcherit != searchers.end(); searcherit++ )
+ {
+ std::vector<Item> f = searcherit->TrySearch(*this,item,&count);
+
+ foundSum.insert(foundSum.end(),f.begin(),f.end());
+ }
+ assert((count > 0 && foundSum.size() > 0) || foundSum.size() == 0);
+
+ if( count == 0 )
+ {
+ LOGW << item << " has no assigned searcher" << std::endl;
+ }
+
+ if( count > typeSearcherCount[item.type] )
+ typeSearcherCount[item.type] = count;
+
+ searchedItems.push_back(item);
+
+ std::vector<Item>::iterator foundSumIt;
+
+ for( foundSumIt = foundSum.begin(); foundSumIt != foundSum.end(); foundSumIt++ )
+ {
+ //We sometimes get broken paths from the files (bad case etc)
+ foundSumIt->VerifyPath();
+ RunRecursiveSearchOn( *foundSumIt );
+ }
+
+ if( foundSum.size() > 0 )
+ LOGD << "Found " << foundSum.size() << " in " << item << std::endl;
+ foundItems.insert(foundSum.begin(), foundSum.end());
+ }
+}
+
+void JobHandler::AddItem(const std::string& input_folder, const std::string& path, const std::string& type, const JobXMLParser::Item& source)
+{
+ LOGD << "Adding " << type << ": " << path << std::endl;
+ items.push_back(Item(input_folder,path,type,source));
+}
+
+std::vector<Item>::const_iterator JobHandler::ItemsBegin() const
+{
+ return items.begin();
+}
+
+std::vector<Item>::const_iterator JobHandler::ItemsEnd() const
+{
+ return items.end();
+}
+
+bool JobHandler::HasItemWithPath( const std::string& path )
+{
+ std::vector<Item>::iterator itemit = items.begin();
+ for( ; itemit != items.end(); itemit++ )
+ {
+ if( itemit->path == path )
+ {
+ return true;
+ }
+ }
+ return false;
+}