private void OnInit()
    {
        GameObject syncPathObj = new GameObject(SyncPathConst.SYNC_PATH_NAME);

        UnityEngine.Object.DontDestroyOnLoad(syncPathObj);
        syncPath = syncPathObj.AddComponent <SyncPath>();
    }
        private void DeleteFile(SyncPath file)
        {
            var fileInfo = new FileInfo(file.Path)
            {
                Attributes = FileAttributes.Normal
            };

            try
            {
                fileInfo.Delete();
                SyncResult.DeletedFiles.Add(file.Path);
                SyncResult.Log.Add("Deleted: " + file.Path);
            }
            catch (Exception e)
            {
                var msg = string.Format("Error: Could not delete the file '{0}'. Error: {1}", file.Path, e.Message);
                SyncResult.Errors.Add(msg);
            }
        }
        private void MoveFile(SyncPath file)
        {
            var fileInfo = new FileInfo(file.Path)
            {
                Attributes = FileAttributes.Normal
            };
            var moveTo = addedFiles.First(path => path.Equals(file.Path, StringComparison.InvariantCultureIgnoreCase));

            try
            {
                fileInfo.MoveTo(moveTo);
                SyncResult.DeletedFiles.Add(file.Path);
                SyncResult.Log.Add("Moved: " + file.Path);
            }
            catch (Exception e)
            {
                var msg = string.Format("Error: Could not move the file '{0}'. Error: {1}", file.Path, e.Message);
                SyncResult.Errors.Add(msg);
            }
        }
