Anhänge programmatisch verschieben

Jan J. hinzugefügt 3 Monaten her
unbeantwortet

Liebes Citavi-Team,


da manchmal doch komfortabler am Tablet zu lesen würde ich gerne ausgewählte Beiträge vom Citavi-Ordner in einen anderen Ordner verschieben. Ich habe daher ein Makro, welches Citavi-Anhänge entkoppelt und in den Ordner der Wahl verschiebt.


using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Net;
using System.Linq;
using System.IO;
using System.Text;
using System.Globalization;

using SwissAcademic.Citavi;
using SwissAcademic.Citavi.Metadata;
using SwissAcademic.Citavi.Shell;
using SwissAcademic.Collections;

using System.Reflection;
using SwissAcademic.Citavi.Shell.Controls.Preview;

public static class CitaviMacro
{
	public static void Main()
	{
		if (Program.ProjectShells.Count == 0) return;		//no project open
		
		Program.ActiveProjectShell.PrimaryMainForm.PreviewControl.ShowNoPreview();
		
		// User Options
		
		// Static Variables
		
		Project activeProject = Program.ActiveProjectShell.Project;
		string citaviAttachmentsFolderPath = activeProject.Addresses.AttachmentsFolderPath;
		List<Reference> references = Program.ActiveProjectShell.PrimaryMainForm.GetSelectedReferences();
		
		// Dynamic Variables
		
		string syncFolder = string.Empty;
		int renameCounter = 0;
		List<Reference> renamingFailed = new List<Reference>();
		
		var fbd = new FolderBrowserDialog();

		// The Magic
		
		DialogResult result = fbd.ShowDialog();
		syncFolder = fbd.SelectedPath;

		foreach (Reference reference in references)
		{
			if (reference.Locations.Count == 0) continue;
			
			// First we move the existing locations
			var locations = reference.Locations.ToList().Where(a => a.LocationType == LocationType.ElectronicAddress && a.Address.LinkedResourceType == LinkedResourceType.AbsoluteFileUri);
			foreach (Location location in locations)
			{			
				if (location == null) return;
				if (location.Address == null) continue;
				if (location.Address.UriString == null) continue;
				
				string oldFilePath = (location.Address.UriString);
								
				FileInfo fileInfo = new FileInfo(oldFilePath);
				
				string fileExtension = fileInfo.Extension;
				if (fileExtension.Equals("pdf", StringComparison.InvariantCultureIgnoreCase)) continue;
				
				string newFilePath = syncFolder + @"\" + System.IO.Path.GetFileName(location.Address.UriString);
				
				try
				{
					File.Move(oldFilePath, newFilePath);
					reference.Locations.Remove(location);
					reference.Locations.Add(LocationType.ElectronicAddress, newFilePath);
					renameCounter++;
				}
				catch (Exception e)
				{
					MessageBox.Show("An error has occured while renaming " + location.Address + ":\n" + e.Message, "Citavi Macro", MessageBoxButtons.OK, MessageBoxIcon.Error);
					renamingFailed.Add(reference);
				}				
			}// end foreach (Location location in reference.Locations)
			
			locations = reference.Locations.ToList().Where(a => a.LocationType == LocationType.ElectronicAddress && a.Address.LinkedResourceType == LinkedResourceType.AttachmentFile);			
			foreach (Location location in locations)
			{			
				if (location == null) return;
				if (location.Address == null) continue;
				if (location.Address.UriString == null) continue;
				
				string oldFilePath = (citaviAttachmentsFolderPath + "\\" + location.Address.UriString);
								
				FileInfo fileInfo = new FileInfo(oldFilePath);
				
				string fileExtension = fileInfo.Extension;
				if (fileExtension.Equals("pdf", StringComparison.InvariantCultureIgnoreCase)) continue;
				
				string newFilePath = syncFolder + @"\" + location.Address.UriString;
				
				if (File.Exists(newFilePath)) continue;
				
				try
				{
					File.Move(oldFilePath, newFilePath);
					reference.Locations.Remove(location);
					reference.Locations.Add(LocationType.ElectronicAddress, newFilePath);
					renameCounter++;
				}
				catch (Exception e)
				{
					MessageBox.Show("An error has occured while renaming " + location.Address + ":\n" + e.Message, "Citavi Macro", MessageBoxButtons.OK, MessageBoxIcon.Error);
					renamingFailed.Add(reference);
				}				
			}// end foreach (Location location in reference.Locations)			
		}// foreach (Reference reference in references)

		// Message upon completion
		string message = "{0} Locations have been renamed.";
		message = string.Format(message, renameCounter);

		if (renamingFailed.Count == 0)
		{
			MessageBox.Show(message, "Citavi Macro", MessageBoxButtons.OK, MessageBoxIcon.Information);
			return;
		}
		else
		{
			DialogResult showFailed = MessageBox.Show(message + "\n Would you like to show a selection of references where renaming has failed?", "Citavi Macro", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
			if (showFailed == DialogResult.Yes)
			{
				var filter = new ReferenceFilter(renamingFailed, "Renaming failed", false);
				Program.ActiveProjectShell.PrimaryMainForm.ReferenceEditorFilterSet.Filters.ReplaceBy(new List<ReferenceFilter> { filter });
				return;
			}
		}
		
		activeProject.SaveAsync();
	}
}
Der Konnaisseur sieht das Problem in Zeilen 71 ff und 103 ff. Das geht im Moment nur, indem Citavi vergisst, dass die Datei bereits mit dem Titel verknüpft ist. Stattdessen wird die Datei systemseitig verschoben und ein neuer Standortnachweis erstellt. Problem an dieser Vorgehensweise ist in erster Linie dass danach auch die Verknüpfungen zwischen Wissenselementen und PDF weg sind. Daher meine Frage: gibt es eine bessere Methode als


File.Move(oldFilePath, newFilePath);
					reference.Locations.Remove(location);
					reference.Locations.Add(LocationType.ElectronicAddress, newFilePath);
					renameCounter++;
Um eine Location des LocationType ElectronicAddress sowie des Address.LinkedResourceType AttachmentFile (ich hab mir das nicht ausgedacht) zu verschieben in einen anderen Ordner als den Citavi-Projektordner zu verschieben? Ein analoges Problem gibt es dann auch in der anderen Richtung:


using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Net;
using System.Linq;
using System.IO;
using System.Text;
using System.Globalization;

using SwissAcademic.Citavi;
using SwissAcademic.Citavi.Metadata;
using SwissAcademic.Citavi.Shell;
using SwissAcademic.Collections;

using System.Reflection;
using SwissAcademic.Citavi.Shell.Controls.Preview;

public static class CitaviMacro
{
	public static void Main()
	{
		if (Program.ProjectShells.Count == 0) return;		//no project open
		
		Program.ActiveProjectShell.PrimaryMainForm.PreviewControl.ShowNoPreview();
		
		//iterate over all references in the current filter (or over all, if there is no filter)
		List<Reference> references = Program.ActiveProjectShell.PrimaryMainForm.GetSelectedReferences();
		
		// User Options
		
		string syncService = "Dropbox";
		string syncFolderName = "Citavi Attachments";
		
		// Static Variables
		
		Project activeProject = Program.ActiveProjectShell.Project;
		string citaviAttachmentsFolderPath = activeProject.Addresses.AttachmentsFolderPath;
		string userProfileFolderPath =  Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
		
		// Dynamic Variables
		
		string syncFolder = string.Empty;
		int renameCounter = 0;
		List<Reference> renamingFailed = new List<Reference>();
		
		// The Magic
		
		syncFolder  = userProfileFolderPath + @"\" + syncService + @"\" + syncFolderName;

		foreach (Reference reference in references)
		{
			if (reference.Locations.Count == 0) continue;
			
			var locations = reference.Locations.ToList().Where(a => a.LocationType == LocationType.ElectronicAddress && a.Address.LinkedResourceType == LinkedResourceType.AbsoluteFileUri);
			
			foreach (Location location in locations)
			{			
				if (location == null) return;
				if (location.Address == null) continue;
				if (location.Address.UriString == null) continue;
				
				string oldFilePath = (location.Address.UriString);
								
				FileInfo fileInfo = new FileInfo(oldFilePath);
				
				string fileExtension = fileInfo.Extension;
				if (fileExtension.Equals("pdf", StringComparison.InvariantCultureIgnoreCase)) continue;
				
				try
				{
					reference.Locations.Remove(location);
					reference.Locations.Add(oldFilePath, AttachmentAction.Move, AttachmentNaming.Auto);
					renameCounter++;
				}
				catch (Exception e)
				{
					MessageBox.Show("An error has occured while renaming " + location.Address + ":\n" + e.Message, "Citavi Macro", MessageBoxButtons.OK, MessageBoxIcon.Error);
					renamingFailed.Add(reference);
				}				
				
			}// end foreach (Location location in reference.Locations)
		}// foreach (Reference reference in references)

		// Message upon completion
		string message = "{0} Locations have been renamed.";
		message = string.Format(message, renameCounter);

		if (renamingFailed.Count == 0)
		{
			MessageBox.Show(message, "Citavi Macro", MessageBoxButtons.OK, MessageBoxIcon.Information);
			return;
		}
		else
		{
			DialogResult showFailed = MessageBox.Show(message + "\n Would you like to show a selection of references where renaming has failed?", "Citavi Macro", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
			if (showFailed == DialogResult.Yes)
			{
				var filter = new ReferenceFilter(renamingFailed, "Renaming failed", false);
				Program.ActiveProjectShell.PrimaryMainForm.ReferenceEditorFilterSet.Filters.ReplaceBy(new List<ReferenceFilter> { filter });
				return;
			}
		}
		
		activeProject.SaveAsync();
	}
}


In Zeile 72 ff führt


