-
Notifications
You must be signed in to change notification settings - Fork 4
Code documentation
-
Plugin specific files
-
the plugin needs to store different kinds of files: configuration files, survey results, recorded events, program snapshots, etc
-
because of the nature of how IJ and Eclipse run, there are multiple paths where the plugin stores its files. When you open a project in IJ or a workspace in Eclipse, both IJ and Eclipse spawn a new instance of the IDE over that project / workspace. This means that both IDEs have two kinds of locations: the installation location of the IDE (where the IDE stores information relevant to all IDE instances, like common settings for all plugins) and the instance location (where the IDE stores information relevant only to a specific instance). This means that in both Eclipse and IJ, plugins have two storage spaces. Thus, our plugin also has two storage paths: the local storage (specific to each project / workspace) and the bundle storage (common to all IDE instances). There is one bundle storage, and multiple local storages.
- the local storage: eventFiles, workspace properties ("config"), logs, project snapshots, projects to ignore, known projects, workspace unique ID.
- the bundle storage: install properties ("config-install") that are global to all instances (e.g., uninstall date, FTP settings), survey responses, user email
-
since our plugin supports updates, and since the event format may change between updates, we store version sensitive files in versioned files
-
StorageManager knows how to provide the local and bundle storage, and versioned variants
-
File syncing between the local storage and the bundle storage
-
the user may perform activities that lead to the deletion of the plugin's files (upgrading the IDE leads to the deletion of the bundle storage, deleting or creating new projects or workspaces leads to new local storage). When this happens we do not want to redo certain tasks, like re-running the survey or resetting the uninstall date. To prevent this, we backup the bundle storage in all the local storages.
-
we also use the backup mechanism to detect new installations (when the bundle storage files do not exist anywhere) and perform installation specific tasks (e.g., running the survey).
-
Installer and InstallerOperation control how file syncing is performed.
-
Uninstall
-
the plugin must stop recording after a certain amount of time
-
Uninstaller is responsible for knowing when the plugin should shut down. The uninstall date is set at plugin installation
-
Snapshots
-
we need to snapshot the entire project at certain key moments:
- Eclipse:
- recording an event in a previously unrecorded project
- when the IDE is closed
- when plugin is updated
- IJ:
- when the plugin is first started
- when the IDE is closed
- when the plugin is update
- Eclipse:
-
snapshotting is useful for:
- it allows us to build the entire recorded project after any change
- if there is a recording error that would cause spurious replay, snapshotting at regular intervals reduces the section length of bad recordings
-
Event recording
-
overall picture: Multiple IDE specific event listeners are subscribed to event sources at plugin startup. Each event listener records the event via the IDE agnostic ClientRecorder.
-
recorded events
- The Events enum contains all the events the plugin is currently recording
-
decoupling event format, storage format, and server storage
- the event format (currently JSON) is specified by the ClientRecorder
- the storage format is specified by ChangePersister and subclasses of the FileProvider. Currently each calendar day has an event file with all the events recorded in that day.
-
To implement new events, one has to write the new event listener, hook it up to the event source at plugin startup (see plugin entry methods), update the Events enum with the new event, and implement the translation to JSON in ClientRecorder. Last, the new event listener needs to call the ClientRecorder after each event.
-
Plugin update
- each IDE implements its own update mechanism. Our plugin detects when it has been magically updated by the IDE and creates new versioned storage files to reflect the new update version (Installer.doUpdate()).
-
Uniquely identify a workspace
- on the FTP server we keep the events as a flat list of workspaces (one directory per workspace). Therefore each workspace has to have a unique ID.
- the RecorderFacade is responsible for providing the id for the current workspace.
-
Choose which projects to record
- Eclipse specific: a user may not want all the projects in the workspace recorded. Therefore the plugin must ask the user at install time which projects to record
-
User survey: at plugin install the user is asked to complete a survey for demographic reasons (so we can characterize the developer population in our research papers). SurveyOperation (and its IDE specific implementations) is responsible for deciding when and how to run the survey.
-
Send ALL local storage files to the server (the most precious of which are the event files)
- The filesender project implements a CRON job that periodically sends all the local storage files to the server
- FileSenderJob sends the files; FileSender sets up the CRON job.
-
Delete event files older than N days
- In order to prevent the user workspace to grow unbounded in size, we delete old events.
- DeleteOldFilesUtil implements this
-
Entry methods (main methods)
-
These are methods where the control flow of the plugin starts. It's good to read them to get a sense of how all the supported use cases are instantiated. Reading both Eclipse and IJ entry methods gives you a good sense of the common concepts
-
They instantiate the common infrastructure, perform install / update / uninstall operations, snapshot the workspace, and if all goes well, instantiate and hook up all the event listeners.
-
IntelliJ: recording is performed on a per project basis (an opened instance of IJ has only one project)
- entry point: COPEComponent.projectOpened()
-
Eclipse: recording is performed on a per workspace basis (an opened instance of Eclipse has multiple projects)
- entry point: COPEPlugin.start() which then calls StartPluginUIJob.runInUIThread()
-
IDE agnostic interfaces
-
These interface (and abstract classes) reside in the common infrastructure repository and define common functionalities for both IDEs. Both IDEs implement these interfaces.
-
StorageManager: provides the IDE specific paths to the local storage, the bundle storage, and to the versioned variants of these paths (see section on plugin stored files).
-
Installer - specifies what happens on plugin startup
- InstallerOperation - specifies how file syncing between the bundle and local storage is performed
- SurveyOperation - specifies how the survey is recorded
-
RecorderFacadeInterface - this facade provides the functionality of the common infrastructure (event recorder, installer operations, uninstaller, property files, workspace ID, etc) to the IDE specific code.