Beispiel #4
0
        public FindFoldersToCleanTask(PerforceConnection InPerforceClient, FolderToClean InRootFolderToClean, string InClientRootPath, IReadOnlyList <string> InSyncPaths, TextWriter InLog)
        {
            PerforceClient    = InPerforceClient;
            ClientRootPath    = InClientRootPath.TrimEnd('/') + "/";
            SyncPaths         = new List <string>(InSyncPaths);
            Log               = InLog;
            RootFolderToClean = InRootFolderToClean;
            FinishedScan      = new ManualResetEvent(true);

            foreach (string SyncPath in SyncPaths)
            {
                Debug.Assert(SyncPath.StartsWith(ClientRootPath));
                if (SyncPath.StartsWith(ClientRootPath, StringComparison.InvariantCultureIgnoreCase))
                {
                    string[] Fragments = SyncPath.Substring(ClientRootPath.Length).Split('/');

                    FolderToClean SyncFolder = RootFolderToClean;
                    for (int Idx = 0; Idx < Fragments.Length - 1; Idx++)
                    {
                        FolderToClean NextSyncFolder = SyncFolder.SubFolders.FirstOrDefault(x => x.Name.Equals(Fragments[Idx], StringComparison.InvariantCultureIgnoreCase));
                        if (NextSyncFolder == null)
                        {
                            NextSyncFolder = new FolderToClean(new DirectoryInfo(Path.Combine(SyncFolder.Directory.FullName, Fragments[Idx])));
                            SyncFolder.SubFolders.Add(NextSyncFolder);
                        }
                        SyncFolder = NextSyncFolder;
                    }

                    string Wildcard = Fragments[Fragments.Length - 1];
                    if (Wildcard == "...")
                    {
                        QueueFolderToPopulate(SyncFolder);
                    }
                    else if (SyncFolder.Directory.Exists)
                    {
                        SyncFolder.FilesToClean = SyncFolder.FilesToClean.Union(SyncFolder.Directory.GetFiles(Wildcard)).GroupBy(x => x.Name).Select(x => x.First()).ToList();
                    }
                }
            }
        }
        public bool Run(out string ErrorMessage)
        {
            Log.WriteLine("Finding files in workspace...");
            Log.WriteLine();

            // Start enumerating all the files that exist locally
            foreach (string SyncPath in SyncPaths)
            {
                Debug.Assert(SyncPath.StartsWith(ClientRootPath));
                if (SyncPath.StartsWith(ClientRootPath, StringComparison.InvariantCultureIgnoreCase))
                {
                    string[] Fragments = SyncPath.Substring(ClientRootPath.Length).Split('/');

                    FolderToClean SyncFolder = RootFolderToClean;
                    for (int Idx = 0; Idx < Fragments.Length - 1; Idx++)
                    {
                        FolderToClean NextSyncFolder;
                        if (!SyncFolder.NameToSubFolder.TryGetValue(Fragments[Idx], out NextSyncFolder))
                        {
                            NextSyncFolder = new FolderToClean(new DirectoryInfo(Path.Combine(SyncFolder.Directory.FullName, Fragments[Idx])));
                            SyncFolder.NameToSubFolder[NextSyncFolder.Name] = NextSyncFolder;
                        }
                        SyncFolder = NextSyncFolder;
                    }

                    string Wildcard = Fragments[Fragments.Length - 1];
                    if (Wildcard == "...")
                    {
                        QueueFolderToPopulate(SyncFolder);
                    }
                    else
                    {
                        if (SyncFolder.Directory.Exists)
                        {
                            foreach (FileInfo File in SyncFolder.Directory.EnumerateFiles(Wildcard))
                            {
                                SyncFolder.NameToFile[File.Name] = File;
                            }
                        }
                    }
                }
            }

            // Get the prefix for any local file
            string LocalRootPrefix = RootFolderToClean.Directory.FullName.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;

            // Query the have table and build a separate tree from it
            PerforceHaveFolder RootHaveFolder = new PerforceHaveFolder();

            foreach (string SyncPath in SyncPaths)
            {
                List <PerforceFileRecord> FileRecords;
                if (!PerforceClient.Stat(String.Format("{0}#have", SyncPath), out FileRecords, Log))
                {
                    ErrorMessage = "Couldn't query have table from Perforce.";
                    return(false);
                }
                foreach (PerforceFileRecord FileRecord in FileRecords)
                {
                    if (!FileRecord.ClientPath.StartsWith(LocalRootPrefix, StringComparison.InvariantCultureIgnoreCase))
                    {
                        ErrorMessage = String.Format("Failed to get have table; file '{0}' doesn't start with root path ('{1}')", FileRecord.ClientPath, RootFolderToClean.Directory.FullName);
                        return(false);
                    }

                    string[] Tokens = FileRecord.ClientPath.Substring(LocalRootPrefix.Length).Split('/', '\\');

                    PerforceHaveFolder FileFolder = RootHaveFolder;
                    for (int Idx = 0; Idx < Tokens.Length - 1; Idx++)
                    {
                        PerforceHaveFolder NextFileFolder;
                        if (!FileFolder.NameToSubFolder.TryGetValue(Tokens[Idx], out NextFileFolder))
                        {
                            NextFileFolder = new PerforceHaveFolder();
                            FileFolder.NameToSubFolder.Add(Tokens[Idx], NextFileFolder);
                        }
                        FileFolder = NextFileFolder;
                    }
                    FileFolder.NameToFile[Tokens[Tokens.Length - 1]] = FileRecord;
                }
            }

            // Find all the files which are currently open for edit. We don't want to force sync these.
            List <PerforceFileRecord> OpenFileRecords;

            if (!PerforceClient.GetOpenFiles("//...", out OpenFileRecords, Log))
            {
                ErrorMessage = "Couldn't query open files from Perforce.";
                return(false);
            }

            // Build a set of all the open local files
            HashSet <string> OpenLocalFiles = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase);

            foreach (PerforceFileRecord OpenFileRecord in OpenFileRecords)
            {
                if (!OpenFileRecord.ClientPath.StartsWith(ClientRootPath, StringComparison.InvariantCultureIgnoreCase))
                {
                    ErrorMessage = String.Format("Failed to get open file list; file '{0}' doesn't start with client root path ('{1}')", OpenFileRecord.ClientPath, ClientRootPath);
                    return(false);
                }
                OpenLocalFiles.Add(LocalRootPrefix + PerforceUtils.UnescapePath(OpenFileRecord.ClientPath).Substring(ClientRootPath.Length).Replace('/', Path.DirectorySeparatorChar));
            }

            // Wait to finish scanning the directory
            FinishedScan.WaitOne();

            // Find the value of the P4CONFIG variable
            string PerforceConfigFile;

            PerforceClient.GetSetting("P4CONFIG", out PerforceConfigFile, Log);

            // Merge the trees
            MergeTrees(RootFolderToClean, RootHaveFolder, OpenLocalFiles, PerforceConfigFile);

            // Remove all the empty folders
            RemoveEmptyFolders(RootFolderToClean);
            ErrorMessage = null;
            return(true);
        }
        private static bool MakeLeanMSG3(Store msg, ResSet predicates, StatementSink removed,
                                         ResSet nodesremoved, SyncPath path)
        {
            // The variable path has to be expanded by including the statements
            // connected to the variables on the frontier.  Statements
            // mentioning a variable node have already been considered.
            // The target of each such statement can be considered fixed
            // or variable. If a variable is considered fixed, the edge
            // must exist in the MSG substituting the variables for their
            // values.  If it's variable, it has to have at least one
            // match in the MSG but not as any of the variable nodes.
            // If all targets are considered fixed (and have matches),
            // then the variables so far (and their edges) can all be
            // removed and no more processing needs to be done.
            // There are (2^N)-1 other considerations.  For each of those,
            // the targets considered variables all become the new
            // frontier, and this is repeated.

            // First, get a list of edges from the frontier that we
            // haven't considered yet.

            ArrayList alledges = new ArrayList();

            foreach (BNode b in path.FrontierVariables)
            {
                // Make sure all edges are kept because even the ones
                // to literals have to be removed when duplication is found.
                foreach (Statement s in msg.Select(new Statement(b, null, null)))
                {
                    alledges.Add(new Edge(true, b, s.Predicate, s.Object));
                }
                foreach (Statement s in msg.Select(new Statement(null, null, b)))
                {
                    alledges.Add(new Edge(false, b, s.Predicate, s.Subject));
                }
            }

            ArrayList newedges           = new ArrayList();
            ResSet    alltargets         = new ResSet();
            ResSet    fixabletargetsset  = new ResSet();         // can be fixed
            ResSet    variabletargetsset = new ResSet();         // must be variable

            foreach (Edge e in alledges)
            {
                if (path.Path.ContainsKey(e))
                {
                    continue;
                }
                path.Path[e] = e;

                // This checks if we can keep the target of this edge
                // fixed, given the variable mappings we have so far.
                bool isTargetFixable =
                    msg.Contains(e.AsStatement().Replace(path.Mapping));

                // If the target of e is any of the following, we
                // can check immediately if the edge is supported
                // by the MSG under the variable mapping we have so far:
                //    a named node, literal, fixed node, or predicate
                //    a variable we've seen already
                // If it's not supported, this path fails.  If it is
                // supported, we're done with this edge.
                if (!(e.End is BNode) ||
                    path.FixedNodes.Contains(e.End) ||
                    predicates.Contains(e.End) ||
                    path.VariableNodes.Contains(e.End))
                {
                    if (!isTargetFixable)
                    {
                        return(false);
                    }
                    continue;                     // this edge is supported, so we can continue
                }

                // The target of e is a new BNode.
                // If this target is not fixable via this edge, it's
                // not fixable at all.

                if (!isTargetFixable)
                {
                    fixabletargetsset.Remove(e.End);
                    variabletargetsset.Add(e.End);
                }

                if (!alltargets.Contains(e.End))
                {
                    alltargets.Add(e.End);
                    fixabletargetsset.Add(e.End);
                }

                newedges.Add(e);
            }

            // If all of the targets were fixable (trivially true also
            // if there simple were no new edges/targets), then we've reached
            // the end of this path.  We can immediately remove
            // the edges we've seen so far, under the variable mapping
            // we've chosen.
            if (variabletargetsset.Count == 0)
            {
                foreach (Edge e in path.Path.Keys)
                {
                    Statement s = e.AsStatement();
                    msg.Remove(s);
                    if (removed != null)
                    {
                        removed.Add(s);
                    }
                }
                foreach (Entity e in path.Mapping.Keys)
                {
                    nodesremoved.Add(e);
                }
                return(true);
            }

            // At this point, at least one target must be a variable
            // and we'll have to expand the path in that direction.
            // We might want to permute through the ways we can
            // take fixable nodes as either fixed or variable, but
            // we'll be greedy and assume everything fixable is
            // fixed and everything else is a variable.

            path.FixedNodes.AddRange(fixabletargetsset);
            path.VariableNodes.AddRange(variabletargetsset);

            // But we need to look at all the ways each variable target
            // can be mapped to a new value, which means intersecting
            // the possible matches for each relevant edge.
            Entity[]   variables    = variabletargetsset.ToEntityArray();
            ResSet[]   values       = new ResSet[variables.Length];
            Entity[][] values_array = new Entity[variables.Length][];
            int[]      choices      = new int[variables.Length];
            for (int i = 0; i < variables.Length; i++)
            {
                foreach (Edge e in newedges)
                {
                    if (e.End != variables[i])
                    {
                        continue;
                    }

                    // Get the possible values this edge allows
                    Resource[] vr;
                    if (e.Direction)
                    {
                        vr = msg.SelectObjects((Entity)path.Mapping[e.Start], e.Predicate);
                    }
                    else
                    {
                        vr = msg.SelectSubjects(e.Predicate, (Entity)path.Mapping[e.Start]);
                    }

                    // Filter out literals and any variables
                    // on the path!  The two paths can't intersect
                    // except at fixed nodes.
                    ResSet v = new ResSet();
                    foreach (Resource r in vr)
                    {
                        if (r is Literal)
                        {
                            continue;
                        }
                        if (path.Mapping.ContainsKey(r))
                        {
                            continue;
                        }
                        v.Add(r);
                    }

                    // Intersect these with the values we have already.
                    if (values[i] == null)
                    {
                        values[i] = v;
                    }
                    else
                    {
                        values[i].RetainAll(v);
                    }

                    // If no values are available for this variable,
                    // we're totally done.
                    if (values[i].Count == 0)
                    {
                        return(false);
                    }
                }

                choices[i]      = values[i].Count;
                values_array[i] = values[i].ToEntityArray();
            }

            // Now we have to permute through the choice of values.
            // Make an array of the number of choices for each variable.
            Permutation p = new Permutation(choices);

            int[] pstate;
            while ((pstate = p.Next()) != null)
            {
                SyncPath newpath = new SyncPath();
                newpath.FixedNodes.AddRange(path.FixedNodes);
                newpath.VariableNodes.AddRange(path.VariableNodes);
                newpath.Mapping = (Hashtable)path.Mapping.Clone();
                newpath.Path    = (Hashtable)path.Path.Clone();

                newpath.FrontierVariables = variabletargetsset;

                for (int i = 0; i < variables.Length; i++)
                {
                    Entity value = values_array[i][pstate[i]];
                    newpath.Mapping[variables[i]] = value;
                    newpath.FixedNodes.Add(value);
                }

                if (MakeLeanMSG3(msg, predicates, removed,
                                 nodesremoved, newpath))
                {
                    return(true);
                }
            }

            return(false);
        }
        private static void MakeLeanMSG2(Store msg, ResSet predicates, StatementSink removed,
                                         ResSet nodesremoved, BNode startingnode)
        {
            // Find every pair of two distinct outgoing edges from startingnode
            // with the same predicate, targeting entities only.

            MultiMap edges = new MultiMap();

            foreach (Statement s in msg.Select(new Statement(startingnode, null, null)))
            {
                if (s.Object is Entity)
                {
                    edges.Put(new Edge(true, startingnode, s.Predicate, null), s.Object);
                }
            }
            foreach (Statement s in msg.Select(new Statement(null, null, startingnode)))
            {
                edges.Put(new Edge(false, startingnode, s.Predicate, null), s.Subject);
            }

            foreach (Edge e in edges.Keys)
            {
                // Make sure we have a distinct set of targets.
                ResSet targets_set = new ResSet();
                foreach (Entity r in edges.Get(e))
                {
                    targets_set.Add(r);
                }
                if (targets_set.Count == 1)
                {
                    continue;
                }

                IList targets = targets_set.ToEntityArray();

                // Take every pair of targets, provided
                // one is a bnode that can be a variable.
                for (int i = 0; i < targets.Count; i++)
                {
                    if (!(targets[i] is BNode) || predicates.Contains((BNode)targets[i]))
                    {
                        continue;
                    }
                    if (nodesremoved.Contains((BNode)targets[i]))
                    {
                        continue;
                    }
                    for (int j = 0; j < targets.Count; j++)
                    {
                        if (i == j)
                        {
                            continue;
                        }
                        // Create a new synchronous-path object.
                        SyncPath p = new SyncPath();
                        p.FixedNodes.Add((Resource)targets[j]);
                        p.FrontierVariables.Add((Resource)targets[i]);
                        p.Mapping[targets[i]] = targets[j];
                        p.Path[new Edge(e.Direction, e.Start, e.Predicate, (BNode)targets[i])] = p.Path;
                        if (MakeLeanMSG3(msg, predicates, removed, nodesremoved, p))
                        {
                            break;                             // the target was removed
                        }
                    }
                }
            }
        }