					reference.Locations.Remove(location);
					reference.Locations.Add(oldFilePath, AttachmentAction.Move, AttachmentNaming.Auto);
					renameCounter++;


ebenfalls dazu, dass die Verknüpfung weg ist.


Beste Grüße

JJ

Antworten (8)

Foto
1

In die Gegenrichtung habe ich das jetzt gelöst, indem ich


reference.Locations.Remove(location);
reference.Locations.Add(oldFilePath, AttachmentAction.Move, AttachmentNaming.Auto);


durch


location.Address.AttachLinkedFileAsync(true, AttachmentNaming.Auto);
ersetzt habe, aber ich habe noch nicht rausgekriegt, wie ich ein Attachment in ein LinkedFile verwandele, bzw. ein Attachment in einen externen Ordner verschiebe , ohne die bestehende Verknüpfung aufzulösen.

Foto
1

Ich hatte jetzt gedacht, ich mache das so, dass ich einen Unterordner anlege, welcher im Projektordner liegt, und welcher per Junction auf einen Ordner in der Cloud verweist. Dann würde die Datei einfach innerhalb Citavis umbenannt, und so würde die Verknüpfung immer erhalten bleiben. Unter Citavi 5 konnte man nämlich noch per Makro Attachments in einen Unterordner des Projektverzeichnisses verschieben . Ich hatte etwa verkürzt sowas hier gemacht:


newFilePath = activeProject.Addresses.AttachmentsFolderPath + @"\" + subfolderName + @"\" + fileName + fileExtension;
fileInfo.MoveTo(newFilePath);
location.Address = newFilePath;


Damit konnte man Dateien selektiv in einen Unterordner verschieben. Wenn man was ähnliches jetzt ausführt, kommt eine Fehlermeldung: "Die angegebene Methode wird nicht unterstützt". Die angegebene Methode muss


location.Address = newFilePath;


sein, denn wenn ich das wegkommentiere ist alles in Ordnung, dann passiert allerdings natürlich auch nichts, außer daß Citavi wenig überraschend die Datei nicht mehr wiederfinden kann, denn die ist ja ordnungsgemäß verschoben werden.


Die Methode


location.Address.Rename(newFilePath);

welche in Citavi 6 glaube ich neu ist, hingegen führt dazu, dass Citavi alles ignoriert, was nicht Dateiname und -erweiterung ist, egal ob newFilePath nun als absoluter oder relativer Pfad definiert wird.

Foto
1

Lieber Jan Jakob,


das ist tatsächlich momentan nicht so ohne weiteres möglich. Ich werde die API hierfür ein wenig "aufbohren" und mit 6.2 veröffentlichen. Die neue Methode wird voraussichtlich wie unten beschrieben implementiert:


public async Task ChangeFilePathAsync(Uri newUri, AttachmentAction attachmentAction = AttachmentAction.Move, CancellationToken cancellationToken = default(CancellationToken))
Viele Grüße,

Torben

Foto
1

Wenn mir eine Rückfrage gestattet sei: Wie soll das denn theoretisch funktionieren? Ich habe jetzt mal in der neuesten Beta 6.1.6 versucht, die Datei zu verschieben, per