Beispiel #8
0
		private static bool MakeLeanMSG3(Store msg, ResSet predicates, StatementSink removed,
			ResSet nodesremoved, SyncPath path) {
			// The variable path has to be expanded by including the statements
			// connected to the variables on the frontier.  Statements
			// mentioning a variable node have already been considered.
			// The target of each such statement can be considered fixed
			// or variable. If a variable is considered fixed, the edge
			// must exist in the MSG substituting the variables for their
			// values.  If it's variable, it has to have at least one
			// match in the MSG but not as any of the variable nodes.
			// If all targets are considered fixed (and have matches),
			// then the variables so far (and their edges) can all be
			// removed and no more processing needs to be done.
			// There are (2^N)-1 other considerations.  For each of those,
			// the targets considered variables all become the new
			// frontier, and this is repeated. 
			
			// First, get a list of edges from the frontier that we
			// haven't considered yet.
			
			ArrayList alledges = new ArrayList();
			foreach (BNode b in path.FrontierVariables) {
				// Make sure all edges are kept because even the ones
				// to literals have to be removed when duplication is found.
				foreach (Statement s in msg.Select(new Statement(b, null, null)))
					alledges.Add(new Edge(true, b, s.Predicate, s.Object));
				foreach (Statement s in msg.Select(new Statement(null, null, b)))
					alledges.Add(new Edge(false, b, s.Predicate, s.Subject));
			}
			
			ArrayList newedges = new ArrayList();
			ResSet alltargets = new ResSet();
			ResSet fixabletargetsset = new ResSet(); // can be fixed
			ResSet variabletargetsset = new ResSet(); // must be variable
			foreach (Edge e in alledges) {
				if (path.Path.ContainsKey(e)) continue;
				path.Path[e] = e;
				
				// This checks if we can keep the target of this edge
				// fixed, given the variable mappings we have so far.
				bool isTargetFixable =
					msg.Contains(e.AsStatement().Replace(path.Mapping));

				// If the target of e is any of the following, we
				// can check immediately if the edge is supported
				// by the MSG under the variable mapping we have so far:
				//    a named node, literal, fixed node, or predicate
				//    a variable we've seen already
				// If it's not supported, this path fails.  If it is
				// supported, we're done with this edge.
				if (!(e.End is BNode)
					|| path.FixedNodes.Contains(e.End)
					|| predicates.Contains(e.End)
					|| path.VariableNodes.Contains(e.End)) {
					if (!isTargetFixable) return false;
					continue; // this edge is supported, so we can continue
				}
				
				// The target of e is a new BNode.
				// If this target is not fixable via this edge, it's
				// not fixable at all.
				
				if (!isTargetFixable) {
					fixabletargetsset.Remove(e.End);
					variabletargetsset.Add(e.End);
				}
				
				if (!alltargets.Contains(e.End)) {
					alltargets.Add(e.End);
					fixabletargetsset.Add(e.End);
				}
				
				newedges.Add(e);
			}
			
			// If all of the targets were fixable (trivially true also
			// if there simple were no new edges/targets), then we've reached
			// the end of this path.  We can immediately remove
			// the edges we've seen so far, under the variable mapping
			// we've chosen.
			if (variabletargetsset.Count == 0) {
				foreach (Edge e in path.Path.Keys) {
					Statement s = e.AsStatement();
					msg.Remove(s);
					if (removed != null) removed.Add(s);
				}
				foreach (Entity e in path.Mapping.Keys)
					nodesremoved.Add(e);
				return true;
			}
			
			// At this point, at least one target must be a variable
			// and we'll have to expand the path in that direction.
			// We might want to permute through the ways we can
			// take fixable nodes as either fixed or variable, but
			// we'll be greedy and assume everything fixable is
			// fixed and everything else is a variable.
			
			path.FixedNodes.AddRange(fixabletargetsset);
			path.VariableNodes.AddRange(variabletargetsset);

			// But we need to look at all the ways each variable target
			// can be mapped to a new value, which means intersecting
			// the possible matches for each relevant edge.
			Entity[] variables = variabletargetsset.ToEntityArray();
			ResSet[] values = new ResSet[variables.Length];
			Entity[][] values_array = new Entity[variables.Length][];
			int[] choices = new int[variables.Length];
			for (int i = 0; i < variables.Length; i++) {
				foreach (Edge e in newedges) {
					if (e.End != variables[i]) continue;
					
					// Get the possible values this edge allows
					Resource[] vr;
					if (e.Direction)
						vr = msg.SelectObjects((Entity)path.Mapping[e.Start], e.Predicate);
					else
						vr = msg.SelectSubjects(e.Predicate, (Entity)path.Mapping[e.Start]);
					
					// Filter out literals and any variables
					// on the path!  The two paths can't intersect
					// except at fixed nodes.
					ResSet v = new ResSet();
					foreach (Resource r in vr) {
						if (r is Literal) continue;
						if (path.Mapping.ContainsKey(r)) continue;
						v.Add(r);
					}
					
					// Intersect these with the values we have already.
					if (values[i] == null)
						values[i] = v;
					else
						values[i].RetainAll(v);
						
					// If no values are available for this variable,
					// we're totally done.
					if (values[i].Count == 0) return false;
				}
				
				choices[i] = values[i].Count;
				values_array[i] = values[i].ToEntityArray();
			}
			
			// Now we have to permute through the choice of values.
			// Make an array of the number of choices for each variable.
			Permutation p = new Permutation(choices);
			int[] pstate;
			while ((pstate = p.Next()) != null) {
				SyncPath newpath = new SyncPath();
				newpath.FixedNodes.AddRange(path.FixedNodes);
				newpath.VariableNodes.AddRange(path.VariableNodes);
				newpath.Mapping = (Hashtable)path.Mapping.Clone();
				newpath.Path = (Hashtable)path.Path.Clone();
				
				newpath.FrontierVariables = variabletargetsset;
				
				for (int i = 0; i < variables.Length; i++) {
					Entity value = values_array[i][pstate[i]];
					newpath.Mapping[variables[i]] = value;
					newpath.FixedNodes.Add(value);
				}

				if (MakeLeanMSG3(msg, predicates, removed,
					nodesremoved, newpath)) return true;
			}
			
			return false;
		}
Beispiel #9
0
		private static void MakeLeanMSG2(Store msg, ResSet predicates, StatementSink removed,
			ResSet nodesremoved, BNode startingnode) {
			
			// Find every pair of two distinct outgoing edges from startingnode
			// with the same predicate, targeting entities only.
			
			MultiMap edges = new MultiMap();
			
			foreach (Statement s in msg.Select(new Statement(startingnode, null, null)))
				if (s.Object is Entity)
					edges.Put(new Edge(true, startingnode, s.Predicate, null), s.Object);
			foreach (Statement s in msg.Select(new Statement(null, null, startingnode)))
				edges.Put(new Edge(false, startingnode, s.Predicate, null), s.Subject);
			
			foreach (Edge e in edges.Keys) {
				// Make sure we have a distinct set of targets.
				ResSet targets_set = new ResSet();
				foreach (Entity r in edges.Get(e))
					targets_set.Add(r);
				if (targets_set.Count == 1) continue;
				
				IList targets = targets_set.ToEntityArray();
				
				// Take every pair of targets, provided
				// one is a bnode that can be a variable.
				for (int i = 0; i < targets.Count; i++) {
					if (!(targets[i] is BNode) || predicates.Contains((BNode)targets[i])) continue;
					if (nodesremoved.Contains((BNode)targets[i])) continue;
					for (int j = 0; j < targets.Count; j++) {
						if (i == j) continue;
						// Create a new synchronous-path object.
						SyncPath p = new SyncPath();
						p.FixedNodes.Add((Resource)targets[j]);
						p.FrontierVariables.Add((Resource)targets[i]);
						p.Mapping[targets[i]] = targets[j];
						p.Path[new Edge(e.Direction, e.Start, e.Predicate, (BNode)targets[i])] = p.Path;
						if (MakeLeanMSG3(msg, predicates, removed, nodesremoved, p))
							break; // the target was removed
					}
				}
			}
		}