					location.Address.ChangeFilePathAsync(new System.Uri(newAbsoluteFilePath), AttachmentAction.Move, default(CancellationToken));
an der entsprechenden Stelle. Der Erfolg ist, dass die Datei zwar verschoben wird, Citavi aber weiterhin die Datei an der alten Stelle erwartet. Ich vermute, das liegt daran, dass ich nichts sinnvolles als drittes Argument eingetragen habe?

Foto
1

Lieber Jan Jakob,


es gab noch einen kleinen Fehler in bestimmten Situationen, dieser sollte aber mit der neuen Beta von gestern behoben sein. Ich würde mich freuen, wenn du es erneut testen würdest.


Liebe Grüße,

Torben

Foto
Foto
1

Hallo Thorben,


Super, das klingt ja vielversprechend.


Beste Grüße

Jan Jakob

Foto
1

Klingt nach von-hinten-durchs-auge. Warum machst verlinkst du nicht die Dateien? Unter Linux wären das hard- oder symbolic Links - was es unter Windows aber mitlerweile auch gibt. Welche Link-Art du benötigst, hängt davon ab, was du wirklich machen willst.


Falls du Python kannst, kann ich dir ein Grund-Skript anbieten, mit dem du direkt die Citiav-Datei auslesen und bearbeiten kannst - ist ja nur ne sqlite3 Datei.

Foto
1

Da die Auswahl der Dateien über die GUI erfolgen soll, nach den verschiedensten Suchkriterien, je nachdem, was ich gerade benötige, ist es einfacher, dass über Citavi zu machen, damit ich Zugriff auf die aktuelle Auswahl habe. Das mit den Links stimmt wahrscheinlich, aber einfacher oder komplizierter in C# als die Datei zu verschieben scheint mir das auch nicht zu sein.